| iMatix home page | << | < | > | >> |
SMT Version 2.81 |
The SMT (Simple Multi-Threading kernel) from iMatix is an add-on for the Libero programming tool that lets you write portable high-performance multithreaded programs based on Libero's finite-state machine (FSM) design method.
You may want to go straight to the Table of Contents.
You can use the SMT kernel for:
The SMT kernel's main features are:
The SMT kernel currently supports C/C++, and is written in portable ANSI C. It uses the iMatix SFL (standard function library) as a technical platform, and the Libero tool as development method. Libero, the SFL, and the SMT kernel are provided as free software, copyrighted by iMatix. You may freely incorporate the SFL and SMT packages in your applications with a minimum of conditions. Read the SMT license agreement for details.
To use the SMT kernel you should have used Libero and understand the concepts involved. SMT applications are essentially written using Libero. We assume that the reader has some familiarity with Libero.
The SMT kernel has been ported to MS-DOS, Windows, UNIX systems (Linux, IBM AIX, SunOS, HP/UX) and should be portable to Digital VMS. It comes with complete sources and documentation in HTML. It includes XITAMI, a portable multithreaded web server.
Send comments about the SMT kernel, bug reports, and submissions to smt@imatix.com.
SMT 2.0 is an evolution of SMT version 1.x of the iMatix SMT kernel. SMT 1.x was specifically designed for TCP/IP server development. SMT 2.x is a more generalised approach that makes multithreaded FSMs suitable for a variety of purposes. SMT 2.0 is not backwards compatible with SMT 1.x, but it is quite straight-forward to convert applications.
The main objectives of SMT 1.0 were:
SMT 2.0 is meant to answer a wider set of issues. Specifically, our goal was to provide multithreading capability in these domains:
In SMT 1.x, external events were received and processed by a kernel built-in to each SMT program. The SMT 1.x kernel was specifically designed to be driven by TCP/IP events. An SMT 1.x program is invoked, does its work, and finally terminates. We call this type of program a 'batch' program. This is suitable for servers ('daemons'), but not for real-time programs that must be integrated into an large-scale event-driven architecture.
SMT 2.x is designed as an event-passing kernel. The application consists of a number of agent programs, each running one or more threads. Each thread has an event queue. Threads send each other events, which are queued, and delivered by the kernel to the state machine that controls each thread. The SMT kernel API lets you send and receive events, create threads, etc. There are various ways to construct an agent program (single- threaded, multithreaded), and different ways to handle event queues (one thread per queue, or several threads per queue). Agents and events can have priorities, which changes the order of execution and delivery.
SMT provides a number of standard agents that are easily reused in applications. For example, in an Internet application, the socket i/o agent collects events from the Internet sockets used by the application. When a thread wants to read data from a socket, it sends an event to the socket i/o agent, telling it which port, and how much data. The socket i/o agent reads the data and returns that as a event. Other standard agents are: a logging agent to write log file data; an operator console agent to handle error and warning events; a timer agent to generate alarm events.
SMT also includes a number of protocol agents for use in SMT applications: echo, HTTP, FTP.
In SMT 1.x, external events such as TCP/IP events were collected by the kernel, in SMT 2.x such events are collected by an agent program. Thus it is possible to add support to any external event source.
Multithreaded programming is often perceived as complicated. When we look at multithreading facilities provided by existing operating systems, we tend to agree. The most common type of multithreading is pre-emptive multithreading. This is typically seen on UNIX and Windows NT systems. The characteristics of this approach are:
A less common type of multithreading is cooperative multithreading. One example is the chained multithreading method used on Digital VMS systems. The characteristics of this approach are:
If we compare these methods, we can see advantages and disadvantages in each:
Both these methods are expensive to program, and can produce code that is hard to maintain, error-prone, and therefore very expensive to make robust enough for real applications.
In 1990, Leif Svalgaard wrote a tiny multitasking monitor for MS-DOS, to demonstrate that multitasking did not require megabytes of memory. This monitor was based on an event-passing kernel. It worked well and could multitask several DOS sessions simply and efficiently. This project was remarkable because it took a very short time to write (one long weekend) and because it required so little memory to run (several kilobytes). This monitor was based on Leif Svalgaard's earlier work in operating systems design, and defined the core principles of the SMT 2.x kernel.
In 1993, Pieter Hintjens developed a complex multithreaded application using the Digital VMS chained multithreading method. Under severe time constraints, he was obliged to take a radical alternative to the normal approach. He used Libero to abstract the 'chain' of multithreaded logic. This reduced the development cost by an estimated 80%, and resulted in a very stable and efficient application. This experience showed that the Libero state-machine abstraction - already useful for writing normal procedural code - was also good in multithreaded applications.
In 1993, Christian Rozet and Stephen Bidoul of ACSE built a version of Libero that generated a C++ 'asynchronous finite-state machine' to handle events coming from a GUI (MS-Windows). The resulting applications were in effect multithreaded applications, with the multithreading handled by Libero (actually the code that Libero generated).
In 1995, Pieter Hintjens and Pascal Antonnaux built SMT version 1.0, and a set of demonstration programs. The smthttpd web server ran on UNIX and Windows 95, showing that portable multitasking was a realistic objective.
SMT 2.0 is a fresh approach that combines the experience of these projects:
The main differences between SMT and 'classic' multithreading are:
We note some other points of interest:
We will consider two specific problems. Firstly, construction of an industrial-scale Internet server. Secondly, construction of a multi-level finite-state machine application.
There are many different ways to design an Internet server. The main problem is to handle multiple connections at once ('concurrency'). The classic way to get concurrency is to use the operating system multitasking functions. This is straight-forward enough. For instance, under UNIX, the server process uses the fork() system call to create a 'clone' of itself. At any moment there are multiple copies of the server process, each handling one connection. The operating system switches rapidly between these processes, so giving concurrency.
The problems with this design become apparent when you try to use it for large-scale work. Firstly, it is not portable - the fork() system call does not work on all operating systems. Secondly, each fork() call duplicates the server program in memory. This duplication takes a certain time, as does the eventual removal of the server process. A protocol like HTTP creates a large number of short-term connections. Lastly, each additional instance of the server process consumes system resources so that a typical system cannot handle more than a few hundred connections.
There are variations on this design that eliminate some of the problems. For instance, you can create a fixed number of instances of the server beforehand, then allow that number of connections. This eliminates the cost of creating and removing server processes, but does not raise the ceiling on the maximum number of connections.
A more sophisticated approach is to handle multiple connections within a single process. This is relatively simple to arrange, using the BSD socket select() function. This lets a program wait for activity on a set of open sockets. The logic of such a program is: wait for activity on a socket; handle the activity; repeat. This approach works when the logic of 'handle the activity' is simple. In realistic applications, however, this logic becomes complex, and involves activity such as reading or writing to files, or manipulating several sockets at once.
The SMT kernel uses the last approach, but provides a level of abstraction that makes the approach practical for large-scale problems. You can create one or several 'threads'. Each thread executes a copy of the finite state machine. The basic unit of logic in a thread is the code module.
The number of threads is limited only by the memory available to the process. Creating or removing a thread is fast (so a new connection can be established faster than using a fork() call), and as far as the operating system is concerned, there is just one process (so the cost to the operating system is lower).
Let's consider the design of an application that consists of several interworking state machines. This is the kind of design one finds in telecommunications and other specialised domains. The approach can be used in many areas. Typically, such a state machines processes an event queue; one state machine can send events to another.
In this type of design we need to save the 'state' of each state machine in some way so that it can process events in a meaningful manner. The 'state' consists of the actual state, the last event, and context information that the state machine program needs to remember between events. We can define this 'state' as a thread: the requirements are very close to that of the Internet server described above. If we only want a single thread in any state machine, we can consider this a special case of the general case, which is a full multithreaded approach.
Our conclusion from these two chains of argument is that a state-machine approach to multithreading is useful and valuable in real applications. Since Libero already provides a state-machine abstraction that converts a state-machine diagram into generated code, it is reasonable to use this mechanism to implement a generic and portable type of multithreading.
The SMT 2.x kernel works with a specific Libero code generation schema, smtschm.c, to provide this generic multithreading.
If you intend to write Internet servers, you should have a basic understanding of the concepts behind IP, TCP/IP, and UDP/IP. While the SMT kernel does a good job of packaging and abstracting the Internet programming model, it is no substitute for a solid understanding of the issues involved. We recommend that you be familiar with these aspects at least:
Before designing or writing an SMT application you should understand the Libero method of program design. The main components of an SMT application - the agents - are designed and written using Libero.
Before writing SMT applications you should be familiar with the standard function library (SFL), since many SFL functions are used in a typical SMT application.
An SMT application generally consists of a number of agents, connected to the SMT kernel:
.------------. : SMT kernel | `============" | - -+------------+------+-----+------------+- - | | | | .-------. .-------. .-------. .-------. : Agent | : Agent | : Agent | : Agent | `=======" `=======" `=======" `=======" Figure: Regular SMT application
We may also use an event driver to make an 'embedded application'. This is necessary when external events cannot be collected by a normal SMT agent program such as the socket i/o agent. An example of such an application is one designed for MS Windows.
.--------------. : Event Driver | `==============" | .------------. : SMT kernel | `============" | - -+------------+------+-----+------------+- - | | | | .-------. .-------. .-------. .-------. : Agent | : Agent | : Agent | : Agent | `=======" `=======" `=======" `=======" Figure: Embedded SMT application
There are many other types of SMT application. The SMT kernel defines a number of objects, and operations on those objects. These objects are arranged in a hierarchy: this is structural, not morphological (they do not inherit attributes from each other):
Agent : : Method Queue / Thread Event Figure: Main SMT Objects
The SMT holds these objects in a set of linked lists. Each thread, for instance, contains the reference of its parent queue; a queue refers to its parent agent.
.------------. : SMT kernel | `============" | -+-------------+------+------+-------------+- | | | | .-------. : Agent | `=======" | +-------------+-------------+ | | | .--------. .--------. .--------. : Queue | : Queue | : Queue | `========" `========" `========" | | | .--------. .--------. .--------. : Thread | : Thread | : Thread | `========" `========" `========" Figure: Agent Program With 3 Threads
In normal cases, events are sent to a queue that is held by an agent program. Sometimes this is a limitation: we can see cases where non-agent programs also need to receive events. Any program can send an event: this is just a call to the SMT kernel API.
We therefore define the concept of a floating event queue; that is, an event queue that is managed by some program that is not constructed as an agent:
Program : Queue : Event Figure: Floating Event Queue
The designer of the application must also find a way to service the event queue, since the SMT kernel cannot schedule and execute such a program.
In most cases, one queue is served by one thread. Thus, when you send an event to a queue, you are effectively sending it to a specific thread. In some cases, it is better to share one queue among several threads. Imagine an agent that executes database requests coming from a set of clients. A thread takes an event, processes it, and returns a reply. Each event is independent, and may take some time to process. It makes sense to start several threads, so that several events can be processed in parallel. It would be wrong to allocate one queue per thread, however, for two reasons. Firstly, this implies that there are N queues which the clients must know about. Secondly, it implies that someone (client or agent) chooses a queue for each event. Consequently, events may be waiting in the queue for one thread while another thread is idle. By allocating just one queue, shared between all the agent threads, events are processed at full speed, in arrival order.
An agent is either defined as a router, or not. When an agent is a router, it is legal to create multiple threads with the same name. All threads in a router agent share a single event queue.
SMT agents are built as event-driven Libero programs. We define two kinds of event: an internal event (supplied by the program itself) and an external event (taken from the event queue). If we consider a normal Libero program, all events are internal. The SMT kernel adds the concept of external events.
When you design the program dialog, you do not distinguish internal and external events. Indeed, a name like "Ok" can be used for both types of event.
The SMT kernel delivers an external event when the program did not supply an internal event itself. This occurs at a state transition, i.e. after executing the action modules for an event. When an external event is delivered, it is converted into an appropriate internal event.
To put it another way: if none of the action modules put a value into the_next_event, then the SMT kernel will pull an event from the event queue, translate it into a suitable value, and put that into the_next_event instead.
The methods define the translation from external event to internal event. External events that do not match declared methods are rejected. This is usually a programming error, so an error message is sent to the console.
The SMT kernel provides an level of event management. Firstly, it will destroy undelivered events after a specified expiry period. Secondly, it will automatically send return events to acknowledge receipt or non-receipt of an event. This works as follows: when you send an event, you may specify reply events for delivery, rejection, and expiry.
The SMT kernel has a simple priority and scheduling mechanism. Agents run with a certain priority level (a number from 1 to 255). When the SMT kernel delivers events to threads, it schedules the threads for execution, by placing them in a queue (the active list). Threads with higher priorities are placed at the start of the active list. No account is taken of the time spent in the active list, so it is quite possible for a set of high-priority threads to take-up all execution time. This normally does not happen, since such threads will eventually wait for events from somewhere else, so become passive.
Events have a similar priority level, defined by the accepting program, in the event method. When the SMT kernel delivers an event, it chooses the event with the highest priority. One example of a high-priority event is shutdown, which is sent to all threads when the SMT kernel receives a 'kill' signal (i.e. when someone decides to stop the running program).
You can change agent and event priorities on the fly, although this may get a little complex to manage. You can also process the events in an event queue directly, without waiting for the SMT kernel to deliver each one.
In general we recommend that you leave agents and events with unspecified priority - i.e. the normal priority level - unless there are good reasons for doing otherwise.
A native program is designed as a finite-state machine (FSM) and built using Libero and the smtschm.c schema. A native program can be multithreaded. It is tightly integrated with the SMT kernel: the kernel supplies events to the program, schedules and executes threads, etc.
A native program is also called a agent. The two terms are equivalent.
A foreign program is not based on the smtschm.c schema. It is always single threaded, and while it may call SMT kernel functions, it is not integrated with the kernel. Typically a foreign program calls the SMT kernel to send and receive events and manage floating event queues.
We can consider the application (an ensemble of agents, queues, threads, and events) as a meta-state-machine (i.e. a state machine that defines a set of state machines). The application has three states:
We do not usually define this as a Libero dialog, though it is possible to do so. What we must do is to write a foreign program, a stub, that implements the application meta-state- machine. This is the stub for an Internet ECHO daemon, showing the three states:
#include "sfl.h" /* SFL library header file */ #include "smtlib.h" /* SMT kernel functions */ int main (int argc, char *argv []) { /* Application is latent - initialise it */ smt init (); /* Initialise SMT kernel */ smtecho init (NULL); /* Initialise ECHO agent */ /* Application is active - execute it */ smt exec full (); /* Run until completed */ /* Application is halted - terminate it */ smt term (); /* Shut-down SMT kernel */ return (0); }
This main code is the glue that joins the various pieces of the application.
The SMT kernel API is aimed mainly at native programs. However, you can also use the event-passing facilities in foreign programs. This can be necessary when interfacing SMT applications to other event-based systems.
This is the complete set of functions supported by the SMT kernel:
The SMT kernel API works with a number of objects - agents, threads, queues - which are defined as C structures. The fields in these structures are the properties of the object. The SMT objects contain private fields, which you should never change or refer to, and public fields, which you are free to change and use. We do not discuss the private fields, except to note that these contain information that is internal to the SMT kernel, or reflect particular implementations that may change.
These are the SMT objects and their public fields:
AGENT /* Agent descriptor */ AGENT *next, *prev /* Doubly-linked list */ NODE methods /* Methods accepted by agent */ NODE queues /* Queues defined for agent */ char *name /* Agent's name */ Bool router /* Default = FALSE */ int priority /* Default = SMT_PRIORITY_NORMAL */ long max_threads /* Default = 0 (no limit) */ long cur_threads /* Current number of threads */ long top_threads /* Max. number threads we had */ long thread_tally /* How many threads created */ long switch_tally /* How many context switches */
An agent defines one program written using the Libero SMT schema. When the generated code initialises, it automatically creates an agent object. You may change the router, priority, and max_threads fields, but not the other fields.
METHOD /* Method descriptor */ METHOD *next, *prev /* Doubly-linked list */ AGENT *agent /* Parent agent descriptor */ char *name /* Name of method */ int priority /* Default = SMT_PRIORITY_NORMAL */ int event_number /* Internal event number */
The start-up code for an agent (in Initialise-The-Program) creates a method for each external event it wants to handle. You may change the priority and event_number fields, but not the other fields.
THREAD /* Thread descriptor */ THREAD *next, *prev /* Doubly-linked list */ QUEUE *queue /* Parent queue descriptor */ long thread_id /* Thread identifier number */ char *name /* Name of thread */ Bool animate /* Animate this thread */ void *tcb /* Thread context block (TCB) */ EVENT *event /* Last-received event */
Any part of the application can create a thread in an agent, if it knows the name of the agent. You may change the animate field, but not the other fields.
QUEUE /* Event queue descriptor */ QUEUE *next, *prev /* Doubly-linked list */ AGENT *agent /* Parent agent descriptor */ NODE events /* Events in queue */ NODE threads /* Threads for queue */ QID qid /* Queue ID descriptor */ int max_events /* Maximum allowed events */ int cur_events /* Current number of events */
The SMT kernel automatically creates event queues as needed. Generally it will create one event queue per thread. When an agent is defined as a router, however, it only creates an event queue for the first thread. You can also create floating event queues. You may change the max_events fields, but not the other fields. Note that each queue has a unique QID; this is the identifier for the queue, and the information you need to send an event to a queue.
EVENT /* Event in queue */ EVENT *next, *prev /* Doubly-linked list */ QUEUE *queue /* Parent queue descriptor */ QID sender /* Replies come back here */ char *name /* Name of event */ size_t body_size /* Size of event body in bytes */ char *body /* Event body */ char *accept_event /* Reply if we accept event */ char *reject_event /* Reply if we reject event */ char *expire_event /* Reply if we expire event */ time_t timeout /* Expires at this time (or 0) */
Any part of the application can send an event to an event queue using the event_send() function. The queue may be served by a thread, or not (if it is a floating event queue). You should not change any of the fields in the event object: the sender, body, and body_size fields are generally interesting.
SEMAPH /* Semaphore definition */ SEMAPH *next, *prev; /* Doubly-linked list */ char *name; /* Name of semaphore */
Semaphores are for synchronising agents, for instance for exclusive access to some resource. We implement general semaphores, i.e. they can take any value of 0 or greater (the value is defined as an integer).
An SMT application must explicitly initialise and shut-down the SMT kernel. This lets the kernel create global objects (such as the symbol table) and then destroy them when finished.
The smt init() function initialises the SMT kernel. The smt term() function shuts-down the SMT kernel. These are generally the first and last SMT functions that the application calls. Usually, we'll put these calls in the stub (main) program.
Any part of the application can call smt atexit() to register a shut-down function; these shut-down functions are called by smt term(), in the order that you declare them.
Any program can create a thread in any agent in the application using the thread_create() function. To create a thread you must know the name of the agent and the name of the thread you want to create.
A thread's name is local within the agent, and lets an outside program look-up the event queue for that thread. There are various ways to name threads. In single-threaded agents, it is useful to leave the thread name empty (specified as an empty string - ""). In multithreaded agents, the thread name can correspond to some resource name. For instance, in the echo agent, the thread uses the connection socket number as its name. In a router agent, several threads can have the same name; since the threads in such an agent share the same event queue, this allows a program to unambigouously find the event queue from the thread name.
This is how a single-threaded agent (e.g. the operator console) creates a thread:
/* Create initial, unnamed thread */ thread create (AGENT_NAME, "");
This is how the echo agent creates a thread to handle a new connection (tcb-> handle contains the handle of the echo master socket):
SOCKET slave_socket; /* Connected socket */ static char thread_name [12]; /* Socket number as string */ slave_socket = accept_socket (tcb-> handle); if (slave_socket != INVALID_SOCKET) { sprintf (thread_name, "%d", slave_socket); thread create (AGENT_NAME, thread_name); }
These are the API functions that you can use to send an event:
In all cases, the target queue is specified as a QID. The QID is a location-independent queue identifier that the SMT kernel creates for each queue. We use a QID instead of the address of the queue object so that events can be sent between processes running in different address spaces. (Although this is not yet implemented.)
When you send an event, you specify an event name. An agent program must have declared a method for each event it can accept. There is no such restriction for other programs that manage event queues themselves.
An event has a body, which is a block of text or binary data that is copied to the receiving event queue. You should always ensure that event bodies are portable, since the receiving event queue could in principle be on a different system. Furthermore, an event body cannot include the address of an object or variable: events can cross address spaces (i.e. be sent to other processes) so that such addresses are not meaningful.
You can send an event to a thread as soon as it has been created. This can be useful if you need to pass arguments to a child thread. The event will be delivered after the initialise_the_thread module, if you do not specify a value for the_next_event. For an example, see the echo agent SMTECHO.
The event send() call lets a program specify these optional arguments:
When a program wants to inspect the events in a queue, it uses event iterate(), which walks through the queue, event by event. To take an event off the queue, a program calls event accept(). This automatically sends an accept event, if specified. If queue is empty it simply returns with an 'not found' feedback. Some programs implement event priorities by combining event iterate() with event accept(). A program may manipulate several event queues.
To remove an event without using it, a program calls event destroy(). The event destroy() call automatically sends a reject event, if specified.
The SMT kernel handles event expiry automatically for native programs. Programs that handle floating event queues must expire old events explicitly by calling queue flush() before they start to process waiting events.
The SMT kernel provides support for event priorities and for thread priorities. These work as follows:
In practice we use priorities rarely, and for specific cases only. In the current version of the SMT kernel we use a high priority for shutdown events, and a low priority for the socket agent. All other events and threads have normal (equal) priority.
A native program is a agent. To build an agent you:
When you design an agent you must decide whether the it is single-threaded or multithreaded. What's the difference?
By convention, a single-threaded agent creates an unnamed thread when it initialises. A multithreaded agent, by contrast, typically create a new thread for each new connection, and uses a different name for each thread.
An example of a single-threaded agent is the operator console. This is a program that accepts error messages or warnings from other parts of the application, then does something useful with them. (The current implementation writes them to stderr.) The operator console has no need for multiple threads.
An example of a multithreaded agent is the logging agent. This is a program that manages log files on behalf of other application programs. It does this at a low priority, and without blocking, so that log file data is written without disturbing ongoing work. The logging agent can write to several log files in parallel: it does this by having one thread for each log file.
To specify that an agent is single-threaded, define SINGLE_THREADED as TRUE near the start of the program. For instance, this code comes from the SMTOPER agent:
/*- Definitions ------------------------------------------------*/ #define AGENT_NAME SMT_OPERATOR /* Our public name */ #define SINGLE_THREADED TRUE /* Single-threaded agent */
An agent program must be 'initialised' before it can do any useful work. For instance, to initialise the logging agent, the application must call the function smtlog init(). Generally, agents are initialised by the stub program, or by other agents. We generally recommend that an agent always try to initialise every agent it requires. It is safe to call initialisation function for an agent several times; only the first call has any effect.
The initialisation function is the only public function for a agent. Once an agent is initialised, it communicates with other programs only via events.
An agent program is based on a Libero dialog, and is 'driven' by a chunk of code generated by Libero. This code (defined as an #include file) handles the initialisation of the agent. The code looks something like this (we explain each part):
if (agent lookup (AGENT_NAME)) return (0); /* Agent already declared */
The agent lookup() function returns NULL if an agent object with the specified name already exists. Otherwise it returns a pointer to the agent object. Here we check that the agent has not already been declared. The generated code assumes that AGENT_NAME has been defined to hold the name of the agent. AGENT_NAME can be a variable or a pre-processor macro (the generated skeleton program defines it as a macro).
if ((agent = agent declare (AGENT_NAME)) == NULL) return (-1); /* Could not declare agent */
The agent declare() function returns a pointer to the newly-created agent object. If there was an error (e.g. not enough memory), it returns NULL.
#if (defined (SINGLE_THREADED)) agent-> tcb_size = 0; /* No context block */ agent-> max_threads = 1; /* and max. 1 thread */ #else agent-> tcb_size = sizeof (TCB); #endif
Once the agent has been created, the generated code sets the thread context block (TCB) size depending on whether the agent is single threaded or multithreaded. A single-threaded agent does not need TCBs, so the size is zero. The code assumes that SINGLE_THREADED has been defined as a macro if required. The generated code then sets a variety of fields in the agent block. This allows the SMT kernel to 'drive' the agent program correctly.
This is how a typical multithreaded agent program initialises (this code is taken from smtlog.c):
#define AGENT_NAME SMT_LOGGING /* Our public name */
The SMT_LOGGING symbol is defined in the standard SMT header file, smtlib.h, since this agent is part of the standard package. For your own agents you would define AGENT_NAME as a string literal. Note that agent names must be unique within the application.
int smtlog_init (void) { AGENT *agent; /* Handle for our agent */ THREAD *thread; /* Handle to console thread */ # include "smtlog.i" /* Include dialog interpreter */
The initialisation code in smtlog.i assumes that a variable 'agent' is defined. Then, you can refer to the agent in following code using this 'handle'. The first thing we do is to declare each method:
/* Method Event value Priority */ method declare (agent, "SHUTDOWN", shutdown_event, SMT_PRIORITY_MAX); method declare (agent, "OPEN", open_event, 0); method declare (agent, "PUT", put_event, 0); method declare (agent, "CLOSE", close_event, 0);
Methods names are not case-sensitive, but by convention we specify them in uppercase. Every agent must support the SHUTDOWN method; this is sent to each agent when the SMT kernel terminates (for instance when interrupted). SHUTDOWN gets a high priority, so that an agent will handle shutdown events before any other waiting events. The other events get a normal priority (0 means 'default').
You can define several methods for the same event. The SMT kernel uses the set of methods to translate an incoming external event into an internal dialog event.
/* Ensure that operator console is running, else start it */ smtoper init (); if ((thread = thread lookup (SMT_OPERATOR, "")) != NULL) console = thread-> queue-> qid; else return (-1);
This agent sends error messages to the operator console agent, which is generally a good idea. It initialises the agent (with no effect if the agent is already initialised) and then gets the console queue id, so it can send events to the operator console. Note how we do a thread lookup() with an empty thread name. The operator console is single threaded, and that single thread has no name.
/* Signal okay to caller that we initialised okay */ return (0); }
Finally, if everything went as expected, we return 0 to signal that to the calling program. This is a convention, although you can write the initialisation function any way you like, accepting any arguments and returning any value that is appropriate.
Threads can share data: any global data in the agent program is de-facto shared by all threads. Since threads also need 'private' data, each thread owns a block of memory called the Thread Context Block, or TCB. The SMT kernel allocates such a block when a thread is created.
The TCB is a structure that contains arbitrary fields. You define this structure at the start of your program. All code modules in the program receive a pointer to this structure: they use the pointer to reference private data. For instance, this is how the smtlog.c agent declares its TCB:
typedef struct /* Thread context block: */ { int handle; /* Handle for i/o */ } TCB;This is how the smtlog.c agent opens a file:
/******************* OPEN THREAD LOGFILE ********************/ MODULE open_thread_logfile (THREAD *thread) { char *logfile_name; tcb = thread-> tcb; /* Point to thread's context */ /* Event body or thread name supplies name for log file */ logfile_name = (strused (thread-> event-> body)? thread-> event-> body: thread-> name); tcb-> handle = lazy creat (logfile_name, S_IREAD | S_IWRITE); if (io_completed) { if (tcb-> handle < 0) /* If open failed, send error */ { /* to console, and terminate */ sendfmt (&console, "ERROR", "Could not open %s for output", logfile_name); senderr (&console); raise exception (exception_event); } } }
We generally use the same name for the method as for the event. E.g. CLOSE and close_event. This is not obligatory, and in some cases not appropriate, but it does make the program easier to understand when dialog event names correspond to methods.
The agent kernel delivers events to threads when required. This happens at a precise moment: when the thread moves to a new dialog state -- after executing the action module list -- and no internal event was provided.
When a thread moves into a state, the set of possible events is those events defined in that state, plus the events defined in the Defaults state, if any.
The SMT kernel takes the following event (or the event with the highest priority) and tries to match it to a method name. If the event does not match a method, the event is rejected. Otherwise it is accepted and translated into an internal event number. If the internal event is illegal at that moment in the dialog, this causes a fatal dialog error (the thread rejects the event).
Normally the SMT kernel delivers an external event when the dialog moves to a new state, and no event was specified. In some cases this can make a dialog rather large, since you need to break each step up into states. The event wait() function causes the dialog to halt until an event can be taken from the queue. When several threads are executable, this function also switches execution to the next thread.
The event wait() call sets the variable "the_external_event". This should be the last statement in a dialog module. When used in the last module in a list, it has strictly no effect.
Events can optionally have a body to carry additional information. When you send textual data - for instance a string - the event body can be transferred between programs without any type of conversion. (We ignore problems of character-set conversion at present.)
When you need to send several items in one event body, we speak of sending structured data. Structured data consists of a mixture of data items of these types:
An example of events with structured data are those accepted by the socket i/o agent SMTSOCK.
In C, we can group the data items in a structure, hence the term. We cannot, however, simply copy the structure into the message and send that. We cannot copy the address of the structure. Both these methods will work today, but an event may (in the future) be sent to an agent in a different process, perhaps running on a different machine.
Our solution is to take each data field in turn, and pack the structure into a machine-independent stream. We transmit this stream, then do the reverse unpacking in the target program.
To do the conversion we use the SFL functions exdr_read() and exdr_write().
This is more work than just sending the complete structure, but is the only way to ensure that data can safely be sent between two programs that may be running on separate systems.
Within one agent, you do not need to use the EXDR functions. It is quite acceptable to pass data in a structure. To do this,
(SOMESTRUCT *) thread-> event-> body-> some_field
In some cases, you may want to ignore reply events sent by an agent. This can be useful to simplify a dialog. This is how we declare a method to ignore some specific event:
method declare (agent, "SOME_EVENT", SMT_NULL_EVENT, 0);
The SMT kernel provides a minimum file access layer that is safe to use in multithreaded programs. To understand what this means, first understand what is 'unsafe'.
On some systems, like UNIX and Digital VMS, file access may need resources that are not always available - like memory for buffers. If you ask to read some data from a file, and there is a problem, the operating system may loop a few times - waiting and then trying again - before finally returning to the calling program. In the meantime your program and all threads are blocked.
The general solution is to request non-blocking file access. Then, in the case of a resource problem, the operating will not loop, but will return at once with an error code that means 'try again'. The SMT kernel integrates this solution with its thread management, so that a thread waiting for file access will loop slowly, allowing other threads to continue to run.
To make this work, you should not call the open(), creat(), read(), or write() functions directly in your program. Instead, call the SMT kernel functions lazy open(), lazy creat(), lazy read(), lazy write(), and lazy close(). Furthermore, construct your code like this:
rc = lazy write (tcb-> handle, formatted, fmtsize); if (io_completed && rc < 0) /* If write failed send error */ { /* to console and terminate */ sendfmt (&console, "ERROR", "Could not write to %s", thread-> name); senderr (&console); raise exception (exception_event); }
If io_completed is FALSE, then the code module should do no further work. In this case the SMT kernel automatically re- executes the entire code module.
This is simple and sufficient for sequential file access. If you need heavy database access, where one SELECT statement may take a long time to complete, you'll find that your program responds slowly. A better architecture in such cases is to handle database requests in a separate process, which talks to your application program using sockets. The requests will take the same time to complete, but in the meantime other threads - e.g. handling new connections - can run normally.
While we generally recommend you use the non-blocking i/o functions, there are instances where this is not really necessary. Most obviously, when an application is initialising (e.g. reading configuration files) or terminating (dumping data to a log file), there is no need to avoid blocking i/o. In such cases, you can access sequential files directly.
When you call a function like lazy read() and it detects a 'busy' condition, it sets io_completed to FALSE, and automatically re-executes the current code module. You may want to manage this yourself, however. It can also be useful to have a similar looping when you access a socket and receive the EAGAIN error code.
The recycle module() function lets you control this looping explicitly. For example, we can decide to abort a file access after more than RETRY_MAX retries:
typedef struct /* Thread context block: */ { int handle; /* Handle for i/o */ int retries; /* Number of retries so far */ } TCB;Let's assume that 'retries' has been set to zero, either during thread initialisation (okay), or by a specific dialog module (better). We can then try to open a file like this, calling recycle module() with a FALSE argument to prevent looping when we have retried too often:
/******************** OPEN THREAD LOGFILE *******************/ MODULE open_thread_logfile (THREAD *thread) { tcb = thread-> tcb; /* Point to thread's context */ /* Our thread's name is the name for the log file */ tcb-> handle = lazy creat (thread-> name, S_IREAD | S_IWRITE); if (io_completed) { /* If open failed, send error to console, and end */ if (tcb-> handle < 0) { sendfmt (&console, "ERROR", "Could not open %s for output", thread-> name); senderr (&console); raise exception (exception_event); } } else { if (++tcb-> retries == RETRY_MAX) { sendfmt (&console, "ERROR", "Could not open %s for output", thread-> name); sendfmt (&console, "ERROR", "Too many retries.") raise exception (exception_event); recycle module (FALSE); } } }
A semaphore is an object that you can use to synchronise threads, lock resources, etc. Semaphores are widely- used in multithreaded and parallel computing, and the SMT kernel implements semaphores in a conventional manner. Semaphores have these characteristics:
In realistic projects you will want to replace the operator console agent and perhaps the logging agent with your own, customised versions. One way to do this is to modify the code of the programs we supply. However, this just causes maintenance problems. A better way is to use the existing code as a basis for new agents that use the same agent name (but written as a different source file). Initialise the replacement agent from your main() function. Then, the standard agent will never be initialised. All events normally sent to the standard agent will be sent to your agent instead. Do not try to change the form or meaning of events sent to the standard agents.
In an SMT application there will always be some foreign programs. Some examples are:
It may make sense in some applications to write all programs as foreign. For instance, you could use the SMT kernel simply to send events between programs without using the multithreading facilities.
When you build a new SMT application you must also write a STUB program (the main() function). The stub program usually does this:
When you compile and link the stub, you create the executable program for the application.
An interface between a native program (or a thread) and a foreign program consists of at least an event queue in either direction. The SMT kernel automatically handles the event queue for the native program. You must handle the other event queue yourself:
Foreign Native .--------------. .--------------. : event_send() :-----------> ()()()(): | : |()()()() <-----:-----: event_send() | `==============" : : `==============" : normal event queue `----- floating event queue Figure: Managing a floating event queue
The program that sends an event does not know whether the event queue is floating or not, although it can find this out if it has to, by examining the queue object. The difference between a floating event queue and a normal event queue is that you must get events off a floating event queue yourself. You can use the API function calls:
You can use methods if you need to. This can be a convenient way of translating event names into internal numbers of some sort. For instance, if you design the foreign program using the standard Libero C schema, you can use methods to translate event names into internal numbers, much as the SMT kernel does for a native program. This is (more or less) the SMT kernel code that does this translation:
EVENT *event; /* Event information block */ EVENT *deliver_event; /* Event to deliver */ METHOD *method; /* Method information block */ THREAD *active; /* Thread in active list */ int top_priority; /* Highest event priority */ int event_number; /* Number for event to deliver */ /* Get event to deliver - find event with highest priority */ top_priority = -1; deliver_event = NULL; event = queue-> events.next; while ((NODE *) event != &queue-> events) { /* Lookup method; if method not declared, reject event */ method = method lookup (agent, event-> name); if (method == NULL) { /* Reject this event, but keep place in queue... */ sendfmt (&console, "ERROR", "Event %s not declared by %s", event-> name, agent-> name); event = event-> next; event reject (queue, event-> prev); continue; /* Process this new event */ } if (method-> priority > top_priority) { deliver_event = event; event_number = method-> event_number; top_priority = method-> priority; } event = event-> next; } if (deliver_event) { /* Deliver event, move thread to active queue */ thread-> event = event accept (queue, deliver_event); thread-> the_next_event = event_number; thread-> active = TRUE; }
The main() function in an executable is a foreign program. Since a foreign program is external to the multithreading process of the SMT kernel, control must pass back and forwards between the SMT kernel and the foreign program. Typically this is a loop like this:
To do the second step, the foreign program calls smt exec full(). This function runs all the agent threads until there are no more events left to deliver, or active threads.
Sometimes it is better to call smt exec step() repeatedly. This function delivers events, but only executes the first active thread. This function provides a finer-grain control. For instance, if the application loops because two threads send each other events in a never-ending loop (usually a programming error), the smt exec full() call will never return, while the smt exec step() call will return each time that the kernel switches between the threads.
The SMT architecture lets one build an application out of existing and new agents. We provide a number of standard agents. All these agents correctly support the shutdown method, which the SMT kernel broadcasts when it needs to end the application.
These are the standard SMT agents:
These agents are in development, or planned:
Creates log files, and writes data to the log files. Can handle multiple log files in parallel; each open log file is managed by one thread. Sends errors to the operator console agent.
To use, call smtlog init(). This does not create any threads. Create an unnamed thread using the thread_create() function. Send an OPEN or APPEND event to create the log file and PUT events to write to the log file. Finally, send a CLOSE event to close the log file and terminate the log file thread. SMTLOG does not reply - errors are sent to the console, not the requesting program. It supports these methods:
Example of initialising SMTLOG:
/* Static data */ static QID logq; /* Logging agent event queue */ /* In agent initialisation code */ THREAD *thread; /* Handle to various threads */ /* Ensure that logging agent is running, else start it */ smtlog init (); if ((thread = thread create (SMT_LOGGING, "")) != NULL) logq = thread-> queue-> qid; /* Get logging queue id */ else return (-1);
If the specified log file already exists, renames it to a file whose name consists of the first letter of the log file name, followed by the date in 7 positions (YYYYDDD). If that file name already exists, generates a unique filename. Takes the event body to supply the log file name; if the event body is empty, uses the thread name instead. If the log file name is empty ("") or the 4-letter string "NULL", nothing is done.
Creates a new, empty log file. Takes the event body to supply the log file name; if the event body is empty, uses the thread name instead. If the log file name is "" or "NULL", no file is created, and SMTLOG discards all output.
Opens an existing log file for additional output. If the log file does not already exist, acts like OPEN. Takes the event body to supply the log file name; if the event body is empty, uses the thread name instead.
Takes the event body as a string, prefixes the date and time, and writes it to the log file.
Disables time-stamping of the logged data. Use this when you do not want the default SMTLOG time-stamping.
Enables time-stamping of the logged data.
Closes the log file and destroys the thread used to manage it. You normally send this event when you end your program.
The SMTECHO agent shows an example of using the Logging agent.
The hypertext view of SMTLOG's dialog and source code may help to understand how SMTLOG works.
Accepts error messages, warnings, and information messages, and does something useful (but unspecified) with them. The current implementation writes all received messages to the standard error device.
To use SMTOPER, call smtoper init(). This creates a single unnamed thread automatically the first time it is called, and has no effect thereafter. You can then send messages to this thread. SMTOPER does not reply. It supports these methods:
Example of initialising SMTOPER:
/* Static data */ static QID console; /* Operator console queue */ /* In agent initialisation code */ THREAD *thread; /* Handle to console thread */ /* Ensure that operator console is running, else start it */ smtoper_init (); if ((thread = thread lookup (SMT_OPERATOR, "")) != NULL) console = thread-> queue-> qid; else return (-1);
The event body contains a string. This string is handled as a serious error message. For example:
sendfmt (&console, "ERROR", "Could not open %s", filename); senderr (&console); raise exception (exception_event);
The event body contains a string. This string is handled as a non-fatal warning message. For example:
sendfmt (&console, "WARNING", "Exceeded connection quota");
The event body contains a string. This string is handled as a information message. For example:
sendfmt (&console, "INFO", "Connection from %s", system);
The event body contains a string that specifies the name of a SMTLOG thread. All operator output is sent to this thread.
The hypertext view of SMTOPER's dialog and source code may help to understand how SMTOPER works.
Generates timing events. You can request one timing event after a specific delay, or repeated timing events at regular intervals. The timer is accurate to 1/100th of a second. You specify delays as a number of days and a number of centiseconds.
To use SMTTIME, call smttime init(). This creates a single unnamed thread automatically the first time it is called, and has no effect thereafter. You can then send messages to this thread. It supports these methods:
Example of initialising SMTTIME:
/* Static data */ static QID timeq; /* Timer agent event queue */ /* In agent initialisation code */ THREAD *thread; /* Handle to various threads */ method declare (agent, "TIME_ALARM", alarm_event, 0); method declare (agent, "TIME_ERROR", error_event, 0); /* Ensure that timer agent is running, else start it */ smttime init (); if ((thread = thread lookup (SMT_TIMER, "")) != NULL) timeq = thread-> queue-> qid; else return (-1);
Sends a single alarm event after some specified delay. Build the event body using exdr_write() and the message definition SMT_TIME_ALARM. The event body consists of these fields (see exdr_write() for the field types):
The ALARM method implicitly does a FLUSH before proceeding.
Sends a single alarm event at some specified day and time. Build the event body using exdr_write() and the message definition SMT_TIME_ALARM. The event body consists of these fields (see exdr_write() for the field types):
The WAKEUP method implicitly does a FLUSH before proceeding.
Sends a repeated alarm event after some specified delay, for ever, or a specific number of times. Build the event body using exdr_write() and the message definition SMT_TIME_CLOCK. The event body consists of these fields (see exdr_write() for the field types):
The CLOCK method implicitly does a FLUSH before proceeding.
Removes any requests sent by a particular client thread. Use this to cancel a CLOCK method, or an unexpired ALARM or WAKEUP request. You do not need to provide an event body, but you must specify your thread's QID correctly when you use event_send(). SMTTIME does not reply to a FLUSH method.
The SMTTST1 test agent shows an example of using the timer agent.
The hypertext view of SMTTIME's dialog and source code may help to understand how SMTTIME works.
Manages 'time slots', a mechanism to allow long-running programs to 'switch on' and 'switch off' at specific times during the day. The time slot agent simulates a wall timer, i.e. a timer that switches a device like a lamp or electric heater on and off during the day.
To use SMTSLOT, call smtslot init(). This does not create any threads. Create a named thread, then send SPECIFY events to define the various time slots for your application. Then send an ON or OFF event to initialise the timer. The time slot agent then sends a SWITCH_ON event when the timer move to an 'ON' state, and a SWITCH_OFF event when the timer moves to an 'OFF' state. Errors are sent to the console, not the requesting program. SMTSLOT supports these methods:
Example of initialising SMTSLOT:
/* Static data */ static QID slotq; /* Time slot event queue */ /* In agent initialisation code */ THREAD *thread; /* Handle to various threads */ /* Ensure that time slot agent is running, else start it */ smtslot init (); if ((thread = thread create (SMT_SLOT, "myprog")) != NULL) slotq = thread-> queue-> qid; else return (-1);
Defines one or more time slots. A slot specification is a string, in the format: "name value ...". The name field is a day name ("mon"-"sun"), a date in MD order ("12/31") or a date in YMD order ("95/12/31"). The value is a list of times in 24 hour HH:MM[-HH:MM] format ("7:30-12:30 13:30-17:30 17:35"). A value "off" clears all time slots for that day. The time slot accuracy is SLOT_TICK csecs. Any day that does not have specified values is switched 'off'. Build the event body using exdr_write() and the message definition SMT_SLOT_SPEC. The event body consists of these fields (see exdr_write() for the field types):
Sets the timer 'ON'. The event does not have a body. The timer will respond with a SWITCH_OFF event when it moves into an 'OFF' state.
Sets the timer 'OFF'. The event does not have a body. The timer will respond with a SWITCH_ON event when it moves into an 'ON' state.
The SMTTST2 agent shows an example of using the time slot agent.
The hypertext view of SMTSLOT's dialog and source code may help to understand how SMTSLOT works.
Handles input and output to TCP and UDP sockets. You should use this agent for all access to TCP or UDP sockets, although you can also access sockets directly using the SFL socket access functions. Socket i/o is both central to most Internet servers, and reasonably delicate, making it a task that is well done by a specific agent.
SMTSOCK has two main functions: it acts as the central 'heartbeat' for an Internet server, and it perform input and output on sockets. The heartbeat function works as follows: SMTSOCK uses the select() function to monitor all open sockets. Each socket is owned by a thread, somewhere. When a socket shows signs of life, SMTSOCK sends an event to the appropriate thread. The thread can then decide to read or write data as required. In a typical Internet application -- such as the XITAMI web server -- the socket agent is the main source of the events that drive the application. By contrast, in non-Internet applications the 'heartbeat' role could be played by the timer agent SMTTIME.
The second task for SMTSOCK is input and output on sockets. For instance, you can ask SMTSOCK to read data from a socket, or to write a block of data to a socket. Both these tasks can require multiple cycles, waiting until the socket is ready, then reading/writing as much data as possible, until all the data has been read/written. SMTSOCK handles this automatically.
To use SMTSOCK, call smtsock init(). This creates a single unnamed thread automatically the first time it is called, and has no effect thereafter. You can then send messages to this thread. SMTSOCK replies to most events. It supports these methods:
Example of initialising SMTSOCK:
/* Static data */ static QID sockq; /* Socket agent event queue */ /* In agent initialisation code */ THREAD *thread; /* Handle to various threads */ /* Reply events from socket agent */ method declare (agent, "SOCK_INPUT_OK", ok_event, 0); method declare (agent, "SOCK_OUTPUT_OK", ok_event, 0); method declare (agent, "SOCK_READ_OK", read_ok_event, 0); method declare (agent, "SOCK_WRITE_OK", write_ok_event, 0); method declare (agent, "SOCK_CLOSED", closed_event, 0); method declare (agent, "SOCK_ERROR", error_event, 0); method declare (agent, "SOCK_TIMEOUT", error_event, 0); /* Ensure that socket agent is running, else start it */ smtsock init (); if ((thread = thread lookup (SMT_SOCKET, "")) != NULL) sockq = thread-> queue-> qid; else return (-1);
Waits for, and reads data from a socket. TCP/IP breaks a stream of data into chunks of arbitrary size, and each low-level read operation will read one such chunk. Thus, to read a specific amount of data, you may need to make several low-level read calls. SMTSOCK packages this so that one READ event can read as much data as required. You can alternatively ask SMTSOCK to read just the next chunk sent by TCP/IP. Build the event body using exdr_write() and the message definition SMT_SOCK_READ. The event body consists of these fields (see exdr_write() for the field types):
Works in the same way as the READ method, but works repeatedly until a FLUSH is sent for the socket. The READR method is useful for servers that have to loop on reading a socket; it saves the need for sending fresh READ events.
Writes a block of data to a socket. If you call the low-level TCP/IP write function directly, you must handle various error and retry conditions. It is easier and safer to use SMTSOCK to do this. Build the event body using exdr_write() and the message definition SMT_SOCK_WRITE. The event body consists of these fields:
Waits for input to arrive on a socket. This can be data, or a connection request. Build the event body using exdr_write() and the message definition SMT_SOCK_INPUT. The event body consists of these fields:
Works in the same way as the INPUT method, but works repeatedly until a FLUSH is sent for the socket. The INPUTR method is useful for servers that have to loop on waiting for a socket; it saves the need for sending fresh INPUT events.
Waits for a socket to be ready for output. If you use the low- level TCP/IP write functions, you must be sure that the socket is ready for output, or your thread will block the entire application if it has to wait. Build the event body using exdr_write() and the message definition SMT_SOCK_OUTPUT. The event body consists of these fields:
Establishes a TCP or UDP connection to some specified host and service (or port). Build the event body using exdr_write() and the message definition SMT_SOCK_CONNECT. The event body consists of these fields:
Removes any requests for a socket. Since events are delivered in a straight first-in first-out basis (ignoring the high priority SHUTDOWN event) it is safe to send first a FLUSH event, followed by another event, with no intervening wait. Build the event body using exdr_write() and the message definition SMT_SOCK_FLUSH. The event body consists of these fields:
The SMTECHO agent provides a good basic example of using SMTSOCK. Study this program, and use it as a basis for your own socket-based agents.
The SOCK_CLOSED and SOCK_TIMEOUT return events can come from various requests; to make processing of this possible, they are always formatted as SOCK_READ_OK events.
The hypertext view of SMTSOCK's dialog and source code may help to understand how SMTSOCK works.
Transfers blocks of data or files to connected sockets. You can use this agent to simplify certain types of communication. The transfer agent uses the socket agent for actual reading and writing to sockets.
To use SMTTRAN, call smttran init(). This creates a single unnamed thread automatically the first time it is called, and has no effect thereafter. You can then send messages to this thread. It supports these methods:
Example of initialising SMTTRAN:
/* Static data */ static QID tranq; /* Transfer agent queue */ /* In agent initialisation code */ THREAD *thread; /* Handle to console thread */ /* Ensure that transfer agent is running, else start it */ smttran init (); if ((thread = thread lookup (SMT_TRANSFER, "")) != NULL) tranq = thread-> queue-> qid; else return (-1);
Writes a length-specified block to a socket: first writes a two-byte length specifier in network format, then writes the block data. Build the event body using exdr_write() and the message definition SMT_TRAN_PUTB. The event body consists of these fields (see exdr_write() for the field types):
Reads a length-specified block from a socket: first reads a two-byte length specifier in network format, then reads that many bytes of block data. Build the event body using exdr_write() and the message definition SMT_TRAN_GETB. The event body consists of these fields (see exdr_write() for the field types):
Writes a file to a socket: reads the file in pieces of unspecified size, and writes these to the output socket. Build the event body using exdr_write() and the message definition SMT_TRAN_PUTF. The event body consists of these fields (see exdr_write() for the field types):
Reads a file from a socket and saves it with the specified name. Build the event body using exdr_write() and the message definition SMT_TRAN_GETF. The event body consists of these fields (see exdr_write() for the field types):
Waits until all put and get requests are finished, then replies with a TRAN_CLOSED event. This event does not take any arguments. SMTTRAN replies to a GET_FILE event with one of these events:
The SMTHTTP agent uses the transfer agent to send files.
The hypertext view of SMTTRAN's dialog and source code may help to understand how SMTTRAN works.
Provides the TCP echo service on port 7.
To use SMTECHO, call smtecho init(). This initialises the echo service on TCP port 7. If you set the global variable ip_portbase to some value before initialising SMTECHO, the echo port is moved by that value. For instance, if you set ip_portbase to 8000, the echo port is 8007. This lets you run the service in 'user space': on most systems you need root access to use ports below 1000.
The hypertext view of SMTECHO's dialog and source code may help to understand how SMTECHO works.
Provides the HTTP service on port 80.
To use SMTHTTP, call smthttp init(). This initialises the HTTP service on TCP port 80. If you set the global variable ip_portbase to some value before initialising SMTHTTP, the HTTP port is moved by that value. For instance, if you set ip_portbase to 8000, the HTTP port is 8080. This lets you run the service in 'user space': on most systems you need root access to use ports below 1000.
To connect to a webserver running on some system at port 8080, use this URL: http://hostname:8080/.
The current implementation of SMTHTTP is quite sophisticated. It will look for default.htm, default.html, index.htm, index.html in that order if no filename is specified. The file smthttp.aut defines directories that are protected by username/password definitions. You can find the full documentation for the web server in the Xitami package.
The program xitami.c is a fairly complete web server based on the SMTHTTP agent. You can use this program as-is, or as a basis for your own work.
The hypertext view of SMTHTTP's dialog and source code may help to understand how SMTHTTP works.
Simulates network delays. You can use SMTSIMU when you want to test that an application will handle network delays in a robust manner. This can be important for applications that use the UDP protocol, which does not provide the same traffic-control features as TCP.
SMTSIMU will insert itself invisibly between your application agents and the socket agent when you include the file smtsimu.h at the start of your program, after the other include files (specifically, after smtlib.h).
The hypertext view of SMTSIMU's dialog and source code may help to understand how SMTSIMU works.
SMT Quick Reference Revised: 1996/12/14 PH ------------------------------------------------------------------------------ Global variables int smt_errno Set when API detects an error char *smt_errlist [] Corresponding error messages event_t _the_next_event May be set by thread code event_t _the_external_event Set by event_wait() event_t _the_exception_event May be set by thread code Bool _exception_raised May be set by thread code Bool _io_completed Last lazy I/O completed Bool _repeat_module Repeat current action module Bool signal_raised True after interrupt Bool shutdown_pending When kill signal in progress int signal_value Value of signal ------------------------------------------------------------------------------ AGENT AGENT *next, *prev; Doubly-linked list NODE methods; Methods accepted by agent NODE queues; Queues defined for agent char *name; Agent's name Bool router; True if multi-thread/queue int priority; 50=Low, 100=Normal, 200=High long max_threads; Max. permitted threads, or 0 long cur_threads; Current number of threads long top_threads; Max. number threads we had long thread_tally; How many threads created long switch_tally; How many context switches ------------------------------------------------------------------------------ METHOD METHOD *next, *prev; Doubly-linked list AGENT *agent; Parent agent descriptor char *name; Name of method int priority; 50=Low, 100=Normal, 200=High int event_number; Internal event number ------------------------------------------------------------------------------ QID long node; Location of queue (zero) long ident; Queue ID number (1..n) ------------------------------------------------------------------------------ QUEUE QUEUE *next, *prev; Doubly-linked list AGENT *agent; Parent agent descriptor NODE events; Events in queue NODE threads; Threads for queue QID qid; Queue ID descriptor int max_events; Maximum allowed events int cur_events; Current number of events ------------------------------------------------------------------------------ EVENT EVENT *next, *prev; Doubly-linked list QUEUE *queue; Parent queue descriptor QID sender; Replies come back here char *name; Name of event size_t body_size; Size of event body in bytes byte *body; Event body char *accept_event; Reply if we accept event char *reject_event; Reply if we reject event char *expire_event; Reply if we expire event time_t timeout; Expires at this time (or 0) ------------------------------------------------------------------------------ THREAD Properties THREAD *next, *prev; Doubly-linked list QUEUE *queue; Parent queue descriptor long thread_id; Thread identifier number char *name; Name of thread Bool animate; Animate this thread void *tcb; Thread context block (TCB) EVENT *event; Last-received event ------------------------------------------------------------------------------ SEMAPH Properties SEMAPH *next, *prev; Doubly-linked list char *name; Name of semaphore ------------------------------------------------------------------------------ Function Prototypes int smt_init (void); int smt_term (void); int smt_exec_full (void); Bool smt_exec_step (void); Bool smt_active (void); void smt_set_console (QID *qid); void smt_set_timer (QID *qid); int smt_atexit (function exitfct); void smt_shutdown (void); AGENT *agent_declare (char *agent); AGENT *agent_lookup (char *agent); int agent_destroy (AGENT *agent); METHOD *method_declare (AGENT *agent, char *method, int nbr, int priority); METHOD *method_lookup (AGENT *agent, char *method); int method_destroy (METHOD *method); QUEUE *queue_create (AGENT *agent, int max_events); QUEUE *queue_lookup (QID *qid); int queue_destroy (QUEUE *queue); int queue_flush (QUEUE *queue); int event_send (QID *to, QID *from, char *name, byte *body, size_t body_size, char *accept, char *reject, char *expire, word timeout); EVENT *event_accept (QUEUE *queue, EVENT *event); int event_reject (QUEUE *queue, EVENT *event); int event_expire (QUEUE *queue, EVENT *event); int event_discard (QUEUE *queue, EVENT *event); EVENT *event_iterate (QUEUE *queue, EVENT *event); int event_destroy (EVENT *event); void event_wait (void); THREAD *thread_create (char *agent, char *thread); THREAD *thread_lookup (char *agent, char *thread); int thread_destroy (THREAD *thread, Bool cleanup); SEMAPH *semaph_create (char *name, int value); SEMAPH *semaph_lookup (char *name); int semaph_destroy (SEMAPH *semaph); int semaph_wait (SEMAPH *semaph); int semaph_signal (SEMAPH *semaph); int lazy_creat (char *path, int mode); int lazy_open (char *path, int flags); int lazy_read (int handle, char *buffer, size_t count); int lazy_write (int handle, char *buffer, size_t count); int lazy_close (int handle); int senderr (QID *to_queue); int sendfmt (QID *to_queue, char *name, char *format,...); void raise_exception (event_t event); void recycle_module (Bool wanted); #define SEND(q,name,s) event_send ((q), NULL, (name), (byte *) (s), strlen(s) + 1, NULL, NULL, NULL, 0) ------------------------------------------------------------------------------ Constants Possible values for smt_errno SMT_NOERRORS 0 No errors SMT_EVENTEXISTS 1 Event already declared SMT_INTERNALERROR 2 Agent had internal error SMT_METHODEXISTS 3 Method already declared SMT_NOSUCHEVENT 4 Unknown event name SMT_NOSUCHMETHOD 5 Unknown method name SMT_NOSUCHQUEUE 6 Event queue not defined SMT_NOSUCHSEMAPH 7 Unknown semaphore name SMT_NOSUCHAGENT 8 Agent not defined SMT_NOSUCHTHREAD 9 Thread not declared SMT_NOTREADY 10 SMT API not initialised SMT_OUTOFMEMORY 11 Not enough heap memory left SMT_QUEUEISEMPTY 12 Event queue is empty SMT_QUEUEISFULL 13 Event queue is full SMT_SEMAPHEXISTS 14 Semaphore already exists SMT_AGENTEXISTS 15 Agent already declared SMT_AGENTNOTREADY 16 Agent not initialised SMT_THREADEXISTS 17 Thread already declared SMT_TOOMANYTHREADS 18 Too many threads Values for priority arguments SMT_PRIORITY_NULL 0 Illegal (null) priority SMT_PRIORITY_MIN 1 Lowest priority SMT_PRIORITY_LOW 50 Low priority SMT_PRIORITY_NORMAL 100 Default priority SMT_PRIORITY_HIGH 200 High priority SMT_PRIORITY_MAX 255 Highest priority Values for agent_declare () type argument SMT_AGENT_NORMAL 0 1 queue for 1 thread SMT_AGENT_ROUTER 1 1 queue for n threads Event constants SMT_NULL_EVENT -2 No event for method ------------------------------------------------------------------------------ SMTLOG - Logging Agent One thread per log file; create thread with log file name, or supply log file name in CYCLE, OPEN, APPEND events. Filename "" or "NULL" means discard all log file output. Errors are sent to SMTOPER. Method: Body: Replies: CYCLE Cycle log file [file name] - OPEN Open new log file [file name] - APPEND Append to existing [file name] - PUT Write line to log Line of text - PLAIN Do not timestamp log - - STAMP Timestamp each line - - CLOSE Close log file - - SMT_LOGGING "smtlog" Name of logging agent int smtlog_init (void); Logging agent ------------------------------------------------------------------------------ SMTOPER - Operator Console Agent Single unnamed thread created automatically when agent initialises. Send messages to console device or log file if specified. Method: Body: Replies: ERROR Error message Message - WARNING Warning message Message - INFO Information message Message - LOG Redirect to log file SMTLOG thread name - SMT_OPERATOR "smtoper" Name of operator agent int smtoper_init (void); Operator agent ------------------------------------------------------------------------------ SMTTIME - Timer Agent Single unnamed thread created automatically when agent initialises. Accurate to 1/100th second. Method: Body: Replies: ALARM Alarm after delay SMT_TIME_ALARM TIME_ALARM, TIME_ERROR WAKEUP Alarm at some time SMT_TIME_ALARM TIME_ALARM, TIME_ERROR CLOCK Alarm at intervals SMT_TIME_CLOCK TIME_ALARM, TIME_ERROR Replies: Body: TIME_ALARM Alarm went off SMT_TIME_REPLY TIME_ERROR Insufficient memory Error message SMT_TIMER "smttime" Name of timer agent int smttime_init (void); Timer agent SMT_TIME_ALARM "qqmM" q=time/delay in days q=time/delay in 1/100th seconds mM=user-defined data block, returned with reply event SMT_TIME_CLOCK "qqwmM" q=delay in days q=delay in 1/100th seconds w=number of times to cycle - zero means forever mM=user-defined data block, returned with reply event SMT_TIME_REPLY "mM" mM=user-defined data block, returned with reply event ------------------------------------------------------------------------------ SMTSLOT - Time Slot Agent One thread per application; create thread with application name. Send SPECIFY events, then ON or OFF to set timer initial state. Specification is "name values..."; name is "mon" to "sun", or date in MM/DD, YY/MM/DD, or YYYY/MM/DD format. Values are hh:mm[-hh:mm] in 24-hour clock format, delimited by spaces. Method: Body: Replies: SPECIFY Specify time slot Specification SLOT_ERROR, if error ON Error message - SWITCH_ON, SWITCH_OFF OFF Error message - SWITCH_ON, SWITCH_OFF SMT_SLOT "smtslot" Name of time slot agent int smtslot_init (void); Time slot agent SMT_SLOT_SPEC "s" s=day of week, mm/dd, or yy/mm/dd followed by 1 or more time specs ------------------------------------------------------------------------------ SMTSOCK - Socket I/O Agent Single unnamed thread created automatically when agent initialises. Multiple writes are processed in order; multiple reads are collapsed. Method: Body: Replies: READ Read socket data SMT_SOCK_READ SOCK_READ_OK,... READR Read, repeat for ever SMT_SOCK_READ SOCK_READ_OK,... WRITE Write socket data SMT_SOCK_WRITE SOCK_WRITE_OK,... INPUT Wait for input SMT_SOCK_INPUT SOCK_INPUT_OK,... INPUTR Wait input, repeat SMT_SOCK_INPUT SOCK_INPUT_OK,... OUTPUT Wait for output SMT_SOCK_OUTPUT SOCK_OUTPUT_OK,... CONNECT Connect to host SMT_SOCK_CONNECT SOCK_WRITE_OK,... FLUSH Flush requests SMT_SOCK_FLUSH - Replies: Body: SOCK_READ_OK Data read okay SMT_SOCK_READ_OK SOCK_WRITE_OK Data written okay SMT_SOCK_OK SOCK_INPUT_OK Socket has input ready SMT_SOCK_OK SOCK_OUTPUT_OK Socket ready for output SMT_SOCK_OK SOCK_CLOSED Socket was closed SMT_SOCK_READ_OK SOCK_TIMEOUT Request timed-out SMT_SOCK_READ_OK SOCK_ERROR Socket error during read SMT_SOCK_ERROR SMT_SOCKET "smtsock" Name of socket i/o agent int smtsock_init (void); Socket i/o agent void smtsock_trace (Bool trace); Socket i/o agent trace on/off SMT_SOCK_READ "dqddq" d=timeout, in seconds - zero means no timeout q=socket to read from d=size of receiving buffer d=minimum amount of data to read, 1 - buffer size; zero = all q=user-defined request tag SMT_SOCK_WRITE "dqmMq" d=timeout, in seconds - zero means no timeout q=socket to write to mM=block of memory to write to the socket q=user-defined request tag SMT_SOCK_INPUT "dqq" d=timeout, in seconds - zero means no timeout q=socket to wait on q=user-defined request tag SMT_SOCK_OUTPUT "dqq" d=timeout, in seconds - zero means no timeout q=socket to wait on q=user-defined request tag SMT_SOCK_CONNECT "dsssdqq" d=timeout, in seconds - zero means no timeout s=type, UDP or TCP s=host, specified as name or dotted address, or NULL s=service, as name or port number in ASCII, or NULL d=16-bit host port, or 0 q=32-bit host address, or 0 -- one of these pairs is required q=user-defined request tag SMT_SOCK_FLUSH "q" q=socket to flush requests for SMT_SOCK_READ_OK "dqmMq" d=timeout, in seconds, from read request q=socket specified for i/o mM=block of memory q=user-defined request tag SMT_SOCK_OK "qq" q=socket specified for i/o, or new connected socket q=user-defined request tag SMT_SOCK_ERROR "sqq" s=error message q=socket specified for i/o, or new connected socket q=user-defined request tag ------------------------------------------------------------------------------ SMTTRAN - Transfer Agent Single unnamed thread created automatically when agent initialises. Transfers fixed-size blocks or complete files. Method: Body: Replies: PUT_BLOCK Write block SMT_TRAN_PUTB SMT_TRAN_PUTB_OK,... GET_BLOCK Read block SMT_TRAN_GETB SMT_TRAN_GETB_OK,... PUT_FILE Write file SMT_TRAN_PUTF SMT_TRAN_PUTF_OK,... GET_FILE Read file SMT_TRAN_GETF SMT_TRAN_GETF_OK,... COMMIT Finish transfers - - SMT_TRANSFER "smttran" Name of transfer agent int smttran_init (void); TCP/IP transfer agent SMT_TRAN_PUTB "qmM" q=socket for output mM=block of data to transmit SMT_TRAN_GETB "q" q=socket for input SMT_TRAN_PUTF "qs" q=socket for output s=name of file to send SMT_TRAN_GETF "qs" q=socket for input s=name of file to receive SMT_TRAN_PUTB_OK "d" d=size of transmitted data SMT_TRAN_GETB_OK "mM" mM=received block of data SMT_TRAN_PUTF_OK "q" q=size of transmitted data SMT_TRAN_GETF_OK "q" q=size of received data
The UPM tool is a small application developed with SMT, and included as an example. The UPM tool was developed on UNIX and may not be very useful on other systems.
The UPM tool allows you to manage 'unattended' processes, which may handle input/output on some devices, typically terminals. The UPM tool lets you start and stop such processes, manually or using a timer.
The UPM tool consists of four programs:
The main UPM configuration file is called "upm.ini". This is a typical "upm.ini" file: [Setup] logfile=upmd.log [Tasks] name=task1 name=task2
The [Tasks] section defines each of the UPM tasks. Each task has a config file to define task-specific values. The config file for "task1" is called "TASK1.ini". For example:
# Configuration for Task 1 [Setup] stdin=/dev/pts/1 stdout=/dev/pts/1 stderr=/dev/pts/1 [Environment] DATABASE=test PRINTER=tcp://localhost:1000 [Run] idle=banner IDLE startup=run BBORNE.gnt cancel=cleanup
The [Setup] section defines the input/output devices for the task. The [Environment] section defines a list of variables for the task. The [Run] section defines the idle command and the startup command. These commands must be executable files (scripts or programs) with optional arguments. Redirection or multiple commands are not permitted.
Each task also has an optional TIMES file. The times file for task1 is called "TASK1.tim", and looks something like this:
# This file specifies the times for Task1 mon 8-12 12:30-20 tue 8-12 12:30-20 wed 8-12 12:30-20 thu 8-12 13:05-13:06 13:08:13-10 fri 8-12 12:30-20 12/25 off 1/1 off
Each line (except blank lines and comments) defines some times for a day. The first word on the line is the day, the remaining words are one or more time specifications. A day can be:
If no TIMES file is provided, you can (or rather, must) start and stop the tasks manually.
nohup upmd&
Run the command upmc to start the BORMAN client. The upmc program acts as a 'front-end' for the upmd daemon. Since the daemon runs as a process in the background, it cannot read commands from the keyboard itself.
When you run upmc you get a message like this:
upmc> 100- Connecting to UPM daemon...
upmc> 102- Connected to UPM daemon version 1.0
upmc>
You can give various commands at the prompt. This is what the 'help' command shows:
upmc> help Commands and arguments can be in any case. Commands are: LIST - list all known tasks START [task | ALL] - start specified task (default all) STOP [task | ALL] - stop specified task (default all) STATUS [task | ALL] - show status for specified task (default all) HALT - halt UPM daemon EXIT - end this UPM client session QUIT - end this UPM client session HELP - show this information VERSION - show UPM client version
If you run upmc when the upmd daemon is not
running, you get this:
upmc> 100- Connecting to UPM daemon...
upmc> 301- UPD daemon connection failed: Connection refused
You can also run upmc to execute a command directly:
$ upmc start all
Which is the same as typing this:
$ upmc start
UPM Trouble Shooting
These are some possible errors:
The hypertext view of SMTUPMD's dialog and source code may help to understand how SMTUPMD works.
The hypertext view of SMTUPMC's dialog and source code may help to understand how SMTUPMC works.
We supply the SMT kernel as two archives: a source kit and a documentation kit (in HTML). These files are available for download by HTTP from our website on a permanent basis. You need to recompile the SMT for your specific system, using an ANSI C compiler. We don't provide binary kits.
To install and use the SMT kernel you must first install the SFL package. SMT requires the current version of the SFL. To write SMT programs you also need to install the Libero tool.
The SMT source archive is supplied as a zip file and a GNU gzipped tar file. Note that the zipped files are in MS-DOS format (carriage-return, linefeed) while the gzip+tar file is in UNIX format (linefeed only). The unzip command allows you to extract correctly for the system you are running on. These are the files in the /pub/smt/src directory:
smtsrc27.tgz 506536 100/04/01 16:17:32 Gzip/tar archive smtsrc27.zip 649300 100/04/01 16:17:32 ZIP archive smtsrc28.tgz 568875 99/08/08 12:05:32 Gzip/tar archive smtsrc28.zip 768325 99/12/30 14:08:55 ZIP archive
If you have trouble accessing the iMatix site, send us an e-mail and we'll send you the SMT archives by return e-mail.
The SMT documentation is supplied as HTML files, available on-line or off-line as a single .zip file that you can install on a hard disk for rapid access, and also as a gzipped tar file. These are the files in the /pub/smt/doc directory:
smtbig27.tgz 80744 100/04/01 16:17:33 Gzip/tar archive smtbig27.zip 80860 100/04/01 16:17:33 ZIP archive smtbig28.tgz 80419 99/08/08 12:05:38 Gzip/tar archive smtbig28.zip 80592 99/08/08 12:04:52 ZIP archive smtdoc27.tgz 168262 100/04/01 16:17:33 Gzip/tar archive smtdoc27.zip 286443 100/04/01 16:17:32 ZIP archive smtdoc28.tgz 168362 99/08/08 12:05:50 Gzip/tar archive smtdoc28.zip 286546 99/08/08 12:04:52 ZIP archive
We recommend that you unzip or gunzip/detar the documentation archive into a subdirectory. Then, point your browser at the index.htm file to access the documentation. We use relative addressing in all HTML documents, so that links work just as well on a local hard-disk (without a web server) as on-line on our website. In a windowing environment is it easy and useful to create an icon that launches a Web browser on this file.
To install the SMT kernel on a UNIX system you need to:
To unzip the source .zip file, you need the Infozip unzip tool:
$ mkdir temp $ mv smtsrc28.zip temp $ cd temp $ unzip -a smtsrc28
To decompress the source archive you need GNU gzip/gunzip and tar:
$ mkdir temp $ mv smtsrc28.tgz temp $ cd temp $ gzip -d smtsrc28.tgz or $ gunzip smtsrc28.tgz $ tar -xvf smtsrc28.tar
You can also, in extreme cases, unzip the files on a PC and transfer the individual files to the UNIX system.
To compile the SMT sources, use this command:
$ chmod +x build $ build
If you get warnings or error messages, this is usually a bad sign. Some compilers issue warnings just because you ask for ANSI compilation. If you get any other error messages, please let us know.
The SMT source archive includes a script, c, that you can (and should) use to compile an individual SMT source. This script invokes the ANSI C compiler to produce an object code file. It detects the platform and invokes the compiler with the necessary switches for ANSI C compilation. On some systems this is the normal behaviour for the cc command. On other systems it is not normal. You should make the c script executable, (preferrably) install it in a shared directory like /usr/local/bin, and try it out:
$ chmod a+rx c $ mv c /usr/local/bin $ c
The c script is also included in the SFL package.
SMT has not been tested on Digital VMS systems. However, it should be portable, especially to later versions of VMS that are POSIX compliant. To install the SMT on a Digital VMS system you need to:
To unzip the source .zip file, you need the Infozip unzip tool:
$ create/dir [.temp] $ ren smtsrc28.zip [.temp] $ set def [.temp] $ unzip -a smtsrc28
You can also, in extreme cases, unzip the files on a PC and transfer the individual files to the VMS system.
To compile the SMT sources, use this command:
$ @build.txt
Note: this command file has not yet been tested, so it would be a little surprising if it worked. Nonetheless, the work it does is simple: compile all .c programs, then link a couple of main programs. If you get warnings or error messages, this is a bad sign - please let us know.
Our preferred way of working is to build the SFL as a static library, and do the same for the SMT kernel. Using a typical Windows-hosted compiler, we create two projects. Then, we create a project for each main program. For instance, we create a project each for the echod and echocli programs. In these projects, we refer to the SFL and SMT library files. While the SFL and SMT libraries compile fine under 16-bit Windows, the link phase usually gives problems, and we prefer to work with a 32-bit memory model. All SMT programs, including the Xitami web server, run as 32-bit console processes under Windows NT and 95.
This section notes constraints that we've come across on various systems. This is not meant to be a complete list of all possible problems.
SMTSOCK uses the select() call to wait for socket events. On UNIX systems, select() returns when the process receives a signal. We use this fact to implement the process timer, SMTTIME, in an application that may also be doing socket i/o.
Under Windows 95, however, the select() call does not unblock when a signal is sent. The winsock call WSACancelBlockingCall() is meant to unblock the select() call. This does not work under Windows 95, at least. (WSAIsBlocking() always reports FALSE.)
The consequence of this is that the timeout specified in the select() call makes a difference to the way that the timer work. If we use a 1-second timeout, for instance, we find that timer events are also limited to a resolution of 1 second. This makes it difficult to use timer events for real-time work in socket based applications.
This appears to be a bug in the Windows 95 winsock.dll; it may also affect Windows NT.
Our solution is to use WSACancelBlockingCall() in any case, should the winsock.dll support it correctly. Secondly, under Windows, we reduce the select() timeout from 1 second to 1/5th of a second. On other systems - UNIX, OS/2 - we use 1 second, so that the application uses minimal system resources.
This license agreement covers your use of the iMatix SIMPLE MULTITHREADING KERNEL (SMT), its source code, documentation, and executable files, hereinafter referred to as "the Product".
The Product is Copyright © 1991-2000 iMatix Corporation. You may use it and distribute it according to this following License Agreement. If you do not agree with these terms, please remove the Product from your system. By incorporating the Product in your work or distributing the Product to others you implicitly agree to these license terms.
The Product is, and remains, Copyright © 1991-2000 iMatix Corporation, with exception of specific copyrights as noted in the individual source files.
You do not need to provide the source code for the Product as part of your product. However, you must do one of these things to comply with the Product License Agreement:
You may freely and at no cost use the Product in any project, commercial, academic, military, or private, so long as you respect the License Agreement. The License Agreement does not affect any software except the Product. In particular, any application that uses the Product does not itself fall under the License Agreement.
You may modify any part of the Product, including sources and documentation, except this License Agreement, which you may not modify.
You must clearly indicate any modifications at the start of each source file. The user of any modified Product code must know that the source file is not original.
At your discretion, you may rewrite or reuse any part of the
Product so that your derived code is not obviously part of the
Product. This derived code does not fall under the Product
License Agreement directly, but you must include a credit at the
start of each source file indicating the original authorship and
source of the code, and a statement of copyright as follows:
"Parts copyright (c) 1991-2000 iMatix Corporation."
You may freely distribute the Product, or any subset of the Product, by any means. The License, in the form of the file called "LICENSE.TXT" must accompany any such distribution.
You may charge a fee for distributing the Product, for providing a warranty on the Product, for making modifications to the Product, or for any other service provided in relation to the Product. You are not required to ask our permission for any of these activities.
At no time will iMatix Corporation associate itself with any distribution of the Product except that supplied from the Internet site http://www.imatix.com.
The Product is provided as free software, in the hope that it will be useful. It is provided "as-is", without warranty of any kind, either expressed or implied, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose. The entire risk as to the quality and performance of the Product is with you. Should the Product prove defective, the full cost of repair, servicing, or correction lies with you.
Filename: smtlib.h
Package: Libero SMT 2.x
Written: 1996/05/27 iMatix SMT kernel team smt@imatix.com
Revised: 1999/08/01
Copyright: Copyright (c) 1991-2000 iMatix Corporation
Defines the functions and objects used in the SMT kernel API.
smtlib.h defines these symbols, possibly conditionally:
Symbol: | Defined as: |
---|---|
SEND(q,name,s) | event_send ((q), NULL, (name), (byte *) (s), |
SMT_ACTION_STOP | 0xFFFFL /* End of module list */ |
SMT_AGENTEXISTS | 15 /* Agent already declared */ |
SMT_AGENTNOTREADY | 16 /* Agent not initialised */ |
SMT_AGENT_NORMAL | 0 /* 1 queue for 1 thread */ |
SMT_AGENT_ROUTER | 1 /* 1 queue for n threads */ |
SMT_EVENTEXISTS | 1 /* Event already declared */ |
SMT_INTERNALERROR | 2 /* Agent had internal error */ |
SMT_LOOP_DETECTION | 2 /* After this, we abort */ |
SMT_METHODEXISTS | 3 /* Method already declared */ |
SMT_NOERRORS | 0 /* No errors */ |
SMT_NOSUCHAGENT | 8 /* Agent not defined */ |
SMT_NOSUCHEVENT | 4 /* Unknown event name */ |
SMT_NOSUCHMETHOD | 5 /* Unknown method name */ |
SMT_NOSUCHQUEUE | 6 /* Event queue not defined */ |
SMT_NOSUCHSEMAPH | 7 /* Unknown semaphore name */ |
SMT_NOSUCHTHREAD | 9 /* Thread not declared */ |
SMT_NOTREADY | 10 /* SMT API not initialised */ |
SMT_NULL_EVENT | -2 /* No event provided */ |
SMT_NULL_STATE | -1 /* Thread not initialised */ |
SMT_OUTOFMEMORY | 11 /* Not enough heap memory left */ |
SMT_PRIORITY_HIGH | 200 /* High priority */ |
SMT_PRIORITY_LOW | 50 /* Low priority */ |
SMT_PRIORITY_MAX | 255 /* Highest priority */ |
SMT_PRIORITY_MIN | 1 /* Lowest priority */ |
SMT_PRIORITY_NORMAL | 100 /* Default priority */ |
SMT_PRIORITY_NULL | 0 /* Illegal (null) priority */ |
SMT_QUEUEISEMPTY | 12 /* Event queue is empty */ |
SMT_QUEUEISFULL | 13 /* Event queue is full */ |
SMT_SEMAPHEXISTS | 14 /* Semaphore already exists */ |
SMT_SHUTDOWN | "w" /* Shutdown signal */ |
SMT_SIGNAL_ALRM | 4 /* SIGALRM - timer signal */ |
SMT_SIGNAL_INT | 1 /* SIGINT - interrupted */ |
SMT_SIGNAL_SEGV | 3 /* SIGSEGV - segment violation */ |
SMT_SIGNAL_TERM | 2 /* SIGTERM - cancelled */ |
SMT_SIGNAL_USER | 5 /* User shutdown request */ |
SMT_TERM_EVENT | -1 /* Terminate the thread */ |
SMT_THREADEXISTS | 17 /* Thread already declared */ |
SMT_THREAD_ACTIVE | 1 /* Active and executing */ |
SMT_THREAD_PASSIVE | 2 /* Passive, between states */ |
SMT_THREAD_WAIT_EVENT | 3 /* Waiting for an event */ |
SMT_THREAD_WAIT_SEMAPH | 4 /* Waiting for a semaphore */ |
SMT_TOOMANYTHREADS | 18 /* Too many threads */ |
SMT_VERSION | "2.71" /* Main SMT version number */ |
_SMTLIB_INCLUDED | TRUE |
Type name: | Defined as: |
---|---|
AGENT | struct _AGENT |
EVENT | struct _EVENT |
HOOK | void () (THREAD *) |
METHOD | struct _METHOD |
QID | struct _QID |
QUEUE | struct _QUEUE |
SEMAPH | struct _SEMAPH |
THREAD | struct _THREAD |
event_t | short int |
state_t | short int |
#include "smtlib.h" int smt_init (void)
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 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); }
#include "smtlib.h" int smt_term (void)
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 |
{ 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); }
#include "smtlib.h" int smt_exec_full (void)
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.
{ #if (defined (SMT_TRACE)) trace ("smt_exec_full"); #endif while (smt exec step ()); return (0); }
#include "smtlib.h" Bool smt_exec_step (void)
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'.
{ #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); }
#include "smtlib.h" Bool smt_active (void)
Returns TRUE if the SMT has active threads, else false.
{ #if (defined (SMT_TRACE)) trace ("smt_active: %d", (active_threads.right != &active_threads)); #endif return (active_threads.right != &active_threads); }
#include "smtlib.h" void smt_set_console (const QID *qid)
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.
{ #if (defined (SMT_TRACE)) trace ("smt_set_console"); #endif console = *qid; }
#include "smtlib.h" void smt_set_timer (const QID *qid)
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.
{ #if (defined (SMT_TRACE)) trace ("smt_set_timer"); #endif timer = *qid; }
#include "smtlib.h" int smt_atexit (function handler)
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 |
{ 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); }
#include "smtlib.h" void smt_shutdown (void)
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.
{ signal_raised = TRUE; /* Signal user shutdown signal */ signal_value = SMT_SIGNAL_USER; /* Kernel takes it from here */ }
#include "smtlib.h" AGENT * agent_declare ( const char *agent_name /* Name of agent to declare */ )
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 |
{ 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); }
#include "smtlib.h" AGENT * agent_lookup ( const char *agent_name /* Name of agent to look for */ )
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 |
{ 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 */ }
#include "smtlib.h" int agent_destroy ( AGENT *agent /* Agent to destroy; null = all */ )
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 |
{ #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); }
#include "smtlib.h" 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 */ )
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 |
{ 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); }
#include "smtlib.h" METHOD * method_lookup ( const AGENT *agent, /* Agent to look at */ const char *method_name /* Name of method to look for */ )
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 |
{ 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 */ }
#include "smtlib.h" int method_destroy ( METHOD *method /* Method to destroy */ )
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 |
{ #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); }
#include "smtlib.h" QUEUE * queue_create ( AGENT *agent, /* Parent agent block, or null */ int max_events /* Max. events; 0 = no limit */ )
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 |
{ 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: "<none>"); #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); }
#include "smtlib.h" QUEUE * queue_lookup ( const QID *qid /* Queue to find */ )
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 |
{ 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); } }
#include "smtlib.h" int queue_destroy ( QUEUE *queue /* Queue to destroy */ )
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 |
{ #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); }
#include "smtlib.h" int queue_deliver ( QUEUE *queue, /* Queue containing events */ THREAD *thread) /* Agent thread to receive event */
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.
{ 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 */ }
#include "smtlib.h" int queue_flush ( QUEUE *queue /* Queue to flush */ )
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 |
{ 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); }
#include "smtlib.h" 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 */ )
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 |
{ 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 */ }
#include "smtlib.h" EVENT * event_accept ( QUEUE *queue, /* Queue to take event from */ EVENT *event /* Event, or null for first */ )
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 |
{ #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); }
#include "smtlib.h" int event_reject ( QUEUE *queue, /* Queue to take event from */ EVENT *event /* Event, or null for first */ )
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 |
{ #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)); }
#include "smtlib.h" int event_expire ( QUEUE *queue, /* Queue to take event from */ EVENT *event /* Event, or null for first */ )
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 |
{ #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)); }
#include "smtlib.h" int event_discard ( QUEUE *queue, /* Queue to take event from */ EVENT *event /* Event, or null */ )
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 |
{ #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); }
#include "smtlib.h" EVENT * event_iterate ( QUEUE *queue, /* Queue to search */ EVENT *event /* Event, or null for first */ )
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 |
{ #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); }
#include "smtlib.h" int event_destroy ( EVENT *event /* Event block to destroy */ )
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.
{ #if (defined (SMT_TRACE)) trace ("event_destroy: event=%s", event-> name); #endif node_destroy (event); /* Unlink and destroy event */ return (0); }
#include "smtlib.h" void event_wait (void)
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.
{ #if (defined (SMT_TRACE)) trace ("event_wait"); #endif break_wanted = BREAK_WAIT_EVENT; }
#include "smtlib.h" THREAD * thread_create ( const char *agent_name, /* Name of agent */ const char *thread_name /* Create thread with this name */ )
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 |
{ 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); }
#include "smtlib.h" THREAD * thread_lookup ( const char *agent_name, /* Name of agent */ const char *thread_name /* Create thread with this name */ )
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 |
{ 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); }
#include "smtlib.h" int thread_destroy ( THREAD *thread, /* Thread to destroy */ Bool cleanup /* Delete queue if last thread */ )
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 |
{ 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); }
#include "smtlib.h" SEMAPH * semaph_create ( const char *semaph_name, /* Name of semaph to create */ int value /* Initial semaphore value */ )
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 |
{ 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); }
#include "smtlib.h" SEMAPH * semaph_lookup ( const char *semaph_name /* Name of semaph to look for */ )
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 |
{ 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 */ }
#include "smtlib.h" int semaph_destroy ( SEMAPH *semaph /* Semaph to destroy; null = all */ )
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 |
{ #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); }
#include "smtlib.h" int semaph_wait ( SEMAPH *semaph /* Semaph to wait for */ )
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 |
{ #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); }
#include "smtlib.h" int semaph_signal ( SEMAPH *semaph /* Semaph to signal */ )
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 |
{ #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 */ }
#include "smtlib.h" int lazy_creat (char *path, int mode)
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 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); }
#include "smtlib.h" int lazy_creat_text (char *path, int mode)
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 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); }
#include "smtlib.h" int lazy_open (char *path, int flags)
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.
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 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); }
#include "smtlib.h" int lazy_open_text (char *path, int flags)
As lazy open(), but opens the file in text mode, on those platforms where this makes a difference.
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 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); }
#include "smtlib.h" int lazy_read (int handle, char *buffer, size_t count)
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 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); }
#include "smtlib.h" int lazy_write (int handle, char *buffer, size_t count)
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 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); }
#include "smtlib.h" int lazy_close (int handle)
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 rc; #if (defined (SMT_TRACE)) trace ("lazy_close: handle=%d", handle); #endif rc = close (handle); set_io_completed (rc); return (rc); }
#include "smtlib.h" int senderr (const QID *to_queue)
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().
senderr (&thread-> event-> sender);
{ 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 */ }
#include "smtlib.h" int sendfmt (const QID *to_queue, char *name, char *format, ...)
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().
sendfmt (&console, "INFO", "Error accessing %s file", filename);
{ 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 */ }
#include "smtlib.h" void raise_exception (event_t event)
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;'.
{ #if (defined (SMT_TRACE)) trace ("raise_exception"); #endif _exception_raised = TRUE; _the_exception_event = event; }
#include "smtlib.h" void recycle_module (Bool wanted)
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.
{ #if (defined (SMT_TRACE)) trace ("recycle_module"); #endif if (wanted) break_wanted = BREAK_RECYCLE; else break_wanted = BREAK_CONTINUE; }
#include "smtlib.h" void smt_set_step (const char *step)
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.
{ cur_step = (char *) step; }
#include "smtlib.h" char * smt_crash_report (void)
Returns a formatted string showing the current agent, state, module, step, event, and catch report, if any.
{ 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); }
Filename: smtdefn.h
Package: Libero SMT 2.x
Written: 1996/05/27 iMatix SMT kernel team smt@imatix.com
Revised: 1999/08/27
Copyright: Copyright (c) 1991-2000 iMatix Corporation
Defines the standard agent prototypes and methods.
smtdefn.h defines these symbols, possibly conditionally:
Symbol: | Defined as: |
---|---|
SMT_ECHO | "smtecho" /* Name of TCP/IP echo agent */ |
SMT_ECHO_PORT | "7" /* Port for echo agent */ |
SMT_FTPC | "smtftpc" /* Name of FTP server control agent */ |
SMT_FTPD | "smtftpd" /* Name of FTP server data agent */ |
SMT_FTP_PORT | "21" /* Port for FTP agent */ |
SMT_HTTP | "smthttp" /* Name of HTTP server agent */ |
SMT_HTTP_PORT | "80" /* Port for HTTP agent */ |
SMT_LOGGING | "smtlog" /* Name of logging agent */ |
SMT_OPERATOR | "smtoper" /* Name of operator agent */ |
SMT_RDNS | "smtrdns" /* Name of reverse dns agent */ |
SMT_SHTTP_PORT | "443" /* Port for SHTTP agent */ |
SMT_SLOT | "smtslot" /* Name of time slot agent */ |
SMT_SOCKET | "smtsock" /* Name of socket i/o agent */ |
SMT_SSL | "smtssl" /* Name of SSL server agent */ |
SMT_TIMER | "smttime" /* Name of timer agent */ |
SMT_TRANSFER | "smttran" /* Name of transfer agent */ |
_SMTDEFN_INCLUDED | TRUE |
#include "smtdefn.h" int smtauth_init (void)
Initialises the SMT authorisation agent. Returns 0 if initialised okay, -1 if there was an error. Supports these public methods:
OPEN | Create a protected url symbol table. |
REOPEN | Reload authorisation data |
CHECK URL | Check if a url is protected. |
CHECK NAME | Check user name and password for a url; returns user name. |
CLOSE | Free symbol table and end thread. |
{ AGENT *agent; /* Handle for our agent */ THREAD *thread; /* Handle to console thread */ # include "smtauth.i" /* Include dialog interpreter */ /* Method name Event value Priority */ /* Shutdown event comes from Kernel */ method declare (agent, "SHUTDOWN", shutdown_event, SMT_PRIORITY_MAX); /* Public methods supported by this agent */ method declare (agent, "OPEN", open_event, 0); method declare (agent, "REOPEN", reopen_event, 0); method declare (agent, "CHECK_URL", check_url_event, 0); method declare (agent, "CHECK_NAME", check_name_event, 0); method declare (agent, "CLOSE", close_event, 0); /* Ensure that operator console is running, else start it up */ smtoper init (); if ((thread = thread lookup (SMT_OPERATOR, "")) != NULL) console = thread-> queue-> qid; else return (-1); /* Signal okay to caller that we initialised okay */ return (0); }
#include "smtdefn.h" int smtecho_init (void)
Initialises the SMT echo agent. Returns 0 if initialised okay, -1 if there was an error. The echo agent handles the TCP ECHO protocol on port 7. It logs connections to the file smtecho.log. It sends errors to the SMTOPER agent. If you set the ip_portbase before calling this function, the echo port is shifted by that amount. A typical value for ip_portbase is 5000: the echo agent will then handle connections on port 5007.
{ AGENT *agent; /* Handle for our agent */ THREAD *thread; /* Handle to various threads */ # include "smtecho.i" /* Include dialog interpreter */ /* Method name Event value Priority */ /* Shutdown event comes from Kernel */ method declare (agent, "SHUTDOWN", shutdown_event, SMT_PRIORITY_MAX); /* Reply events from socket agent */ method declare (agent, "SOCK_INPUT_OK", ok_event, 0); method declare (agent, "SOCK_OUTPUT_OK", ok_event, 0); method declare (agent, "SOCK_READ_OK", read_ok_event, 0); method declare (agent, "SOCK_WRITE_OK", write_ok_event, 0); method declare (agent, "SOCK_CLOSED", closed_event, 0); method declare (agent, "SOCK_ERROR", error_event, 0); method declare (agent, "SOCK_TIMEOUT", error_event, 0); /* Private methods used to pass initial thread events */ method declare (agent, "_MASTER", master_event, 0); method declare (agent, "_CLIENT", client_event, 0); /* Ensure that operator console is running, else start it up */ smtoper init (); if ((thread = thread lookup (SMT_OPERATOR, "")) != NULL) operq = thread-> queue-> qid; else return (-1); /* Ensure that socket agent is running, else start it up */ smtsock init (); if ((thread = thread lookup (SMT_SOCKET, "")) != NULL) sockq = thread-> queue-> qid; else return (-1); /* Ensure that logging agent is running, and create new thread */ smtlog init (); if ((thread = thread create (SMT_LOGGING, "")) != NULL) logq = thread-> queue-> qid; /* Get logging queue id */ else return (-1); /* Create initial thread to manage master port */ if ((thread = thread create (AGENT_NAME, "")) != NULL) { SEND (&thread-> queue-> qid, "_MASTER", ""); ((TCB *) thread-> tcb)-> thread_type = master_event; ((TCB *) thread-> tcb)-> handle = 0; } else return (-1); /* Signal okay to caller that we initialised okay */ return (0); }
#include "smtdefn.h" int smthttp_init (char *p_rootdir, /* Document root directory */ char *p_cgidir) /* CGI binary directory */
Initialises the HTTP agent. Returns 0 if initialised okay, -1 if there was an error. The HTTP agent manages a HTTP service on port 80 (+ ip_portbase). Sends errors and information messages to the SMTOPER agent.
{ AGENT *agent; /* Handle for our agent */ THREAD *thread; /* Handle to various threads */ # include "smthttp.i" /* Include dialog interpreter */ /* Method name Event value Priority */ /* Shutdown event comes from Kernel */ method declare (agent, "SHUTDOWN", shutdown_event, SMT_PRIORITY_MAX); /* Reply events from socket agent */ method declare (agent, "SOCK_INPUT_OK", ok_event, 0); method declare (agent, "SOCK_OUTPUT_OK", ok_event, 0); method declare (agent, "SOCK_READ_OK", ok_event, 0); method declare (agent, "SOCK_WRITE_OK", ok_event, 0); method declare (agent, "SOCK_CLOSED", sock_closed_event, 0); method declare (agent, "SOCK_ERROR", sock_error_event, 0); method declare (agent, "SOCK_TIMEOUT", sock_timeout_event, 0); /* Reply events from transfer agent */ method declare (agent, "TRAN_PUTF_OK", finished_event, 0); method declare (agent, "TRAN_CLOSED", sock_closed_event, 0); method declare (agent, "TRAN_ERROR", sock_error_event, 0); /* Registration events from WSX agents */ method declare (agent, "WSX_INSTALL", wsx_install_event, 0); method declare (agent, "WSX_CANCEL", wsx_cancel_event, 0); /* Reply events from WSX agents */ method declare (agent, "WSX_OK", wsx_ok_event, 0); method declare (agent, "WSX_MULTIPART", wsx_multi_event, 0); method declare (agent, "WSX_BIN", wsx_binary_event, 0); method declare (agent, "WSX_MBIN", wsx_multibin_event, 0); method declare (agent, "WSX_ERROR", wsx_error_event, 0); method declare (agent, "WSX_REDIRECT", wsx_redirect_event, 0); method declare (agent, "WSX_RESTART", wsx_restart_event, 0); method declare (agent, "WSX_KILL", wsx_kill_event, 0); /* Reply events from timer agent */ method declare (agent, "TIME_ALARM", timer_event, SMT_PRIORITY_LOW); /* Private methods used to pass initial thread events */ method declare (agent, "_MASTER", master_event, 0); method declare (agent, "_CLIENT", client_event, 0); /* Private methods used between threads */ method declare (agent, "_CANCEL", cancel_event, 0); method declare (agent, "_RESTART", restart_event, 0); /* Reply events from SSL agents */ declare_ssl_accepted (new_ssl_client_event, 0); declare_ssl_read_ok (ok_event, 0); declare_ssl_open_ok (ssl_open_event, 0); declare_ssl_error (ssl_error_event, 0); declare_ssl_write_ok (ok_event, 0); declare_ssl_put_slice_ok (finished_event, 0); /* Ensure that operator console is running, else start it up */ smtoper init (); if ((thread = thread lookup (SMT_OPERATOR, "")) != NULL) operq = thread-> queue-> qid; else return (-1); /* Ensure that socket i/o agent is running, else start it up */ smtsock init (); if ((thread = thread lookup (SMT_SOCKET, "")) != NULL) sockq = thread-> queue-> qid; else return (-1); /* Check whether SSL agent is running */ if ((thread = thread lookup ("SMTSSL", "main")) != NULL) { sslq = thread-> queue-> qid; server_secure = TRUE; } else server_secure = FALSE; /* Ensure that timer agent is running, else start it up */ smttime init (); if ((thread = thread lookup (SMT_TIMER, "")) != NULL) timeq = thread-> queue-> qid; else return (-1); /* Ensure that transfer agent is running, else start it up */ smttran init (); if ((thread = thread lookup (SMT_TRANSFER, "")) != NULL) tranq = thread-> queue-> qid; else return (-1); /* Create initial thread to manage master port */ if ((thread = thread create (AGENT_NAME, "main")) != NULL) { SEND (&thread-> queue-> qid, "_MASTER", ""); ((TCB *) thread-> tcb)-> thread_type = master_event; ((TCB *) thread-> tcb)-> handle = 0; } else return (-1); this_agent = agent; /* Handle to ourselves */ /* Use standard port number. Caller can set ip_portbase if wanted */ /* to place the port into a specific area. */ master_port = SMT_HTTP_PORT; http_init (); /* Initialise HTTP library */ /* Get root and cgi directories passed to smthttp.c */ rootdir = p_rootdir; cgidir = p_cgidir; /* Get local host addresses table, which may be NULL */ hostaddrs = get_hostaddrs (); /* These strings are allocated if not null - initialise now */ request_log = NULL; header_log = NULL; /* Signal okay to caller that we initialised okay */ return (0); }
#include "smtdefn.h" int smtftpc_init (char *p_rootdir) /* Server root directory */
Initialises the FTP control agent. Returns 0 if initialised okay, -1 if there was an error.
{ AGENT *agent; /* Handle for our agent */ THREAD *thread; /* Handle to various threads */ # include "smtftpc.i" /* Include dialog interpreter */ /* Method name Event value Priority */ /* Shutdown event comes from Kernel */ method declare (agent, "SHUTDOWN", shutdown_event, SMT_PRIORITY_MAX); /* Restart can come from control panel and HTTP agent */ method declare (agent, "RESTART", restart_event, 0); method declare (agent, "RELOAD", reload_event, 0); /* Reply events from socket agent */ method declare (agent, "SOCK_INPUT_OK", input_ok_event, 0); method declare (agent, "SOCK_OUTPUT_OK", ok_event, 0); method declare (agent, "SOCK_READ_OK", ok_event, 0); method declare (agent, "SOCK_WRITE_OK", ok_event, 0); method declare (agent, "SOCK_CLOSED", sock_closed_event, 0); method declare (agent, "SOCK_ERROR", sock_error_event, 0); method declare (agent, "SOCK_TIMEOUT", sock_timeout_event, 0); /* Reply events from timer agent */ method declare (agent, "TIME_ALARM", timeout_event, SMT_PRIORITY_LOW); /* Reply events from FTP data agent */ method declare (agent, "FTPD_CONNECTED", connected_event, SMT_PRIORITY_LOW); method declare (agent, "FTPD_ERROR", error_event, SMT_PRIORITY_LOW); method declare (agent, "FTPD_END", end_event, SMT_PRIORITY_HIGH); method declare (agent, "FTPD_PASS_OK", ok_event, SMT_PRIORITY_LOW); method declare (agent, "FTPD_ABORTED", aborted_event, SMT_PRIORITY_HIGH); /* Private methods used to pass initial thread events */ method declare (agent, "_MASTER", master_event, 0); method declare (agent, "_CLIENT", client_event, 0); /* Private methods used between threads */ method declare (agent, "_CANCEL", cancel_event, 0); /* Ensure that operator console is running, else start it up */ smtoper init (); if ((thread = thread lookup (SMT_OPERATOR, "")) != NULL) operq = thread-> queue-> qid; else return (-1); /* Ensure that socket i/o agent is running, else start it up */ smtsock init (); if ((thread = thread lookup (SMT_SOCKET, "")) != NULL) sockq = thread-> queue-> qid; else return (-1); /* Ensure that timer agent is running, else start it up */ smttime init (); if ((thread = thread lookup (SMT_TIMER, "")) != NULL) timeq = thread-> queue-> qid; else return (-1); /* Ensure that FTP data agent is running, else start it up */ smtftpd init (); if ((thread = thread lookup (SMT_FTPD, "")) != NULL) dataq = thread-> queue-> qid; else return (-1); /* Create initial thread to manage master port */ if ((thread = thread create (AGENT_NAME, "main")) != NULL) { SEND (&thread-> queue-> qid, "_MASTER", ""); ((TCB *) thread-> tcb)-> thread_type = master_event; ((TCB *) thread-> tcb)-> handle = 0; } else return (-1); this_agent = agent; /* Handle to ourselves */ ftp_initialise (); /* Get root directory */ rootdir = mem_strdup (p_rootdir); /* Get local host addresses table, which may be NULL */ hostaddrs = get_hostaddrs (); /* Signal to caller that we initialised okay */ return (0); }
#include "smtdefn.h" int smtftpd_init (void)
Initialises the FTP data agent. Returns 0 if initialised okay, -1 if there was an error.
{ AGENT *agent; /* Handle for our agent */ THREAD *thread; /* Handle to various threads */ # include "smtftpd.i" /* Include dialog interpreter */ /* Method name Event value Priority */ /* Shutdown event comes from Kernel */ method declare (agent, "SHUTDOWN", shutdown_event, SMT_PRIORITY_MAX); /* Reply events from socket agent */ method declare (agent, "SOCK_INPUT_OK", ok_event, 0); method declare (agent, "SOCK_OUTPUT_OK", ok_event, 0); method declare (agent, "SOCK_READ_OK", ok_event, 0); method declare (agent, "SOCK_WRITE_OK", ok_event, 0); method declare (agent, "SOCK_CLOSED", sock_closed_event, 0); method declare (agent, "SOCK_ERROR", sock_error_event, 0); method declare (agent, "SOCK_TIMEOUT", sock_timeout_event, 0); /* Reply events from transfer agent */ method declare (agent, "TRAN_PUTF_OK", finished_event, SMT_PRIORITY_HIGH); method declare (agent, "TRAN_GETF_OK", finished_event, SMT_PRIORITY_HIGH); method declare (agent, "TRAN_CLOSED", sock_closed_event, SMT_PRIORITY_HIGH); method declare (agent, "TRAN_ERROR", sock_error_event, SMT_PRIORITY_HIGH); /* Public methods supported by this agent */ method declare (agent, "FTPD_PASSIVE", passive_event, SMT_PRIORITY_LOW); method declare (agent, "FTPD_PUT_FILE", put_file_event, SMT_PRIORITY_NORMAL); method declare (agent, "FTPD_GET_FILE", get_file_event, SMT_PRIORITY_NORMAL); method declare (agent, "FTPD_APPEND_FILE", append_file_event, SMT_PRIORITY_NORMAL); method declare (agent, "FTPD_ABORT", abort_event, SMT_PRIORITY_HIGH); method declare (agent, "FTPD_CLOSECTRL", close_control_event, SMT_PRIORITY_HIGH); /* Private method used to pass initial thread events */ method declare (agent, "_MASTER", master_event, 0); /* Ensure that operator console is running, else start it up */ smtoper init (); if ((thread = thread lookup (SMT_OPERATOR, "")) != NULL) operq = thread-> queue-> qid; else return (-1); /* Ensure that socket i/o agent is running, else start it up */ smtsock init (); if ((thread = thread lookup (SMT_SOCKET, "")) != NULL) sockq = thread-> queue-> qid; else return (-1); /* Ensure that timer agent is running, else start it up */ smttime init (); if ((thread = thread lookup (SMT_TIMER, "")) != NULL) timeq = thread-> queue-> qid; else return (-1); /* Ensure that transfer agent is running, else start it up */ smttran init (); if ((thread = thread lookup (SMT_TRANSFER, "")) != NULL) tranq = thread-> queue-> qid; else return (-1); /* Create initial thread to manage master port */ if ((thread = thread create (AGENT_NAME, "")) != NULL) { SEND (&thread-> queue-> qid, "_MASTER", ""); ((TCB *) thread-> tcb)-> thread_type = master_event; } else return (-1); pasv_port = sym_create_table (); /* Signal okay to caller that we initialised okay */ return (0); }
#include "smtdefn.h" int smtrdns_init (void)
Initialises the reverse DNS agent. Returns 0 if initialised okay, -1 if there was an error.
{ AGENT *agent; /* Handle for our agent */ THREAD *thread; /* Handle to various threads */ # include "smtrdns.i" /* Include dialog interpreter */ /* Method name Event value Priority */ /* Shutdown event comes from Kernel */ method declare (agent, "SHUTDOWN", shutdown_event, SMT_PRIORITY_MAX); /* Reply events from socket agent */ method declare (agent, "SOCK_INPUT_OK", input_ok_event, 0); method declare (agent, "SOCK_OUTPUT_OK", ok_event, 0); method declare (agent, "SOCK_READ_OK", ok_event, 0); method declare (agent, "SOCK_WRITE_OK", ok_event, 0); method declare (agent, "SOCK_CLOSED", sock_closed_event, 0); method declare (agent, "SOCK_ERROR", sock_error_event, 0); method declare (agent, "SOCK_TIMEOUT", sock_timeout_event, 0); /* Public methods supported by this agent */ method declare (agent, "GET_HOST_NAME", get_host_event, 0); method declare (agent, "GET_HOST_IP", get_ip_event, 0); /* Reply events from timer agent */ method declare (agent, "TIME_ALARM", timer_event, SMT_PRIORITY_LOW); /* Private methods used to pass initial thread events */ method declare (agent, "_MASTER", master_event, 0); method declare (agent, "_CLIENT", client_event, 0); /* Ensure that operator console is running, else start it up */ smtoper init (); if ((thread = thread lookup (SMT_OPERATOR, "")) != NULL) operq = thread-> queue-> qid; else return (-1); /* Ensure that socket i/o agent is running, else start it up */ smtsock init (); if ((thread = thread lookup (SMT_SOCKET, "")) != NULL) sockq = thread-> queue-> qid; else return (-1); /* Ensure that timer agent is running, else start it up */ smttime init (); if ((thread = thread lookup (SMT_TIMER, "")) != NULL) timeq = thread-> queue-> qid; else return (-1); /* Create initial thread to manage master port */ if ((thread = thread create (AGENT_NAME, "")) != NULL) { SEND (&thread-> queue-> qid, "_MASTER", ""); ((TCB *) thread-> tcb)-> thread_type = master_event; } else return (-1); /* Signal okay to caller that we initialised okay */ return (0); }
#include "smtdefn.h" int smtlog_init (void)
Initialises the SMT logging agent. Returns 0 if initialised okay, -1 if there was an error. The logging agent writes data to log files. Create an unnamed thread for each log file you want to manage, then send events to that thread. Supports these public methods:
CYCLE | Cycle log file if it already exists. |
OPEN | Start a new logfile as specified by event body. |
APPEND | Append to an existing logfile as specified by event body. |
PUT | Write line to logile, prefixed by date and time. |
PLAIN | Use plain logfile output (no timestamp). |
STAMP | Put timestamp at start of each logged line. |
CLOSE | Close logfile and destroy thread. |
{ AGENT *agent; /* Handle for our agent */ THREAD *thread; /* Handle to console thread */ # include "smtlog.i" /* Include dialog interpreter */ /* Method name Event value Priority */ /* Shutdown event comes from Kernel */ method declare (agent, "SHUTDOWN", shutdown_event, SMT_PRIORITY_MAX); /* Public methods supported by this agent */ method declare (agent, "CYCLE", cycle_event, 0); method declare (agent, "OPEN", open_event, 0); method declare (agent, "APPEND", append_event, 0); method declare (agent, "PUT", put_event, 0); method declare (agent, "PLAIN", plain_event, 0); method declare (agent, "STAMP", stamp_event, 0); method declare (agent, "CLOSE", close_event, 0); /* Ensure that operator console is running, else start it up */ smtoper init (); if ((thread = thread lookup (SMT_OPERATOR, "")) != NULL) console = thread-> queue-> qid; else return (-1); /* Signal okay to caller that we initialised okay */ return (0); }
#include "smtdefn.h" int smtoper_init (void)
Initialises the SMT operator agent. Returns 0 if initialised okay, -1 if there was an error. The operator agent writes messages to the standard error device. More sophisticated implementations could send messages to consoles. Creates one unnamed thread automatically. Supports these public methods:
ERROR | Handle event as a serious error message. |
WARNING | Handle event as a warning message. |
INFO | Handle event as an information message. |
SET LOG | Send all output to specified thread. |
DISABLE | Console ignores all messages. |
ENABLE | Console handles messages (default). |
{ AGENT *agent; /* Handle for our agent */ THREAD *thread; /* Handle for initial thread */ # include "smtoper.i" /* Include dialog interpreter */ /* We give this agent a high priority, so that we get to see messages */ /* and errors as soon as possible. */ agent-> priority = SMT_PRIORITY_HIGH; /* Method name Event value Priority */ /* Shutdown event comes from Kernel, normal priority so we can show */ /* incoming messages before we shut down. */ method declare (agent, "SHUTDOWN", shutdown_event, 0); /* Public methods supported by this agent */ method declare (agent, "SET_LOG", log_event, 0); method declare (agent, "ERROR", error_event, 0); method declare (agent, "WARNING", warning_event, 0); method declare (agent, "INFO", info_event, 0); method declare (agent, "DISABLE", disable_event, 0); method declare (agent, "ENABLE", enable_event, 0); /* Create initial, unnamed thread */ thread = thread create (AGENT_NAME, ""); smt set console (&thread-> queue-> qid); /* Signal okay to caller that we initialised okay */ return (0); }
#include "smtdefn.h" int smtslot_init (void)
Initialises the SMT time slot agent. Returns 0 if initialised okay, -1 if there was an error. The time slot agent manages time slots. You create a named thread, then send SPECIFY events to define the various time slots for your application. Then you send an ON or OFF event to initialise the timer. The time slot agent then sends SWITCH_ON and SWITCH_OFF events as required. A slot specification is a string, in the format: "name value ...". The name field is a day name ("mon"-"sun"), a date in MD order ("12/31") or a date in YMD order ("95/12/31"). The value is a list of times in 24 hour HH:MM[-HH:MM] format ("7:30-12:30 13:30-17:30 17:35"). A value "off" clears all time slots for that day. The time slot accuracy is SLOT_TICK csecs. Any day that does not have specified values is switched 'off'. Supports these public methods:
SPECIFY | Define a time slot specification. |
RESET | Reset all time slots. |
ON | Initialise timer - application is switched on. |
OFF | Initialise timer - application is switched off. |
FINISH | End time slot thread. |
{ AGENT *agent; /* Handle for our agent */ THREAD *thread; /* Handle to console thread */ # include "smtslot.i" /* Include dialog interpreter */ /* Method name Event value Priority */ /* Shutdown event comes from Kernel */ method declare (agent, "SHUTDOWN", shutdown_event, SMT_PRIORITY_MAX); /* Alarm event sent by timer to this agent */ method declare (agent, "TIME_ALARM", tick_event, 0); /* Public methods supported by this agent */ method declare (agent, "SPECIFY", specify_event, 0); method declare (agent, "RESET", reset_event, 0); method declare (agent, "ON", on_event, 0); method declare (agent, "OFF", off_event, 0); method declare (agent, "FINISH", finish_event, 0); /* Ensure that timer agent is running, else start it up */ smttime init (); if ((thread = thread lookup (SMT_TIMER, "")) != NULL) timeq = thread-> queue-> qid; else return (-1); /* Signal okay to caller that we initialised okay */ return (0); }
#include "smtdefn.h" int smtsock_init (void)
Initialises the SMT socket agent. Returns 0 if initialised okay, -1 if there was an error. The socket agent manages all sockets (TCP and UPD) used by an SMT application. Creates an unnamed thread automatically: send events to that thread. Initialises the sflsock socket interface automatically. Supports these public methods:
READ | Read a specified amount of input data (use SMT_SOCK_READ). |
WRITE | Write a specified amount of output data (use SMT_SOCK_WRITE). |
READR | Read input data, repeatedly (use SMT_SOCK_READ). |
READH | As for READ, but for blocks > 64k (use SMT_SOCK_READH). |
WRITEH | As for WRITE, but for blocks > 64k (use SMT_SOCK_WRITEH). |
READRH | As for READR, but for blocks > 64k (use SMT_SOCK_READH). |
INPUT | Wait for any input ready on socket (use SMT_SOCK_INPUT). |
INPUTR | Wait for any input, repeatedly (use SMT_SOCK_INPUT). |
OUTPUT | Wait for any output ready on socket (use SMT_SOCK_OUTPUT). |
CONNECT | Make socket connection to host & port (use SMT_SOCK_CONNECT). |
FLUSH | Delete all requests for specified socket (use SMT_SOCK_FLUSH). |
{ AGENT *agent; /* Handle for our agent */ THREAD *thread; /* Handle to console thread */ # include "smtsock.i" /* Include dialog interpreter */ /* We give this agent a low priority, so that it will only run after */ /* all other threads. This is important, since it blocks on select(). */ agent-> priority = SMT_PRIORITY_LOW; /* Method name Event value Priority */ /* Shutdown event comes from Kernel */ method declare (agent, "SHUTDOWN", shutdown_event, SMT_PRIORITY_MAX); /* Public methods supported by this agent */ method declare (agent, "READ", read_event, 0); method declare (agent, "READR", readr_event, 0); method declare (agent, "READH", readh_event, 0); method declare (agent, "READRH", readrh_event, 0); method declare (agent, "WRITE", write_event, 0); method declare (agent, "WRITEH", writeh_event, 0); method declare (agent, "INPUT", input_event, 0); method declare (agent, "INPUTR", inputr_event, 0); method declare (agent, "OUTPUT", output_event, 0); method declare (agent, "CONNECT", connect_event, 0); method declare (agent, "FLUSH", flush_event, 0); /* Private method used to cycle on select() call */ method declare (agent, "_TIMEOUT", timeout_event, 0); /* Ensure that operator console is running, else start it up */ if (agent lookup (SMT_OPERATOR) == NULL) smtoper init (); if ((thread = thread lookup (SMT_OPERATOR, "")) != NULL) operq = thread-> queue-> qid; else return (-1); /* Initialise the socket interface and register sock_term() */ if (sock_init () == 0) smt atexit ((function) sock_term); else { sendfmt (&operq, "ERROR", "smtsock: could not initialise socket interface"); sendfmt (&operq, "ERROR", "smtsock: %s", connect_errlist [connect_error ()]); return (-1); } ip_nonblock = TRUE; /* Want nonblocking sockets */ /* Create initial, unnamed thread */ thread create (AGENT_NAME, ""); /* Signal okay to caller that we initialised okay */ return (0); }
#include "smtdefn.h" void smtsock_trace (Bool trace_value)
Enables/disables socket tracing: to enable, call with TRUE as argument; to disable call with FALSE as argument. Socket trace data is sent to the console.
{ trace_flag = trace_value; }
#include "smtdefn.h" int smttime_init (void)
Initialises the SMT timer agent. Returns 0 if initialised okay, -1 if there was an error. The timer agent provides timing events after a certain delay, at a specific time, or at a specific frequency. When you initialise the timer agent it creates an unnamed thread automatically. Send events to this thread. The timer accuracy is 1/100th of a second, depending on the system capacity and speed. Supports these public methods:
ALARM | Send an alarm after some delay (use SMT_TIME_ALARM). |
WAKEUP | Send an alarm at some time (use SMT_TIME_ALARM). |
CLOCK | Send an alarm at some frequency (use SMT_TIME_CLOCK). |
FLUSH | Cancel all timing events for a client thread. |
{ AGENT *agent; /* Handle for our agent */ # include "smttime.i" /* Include dialog interpreter */ /* Method name Event value Priority */ /* Shutdown event comes from Kernel */ method declare (agent, "SHUTDOWN", shutdown_event, SMT_PRIORITY_MAX); /* Timer event sent by kernel to the timer agent (this program) */ method declare (agent, "TIMER", timer_event, 0); /* Private event, used to loop if no system timers are available */ method declare (agent, "_TIMER", timer_event, SMT_PRIORITY_LOW); /* Public methods supported by this agent */ method declare (agent, "ALARM", alarm_event, 0); method declare (agent, "WAKEUP", wakeup_event, 0); method declare (agent, "CLOCK", clock_event, 0); method declare (agent, "FLUSH", flush_event, 0); /* Create initial, unnamed thread */ thread create (AGENT_NAME, ""); /* Signal okay to caller that we initialised okay */ return (0); }
#include "smtdefn.h" int smttran_init (void)
Initialises the SMT transfer agent. Returns 0 if initialised okay, -1 if there was an error. The transfer agent reads and writes blocks of data, or complete files, through an open TCP/IP socket. A block of data is sent/received as a two-byte length header followed by the data block. Creates an unnamed thread automatically. Supports these public methods:
PUT BLOCK | Write a length-specified block to a socket (< 64k) |
GET BLOCK | Read a length-specified block from a socket (< 64k) |
PUT HUGE | Write a length-specified block to a socket (< 2Gb) |
GET HUGE | Read a length-specified block from a socket (< 2Gb) |
PUT FILE | Write part or all of a file to a socket |
GET FILE | Read part or all of a file from a socket |
PIPE CREATE | Create new transfer pipe |
CLEAR PIPES | Destroy all transfer pipes |
COMMIT | Wait until all transfer requests are finished |
{ AGENT *agent; /* Handle for our agent */ THREAD *thread; /* Handle to various threads */ # include "smttran.i" /* Include dialog interpreter */ /* Method name Event value Priority */ /* Shutdown event comes from Kernel */ method declare (agent, "SHUTDOWN", shutdown_event, SMT_PRIORITY_MAX); /* Reply events from socket agent */ method declare (agent, "SOCK_INPUT_OK", ok_event, 0); method declare (agent, "SOCK_OUTPUT_OK", ok_event, 0); method declare (agent, "SOCK_READ_OK", read_ok_event, 0); method declare (agent, "SOCK_WRITE_OK", write_ok_event, 0); method declare (agent, "SOCK_READH_OK", readh_ok_event, 0); method declare (agent, "SOCK_WRITEH_OK", writeh_ok_event, 0); method declare (agent, "SOCK_CLOSED", sock_closed_event, 0); method declare (agent, "SOCK_ERROR", sock_error_event, 0); method declare (agent, "SOCK_TIMEOUT", sock_error_event, 0); /* Reply events from timer agent */ method declare (agent, "TIME_ALARM", alarm_event, 0); method declare (agent, "TIME_ERROR", error_event, 0); /* Public methods supported by this agent */ declare_put_block (put_block_event, 0); declare_get_block (get_block_event, 0); declare_put_huge (put_huge_event, 0); declare_get_huge (get_huge_event, 0); declare_put_file (put_file_event, 0); declare_get_file (get_file_event, 0); declare_pipe_create (pipe_create_event, 0); declare_clear_pipes (clear_pipes_event, 0); declare_tran_commit (commit_event, SMT_PRIORITY_MIN); /* Private methods used to pass initial thread events */ method declare (agent, "_MASTER", master_event, 0); method declare (agent, "_PIPE_MANAGER", pipe_manager_event, 0); /* Ensure that operator console is running, else start it up */ smtoper init (); if ((thread = thread lookup (SMT_OPERATOR, "")) != NULL) operq = thread-> queue-> qid; else return (-1); /* Ensure that socket agent is running, else start it up */ smtsock init (); if ((thread = thread lookup (SMT_SOCKET, "")) != NULL) sockq = thread-> queue-> qid; else return (-1); /* Ensure that timer agent is running, else start it up */ smttime init (); if ((thread = thread lookup (SMT_TIMER, "")) != NULL) timeq = thread-> queue-> qid; else return (-1); /* Create initial thread to manage master thread */ if ((thread = thread create (AGENT_NAME, "")) != NULL) { SEND (&thread-> queue-> qid, "_MASTER", ""); ((TCB *) thread-> tcb)-> thread_type = master_event; ((TCB *) thread-> tcb)-> filename = NULL; } else return (-1); /* Create initial thread to manage pipes */ if ((thread = thread create (AGENT_NAME, "__pipe_manager")) != NULL) { SEND (&thread-> queue-> qid, "_PIPE_MANAGER", ""); ((TCB *) thread-> tcb)-> thread_type = pipe_manager_event; ((TCB *) thread-> tcb)-> filename = NULL; } else return (-1); /* Clear & prepare request queue */ node_reset (&requests); /* Create pipe lookup table */ pipes = sym_create_table (); /* Signal okay to caller that we initialised okay */ return (0); }
Filename: smtsimu.h
Package: Libero SMT 2.x
Written: 1996/11/28 iMatix SMT kernel team smt@imatix.com
Revised: 1997/12/15
Copyright: Copyright (c) 1991-2000 iMatix Corporation
Redefines socket functions to pass via smtsimu. Include smtsimu.h after smtlib.h when you want to simulate network delays. You do not need to change your program in any other way.
smtsimu.h defines these symbols, possibly conditionally:
Symbol: | Defined as: |
---|---|
_SMTSIMU_INCLUDED | TRUE |
smtsock_init | smtsimu_smtsock_init |
smtsock_trace | smtsimu_smtsock_trace |
sock_init | smtsimu_sock_init |
write_UDP | smtsimu_write_UDP |
#include "smtsimu.h" int smtsimu_init (void)
Initialises the SMT network delay simulation agent. Returns 0 if okay, -1 if there was an error. You do not need to call this function explicitly. To use SMTSIMU, include "smtsimu.h" in your program after "smtlib.h". This redefines the standard socket functions to go through SMTSIMU. Sends all errors to the SMTOPER agent.
{ AGENT *agent; /* Handle for our agent */ THREAD *thread; /* Handle to console thread */ # include "smtsimu.i" /* Include dialog interpreter */ /* Method name Event value Priority */ /* Shutdown event comes from Kernel */ method declare (agent, "SHUTDOWN", shutdown_event, SMT_PRIORITY_MAX); /* Public methods supported by this agent */ method declare (agent, "TIME_ALARM", alarm_event, 0); method declare (agent, "TIME_ERROR", error_event, 0); /* Ensure that operator console is running, else start it up */ smtoper init (); if ((thread = thread lookup (SMT_OPERATOR, "")) != NULL) operq = thread-> queue-> qid; else return (-1); /* Ensure that timer agent is running, else start it up */ smttime init (); if ((thread = thread lookup (SMT_TIMER, "")) != NULL) timeq = thread-> queue-> qid; else return (-1); /* Create initial thread */ if ((thread = thread create (AGENT_NAME, "")) != NULL) simuq = thread-> queue-> qid; else return (-1); /* Initialise static variables */ last_id = 0; /* Signal okay to caller that we initialised okay */ return (0); }
#include "smtsimu.h" int smtsimu_sock_init (void)
Replaces the normal sock_init() function call. Initialises the socket interface as usual, and then initialises the network delay simulation agent. Returns SOCKET_ERROR if there was an error, else returns 0.
{ /* Really start sockets */ if (sock_init ()) return ((int) SOCKET_ERROR); /* Now initialise the SMTSIMU agent, and return 0 or SOCKET_ERROR */ if (smtsimu init ()) return ((int) SOCKET_ERROR); else return (0); }
#include "smtsimu.h" int smtsimu_smtsock_init (void)
Replaces the normal smtsock init() function call. Calls the SMTSOCK agent to initialise, as usual, then initialises the network delay simulation agent. Returns -1 if there was an error, else 0.
{ /* Initialise SMTSOCK agent */ if (smtsock init ()) return (-1); /* Now initialise the SMTSIMU agent */ return (smtsimu init ()); }
#include "smtsimu.h" void smtsimu_smtsock_trace (Bool trace_value)
Replaces the normal smtsock trace() function call. Calls the SMTSOCK agent to enable/disable tracing, then uses the same trace flag for the delay simulation. Tracing is sent to the operator console.
{ trace_flag = trace_value; smtsock trace (trace_value); }
#include "smtsimu.h" int smtsimu_write_UDP ( sock_t handle, /* Socket handle */ void *buffer, /* Buffer containing data */ size_t length, /* Amount of data to write */ struct sockaddr_in *sin /* Address to send to, or null */ )
Replaces the normal write_UDP() function call. The data is sent, after a small but significant delay. Returns the length of the data packet. There is no guarantee that the packet will be sent and received successfully. Returns SOCKET_ERROR if there was an error, such as insufficient memory.
{ SOCKREQ *request; /* New socket transfer request */ dbyte req_size; /* Size of transfer request */ byte *msg_body; /* Event to send via delay timer */ int msg_size; /* Size of event body */ qbyte csecs = 100; /* Delay time in csecs */ /* Allocate a new transfer request */ req_size = sizeof (SOCKREQ) + length; if ((request = mem_alloc (req_size)) == NULL) { sendfmt (&operq, "ERROR", "smtsimu: out of memory"); return ((int) SOCKET_ERROR); } request-> id = ++last_id; request-> handle = handle; request-> length = length; request-> sin = *sin; memcpy ((byte *) request + sizeof (SOCKREQ), buffer, length); /* Calculate size required for message body */ msg_size = exdr_write ( NULL, /* No body - just get size */ SMT_TIME_ALARM, /* Format event for smttime */ 0, csecs, /* Delay time */ req_size, request); /* Request info */ /* Now allocate and format the buffer */ msg_body = mem_alloc (msg_size); msg_size = exdr_write ( msg_body, /* Event body */ SMT_TIME_ALARM, /* Format event for smttime */ 0, csecs, /* Delay time */ req_size, request); /* Request info */ event send ( &timeq, /* Send to specified queue */ &simuq, /* No queue for reply */ "ALARM", /* Name of event to send */ msg_body, /* Event body contents */ msg_size, /* Event body size */ NULL, NULL, NULL, /* No response events */ 0); /* No timeout */ mem_free (msg_body); mem_free (request); if (trace_flag) sendfmt (&operq, "INFO", "smtsimu: packet %u delayed by %u csecs", last_id, csecs); return (length); }
Filename: smtupm.h
Package: Libero SMT 2.x
Written: 1996/09/10 iMatix SMT kernel team smt@imatix.com
Revised: 1997/09/08
Copyright: Copyright (c) 1991-2000 iMatix Corporation
Defines constants, messages, and prototypes for the UPM (unattended process monitor) agents.
smtupm.h defines these symbols, possibly conditionally:
Symbol: | Defined as: |
---|---|
SMT_UPMC | "smtupmc" /* Name of UPM client agent */ |
SMT_UPMD | "smtupmd" /* Name of UPM daemon agent */ |
SMT_UPM_MESSAGE | "ds" |
SMT_UPM_PORT | "5050" /* Port for UPM connection */ |
UPM_ERROR | 201 /* Reply fatal error */ |
UPM_HALT | 101 /* Halt UPM and processes */ |
UPM_HALTING | 202 /* Reply halting UPM */ |
UPM_LIST | 100 /* List processes */ |
UPM_READY | 200 /* Reply ready for command */ |
UPM_START | 102 /* Start-up a process */ |
UPM_START_ERROR | 209 /* Reply task could not be started */ |
UPM_START_OK | 208 /* Reply task started ok */ |
UPM_STATUS | 104 /* Enquire process status */ |
UPM_STOP | 103 /* Shut-down a process */ |
UPM_STOP_ERROR | 211 /* Reply task could not be stopped */ |
UPM_STOP_OK | 210 /* Reply task stopped ok */ |
UPM_TASK_ID | 203 /* Reply task name */ |
UPM_TASK_NF | 205 /* Reply unknown task */ |
UPM_TASK_OK | 204 /* Reply task status */ |
UPM_TASK_RUNNING | 206 /* Reply task is running */ |
UPM_TASK_STOPPED | 207 /* Reply task is not running */ |
_SMTUPM_INCLUDED | TRUE |
#include "smtupm.h" int smtupmc_init (char *p_args, char *p_portname)
Initialises the SMT UPM client agent. Returns 0 if okay, -1 if there was an error. The UPM client agent allows you to pass commands to the UPM daemon. See the SMT documentation for details.
{ AGENT *agent; /* Handle for our agent */ THREAD *thread; /* Handle to console thread */ # include "smtupmc.i" /* Include dialog interpreter */ /* Shutdown event comes from Kernel */ method declare (agent, "SHUTDOWN", shutdown_event, SMT_PRIORITY_MAX); /* Reply events from socket agent */ method declare (agent, "SOCK_INPUT_OK", ok_event, 0); method declare (agent, "SOCK_OUTPUT_OK", ok_event, 0); method declare (agent, "SOCK_READ_OK", ok_event, 0); method declare (agent, "SOCK_WRITE_OK", ok_event, 0); method declare (agent, "SOCK_CLOSED", sock_closed_event, 0); method declare (agent, "SOCK_ERROR", sock_error_event, 0); method declare (agent, "SOCK_TIMEOUT", sock_error_event, 0); /* Reply events from transfer agent */ method declare (agent, "TRAN_GETF_OK", ok_event, 0); method declare (agent, "TRAN_PUTF_OK", SMT_NULL_EVENT, 0); method declare (agent, "TRAN_CLOSED", sock_closed_event, 0); method declare (agent, "TRAN_ERROR", sock_error_event, 0); /* Create initial, unnamed thread */ thread create (AGENT_NAME, ""); /* Ensure that socket agent is running, else start it up */ smtsock init (); if ((thread = thread lookup (SMT_SOCKET, "")) != NULL) sockq = thread-> queue-> qid; else return (-1); /* Ensure that transfer agent is running, else start it up */ smttran init (); if ((thread = thread lookup (SMT_TRANSFER, "")) != NULL) tranq = thread-> queue-> qid; else return (-1); /* Signal okay to caller that we initialised okay */ args = p_args; /* Get command-line arguments */ portname = p_portname; return (0); }
| << | < | > | >> | Copyright © 1996-99 iMatix Corporation |