| iMatix home page
| << | < | > | >>
SMT Logo SMT
Version 2.81

The SMT Kernel

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.  

Table of Contents

  • Introduction
  • Origins
  • Classic Multithreading Environments
  • The History Of Libero Multithreading
  • Differences with Classic Multithreading
  • Why Use Multithreading?
  • What You Should Know
  • The SMT Kernel Architecture
  • Structure Of An SMT Application
  • Floating Event Queues
  • Routers
  • Internal and External Events
  • Event Management
  • Priorities and Scheduling
  • Native Programs Vs. Foreign Programs
  • The SMT Kernel Meta-State-Machine
  • Using The SMT Kernel API
  • SMT Kernel Functions
  • Initialisation and Termination
  • Creating A Thread
  • Sending and Receiving Events
  • Sending An Initial Event To A Thread
  • Event Lifespan and Acknowledgment
  • Using Priorities
  • Writing A Native SMT Program
  • Initialising An agent
  • The Thread Context Block
  • Choosing Event Names
  • Mechanics Of Event Delivery
  • Waiting For An External Event
  • Sending Structured Event Data
  • Sending Events Within An Agent
  • Ignoring External Events
  • Non-Blocking File Access
  • Real-time Programming
  • Using Semaphores
  • Replacing The Standard Agents
  • Using The SMT API In A Foreign Program
  • Writing A Stub Program
  • Managing A Floating Event Queue
  • Executing The Application
  • Standard SMT Agents
  • The Logging Agent - SMTLOG
  • The Operator Console Agent - SMTOPER
  • The Timer Agent - SMTTIME
  • The Time Slot Agent - SMTSLOT
  • The Socket I/O Agent - SMTSOCK
  • The Transfer Agent - SMTTRAN
  • The TCP ECHO Agent - SMTECHO
  • The HTTP Agent - SMTHTTP
  • The Network Delay Simulation Agent - SMTSIMU
  • SMT API Quick Reference
  • The Unattended Process Monitor (UPM) Tool
  • Overview
  • Technical Details
  • Using The UPM Tool
  • UPM Trouble Shooting
  • Installing The SMT Kernel
  • Availability and Distribution
  • Installation for UNIX Systems
  • Installation for Digital VMS Systems
  • Installation for Windows
  • System-Specific Concerns
  • Windows
  • The SMT License Agreement
  • SMT Kernel API functions
  • smt_init
  • smt_term
  • smt_exec_full
  • smt_exec_step
  • smt_active
  • smt_set_console
  • smt_set_timer
  • smt_atexit
  • smt_shutdown
  • agent_declare
  • agent_lookup
  • agent_destroy
  • method_declare
  • method_lookup
  • method_destroy
  • queue_create
  • queue_lookup
  • queue_destroy
  • queue_deliver
  • queue_flush
  • event_send
  • event_accept
  • event_reject
  • event_expire
  • event_discard
  • event_iterate
  • event_destroy
  • event_wait
  • thread_create
  • thread_lookup
  • thread_destroy
  • semaph_create
  • semaph_lookup
  • semaph_destroy
  • semaph_wait
  • semaph_signal
  • lazy_creat
  • lazy_creat_text
  • lazy_open
  • lazy_open_text
  • lazy_read
  • lazy_write
  • lazy_close
  • senderr
  • sendfmt
  • raise_exception
  • recycle_module
  • smt_set_step
  • smt_crash_report
  • The Standard SMT Agents
  • smtauth_init
  • smtecho_init
  • smthttp_init
  • smtftpc_init
  • smtftpd_init
  • smtrdns_init
  • smtlog_init
  • smtoper_init
  • smtslot_init
  • smtsock_init
  • smtsock_trace
  • smttime_init
  • smttran_init
  • The Network Delay Simulator
  • smtsimu_init
  • smtsimu_sock_init
  • smtsimu_smtsock_init
  • smtsimu_smtsock_trace
  • smtsimu_write_UDP
  • The Unattended Process Monitor
  • smtupmc_init
  • Introduction

    Origins

    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:

    1. Mulithreading - a cleaner and more efficient approach for certain types of problems than alternatives such as multiprocessing (forking) or iteration.
    2. Simplicity - using the Libero method to simplify an otherwise complex multithreaded model.
    3. Portability - so that a SMT application runs on UNIX, Windows 95, Windows NT, and Digital VMS with similar functionality.

    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.

    Classic Multithreading Environments

    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.

    The History Of Libero Multithreading

    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:

    Differences with Classic Multithreading

    The main differences between SMT and 'classic' multithreading are:

    We note some other points of interest:

    Why Use Multithreading?

    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.

    What You Should Know

    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:

    The SFL socket functions provide the main abstraction layer; for instance you will not need to consider system-specific issues such as the WINSOCK interface.

    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.

    The SMT Kernel Architecture

    Structure Of An 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
    

    Floating Event Queues

    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.

    Routers

    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.

    Internal and External Events

    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.

    Event Management

    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.

    Priorities and Scheduling

    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.

    Native Programs Vs. Foreign Programs

    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.

    The SMT Kernel Meta-State-Machine

    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.

    Using The SMT Kernel API

    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.

    SMT Kernel Functions

    This is the complete set of functions supported by the SMT kernel:

    smt init()
    Initialise the SMT kernel.
    smt term()
    Shut-down the SMT kernel.
    smt exec full()
    Execute the SMT application until halted.
    smt exec step()
    Execute just next scheduled thread.
    smt active()
    Check if application has halted.
    smt set console()
    Specify an agent to act as console.
    smt set timer()
    Specify an agent to act as timer.
    smt atexit()
    Define a termination function.
    smt shutdown()
    Halt the application prematurely.
    agent declare()
    Define a new agent.
    agent lookup()
    Check if a specific agent is defined.
    agent destroy()
    Remove an agent from the SMT kernel tables.
    method declare()
    Define a method for an agent.
    method lookup()
    Check if a specific method is defined.
    method destroy()
    Remove a method from the SMT kernel tables.
    queue create()
    Define an event queue for an agent, or a floating queue.
    queue lookup()
    Check if an event specific queue is defined.
    queue destroy()
    Remove an event queue from the SMT kernel tables.
    queue flush()
    Expire out-of-date events in the event queue.
    event send()
    Send an event to some event queue.
    event accept()
    Accept the next event from an event queue.
    event reject()
    Reject the next event from an event queue.
    event expire()
    Expire the next event from an event queue.
    event discard()
    Discard the next event from an event queue.
    event iterate()
    Find the next event in an event queue.
    event destroy()
    Destroy a specific event.
    event wait()
    Wait explicitly for an external event.
    thread create()
    Define a thread for an agent, maybe create a queue.
    thread lookup()
    Check if a specific thread is defined.
    thread destroy()
    Remove a thread from the SMT kernel tables.
    semaph create()
    Create a new semaphore.
    semaph lookup()
    Check if a specific semaphore is defined.
    semaph destroy()
    Remove a semaphore from the SMT kernel tables.
    semaph wait()
    When the semaphore value is > 0, decrement it.
    semaph signal()
    Add 1 to the semaphore value.
    lazy creat()
    Create a file, without blocking.
    lazy open()
    Open a file, without blocking.
    lazy read()
    Read from a file, without blocking.
    lazy write()
    Write to a file, without blocking.
    lazy close()
    Close a file, without blocking.
    senderr()
    Send the current strerror (errno) to some queue.
    sendfmt()
    Format a text using printf conventions and send to some queue.
    raise exception()
    Raise an exception (for dialog programs only).
    recycle module()
    Repeat execution of the current dialog module.

    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).

    Initialisation and Termination

    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.

    Creating A Thread

    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);
          }
    

    Sending and Receiving Events

    These are the API functions that you can use to send an event:

    event send()
    Send an event to some event queue.
    senderr()
    Send the current strerror (errno) to some queue.
    sendfmt()
    Format a text using printf conventions and send to some queue.

    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.

    Sending An Initial Event To A Thread

    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.

    Event Lifespan and Acknowledgment

    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.

    Using Priorities

    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.

    Writing A Native SMT Program

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

    Initialising An 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.

    The Thread Context Block

    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);
              }
          }
    }
    

    Choosing Event Names

    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.

    Mechanics Of Event Delivery

    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).

    Waiting For An External 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.

    Sending Structured Event Data

    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.

    Sending Events Within An Agent

    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,

    Ignoring External Events

    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);
    

    Non-Blocking File Access

    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.

    Real-time Programming

    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);
              }
          }
    }
    

    Using Semaphores

    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:

    Replacing The Standard Agents

    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.

    Using The SMT API In A Foreign Program

    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.

    Writing A Stub Program

    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.

    Managing A Floating Event Queue

    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:

    event accept()
    Accept the next event from an event queue.
    event reject()
    Reject the next event from an event queue.
    event expire()
    Expire the next event from an event queue.
    event discard()
    Discard the next event from an event queue.
    event iterate()
    Find the next event in an event queue.
    event destroy()
    Destroy a specific event.

    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;
          }
    

    Executing The Application

    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.

    Standard SMT Agents

    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:

     

    The Logging Agent - SMTLOG

    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);
    

    The CYCLE Method

    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.

    The OPEN Method

    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.

    The APPEND Method

    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.

    The PUT Method

    Takes the event body as a string, prefixes the date and time, and writes it to the log file.

    The PLAIN Method

    Disables time-stamping of the logged data. Use this when you do not want the default SMTLOG time-stamping.

    The STAMP Method

    Enables time-stamping of the logged data.

    The CLOSE Method

    Closes the log file and destroys the thread used to manage it. You normally send this event when you end your program.

    Example Of Use

    The SMTECHO agent shows an example of using the Logging agent.

    SMTLOG Implementation

    The hypertext view of SMTLOG's dialog and source code may help to understand how SMTLOG works.  

    The Operator Console Agent - SMTOPER

    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 ERROR Method

    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 WARNING Method

    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 INFO Method

    The event body contains a string. This string is handled as a information message. For example:

        sendfmt (&console, "INFO", "Connection from %s", system);
    

    The LOG Method

    The event body contains a string that specifies the name of a SMTLOG thread. All operator output is sent to this thread.

    SMTOPER Implementation

    The hypertext view of SMTOPER's dialog and source code may help to understand how SMTOPER works.  

    The Timer Agent - SMTTIME

    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);
    

    The ALARM Method

    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):

    SMTTIME replies to an ALARM event with one of these events:

    The ALARM method implicitly does a FLUSH before proceeding.

    The WAKEUP Method

    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):

    SMTTIME replies to a WAKEUP event with one of these events:

    The WAKEUP method implicitly does a FLUSH before proceeding.

    The CLOCK Method

    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):

    SMTTIME replies to a CLOCK event with one of these events:

    The CLOCK method implicitly does a FLUSH before proceeding.

    The FLUSH Method

    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.

    Example Of Use

    The SMTTST1 test agent shows an example of using the timer agent.

    SMTTIME Implementation

    The hypertext view of SMTTIME's dialog and source code may help to understand how SMTTIME works.  

    The Time Slot Agent - SMTSLOT

    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);
    

    The SPECIFY Method

    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):

    SMTTRAN replies to a SPECIFY event with a SLOT_ERROR event if there was an error, else it does not reply.

    The ON Method

    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.

    The OFF Method

    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.

    Example Of Use

    The SMTTST2 agent shows an example of using the time slot agent.

    SMTSLOT Implementation

    The hypertext view of SMTSLOT's dialog and source code may help to understand how SMTSLOT works.  

    The Socket I/O Agent - SMTSOCK

    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);
    

    The READ Method

    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):

    SMTSOCK replies to a READ event with one of these events:

    The READR Method

    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.

    The WRITE Method

    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:

    SMTSOCK replies to a WRITE event with one of these events:

    The INPUT Method

    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:

    SMTSOCK replies to an INPUT event with one of these events:

    The INPUTR Method

    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.

    The OUTPUT Method

    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:

    SMTSOCK replies to an OUTPUT event with one of these events:

    The CONNECT Method

    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:

    SMTSOCK replies to a CONNECT event with one of these events:

    The FLUSH Method

    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:

    SMTSOCK does not reply to a FLUSH event.

    Example Of Use

    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.

    Notes and Comments

    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.

    SMTSOCK Implementation

    The hypertext view of SMTSOCK's dialog and source code may help to understand how SMTSOCK works.  

    The Transfer Agent - SMTTRAN

    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);
    

    The PUT_BLOCK Method

    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):

    SMTTRAN replies to a PUT_BLOCK event with one of these events:

    The GET_BLOCK Method

    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):

    SMTTRAN replies to a GET_BLOCK event with one of these events:

    The PUT_FILE Method

    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):

    SMTTRAN replies to a PUT_FILE event with one of these events:

    The GET_FILE Method

    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):

    SMTTRAN replies to a GET_FILE event with one of these events:

    The COMMIT Method

    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:

    Example Of Use

    The SMTHTTP agent uses the transfer agent to send files.

    SMTTRAN Implementation

    The hypertext view of SMTTRAN's dialog and source code may help to understand how SMTTRAN works.  

    The TCP ECHO Agent - SMTECHO

    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.

    SMTECHO Implementation

    The hypertext view of SMTECHO's dialog and source code may help to understand how SMTECHO works.  

    The HTTP Agent - SMTHTTP

    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.

    SMTHTTP Implementation

    The hypertext view of SMTHTTP's dialog and source code may help to understand how SMTHTTP works.  

    The Network Delay Simulation Agent - SMTSIMU

    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).

    SMTSIMU Implementation

    The hypertext view of SMTSIMU's dialog and source code may help to understand how SMTSIMU works.

    SMT API Quick Reference

    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 Unattended Process Monitor (UPM) Tool

    Overview

    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.

    Technical Details

    The UPM tool consists of four programs:

    1. upmd is a server program that runs permanently.
    2. upmc is a client program that provides a command-level interface.
    3. smtupmd is an agent that handles the UPM server protocol. The upmd program is basically a wrapper around this agent.
    4. smtupmc is an agent that handles the UPM client protocol. The upmc program is basically a wrapper around this agent.

    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:

    A time specification can be:

    If no TIMES file is provided, you can (or rather, must) start and stop the tasks manually.

    Using The UPM Tool

    1. Define the upm.ini and task .ini files correctly. Make sure that the devices exist and are writable.
    2. For testing, run the upmd program in a shell window. For real life use, place into the system startup procedure (inittab on a UNIX system). You can run upmd in the background like this (assuming a UNIX system):
      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 device you specified in the task .ini file is not accessible to the upm daemon. You will get an error message in the daemon log file, or in the shell window where you ran upmd.
      • The commands you specified in the task .ini file are not found or not accessible. You will get an error message in the daemon log file, or in the shell window where you ran upmd.

      UPM Implementation

      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.  

      Installing The SMT Kernel

      Availability and Distribution

      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.

      Getting The Source Archive

      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.

      Getting The The Documentation Kit

      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.

      Installation for UNIX Systems

      To install the SMT kernel on a UNIX system you need to:

      1. Install the SFL package: you will want to place libsfl.a and sfl.h in the current directory.
      2. Download the SMT source archive and decompress it.
      3. Run the 'build' script to compile the SMT sources and build the libsmt.a archive file and various executables.

      Decompressing The Source Archive

      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.

      Compiling The SMT Sources

      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.

      Installation for Digital VMS Systems

      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:

      1. Install the SFL package: you will want to place libsfl.olb and sfl.h in the current directory.
      2. Download the source archive and decompress it.
      3. Run the 'build.txt' command file to build the libsmt.olb library file and the various executables.

      Decompressing The Source Archive

      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.

      Compiling The SMT Sources

      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.

      Installation for Windows

      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.

      System-Specific Concerns

      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.

      Windows

      SMTSOCK and the select() call

      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.  

      The SMT License Agreement

      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.

      Statement Of Copyright

      The Product is, and remains, Copyright © 1991-2000 iMatix Corporation, with exception of specific copyrights as noted in the individual source files.

      Conditions Of Use

      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:

      1. Provide the source code for Product modules that you use, or
      2. Make your product freely available according to a license similar to the GNU General Public License, or the Perl Artistic License, or
      3. Add this phrase to the documentation for your product: "This product uses parts of the iMatix SMT kernel, Copyright © 1991-2000 iMatix Corporation <http://www.imatix.com>".

      Rights Of Usage

      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."

      Rights Of Distribution

      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.

      Disclaimer Of Warranty

      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.

      SMT Kernel API functions

      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

      Synopsis

      Defines the functions and objects used in the SMT kernel API.

      List of Functions

      List of Symbol Definitions

      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

      List of Type Definitions

      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
       

      smt_init

      #include "smtlib.h"
      int
      smt_init (void)
      

      Synopsis

      Initialises the SMT. Returns 0 if there were no errors. Else returns -1, after seting smt_errno to one of these values:
      SMT OUTOFMEMORY Not enough heap memory left

      Source Code - (smtlib.c)

      {
          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);
      }
      
       

      smt_term

      #include "smtlib.h"
      int
      smt_term (void)
      

      Synopsis

      Shuts-down the SMT. Destroys all agents, methods, queues, events, threads. Returns 0 if there were no errors, otherwise returns -1 and sets smt_errno to one of these values:
      SMT NOTREADY smt_init() was not called, or failed
      SMT OUTOFMEMORY Not enough heap memory left

      Source Code - (smtlib.c)

      {
          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);
      }
      
       

      smt_exec_full

      #include "smtlib.h"
      int
      smt_exec_full (void)
      

      Synopsis

      Executes the current set of threads until there are no more active threads left, or events to deliver. At this point we say that the application has 'halted'. Returns 0.

      Source Code - (smtlib.c)

      {
      #if (defined (SMT_TRACE))
          trace ("smt_exec_full");
      #endif
          while (smt exec step ());
          return (0);
      }
      
       

      smt_exec_step

      #include "smtlib.h"
      Bool
      smt_exec_step (void)
      

      Synopsis

      Rebuilds the active list, delivering any events possible, then executes the first thread in the active list. Returns TRUE so long as there is something happening; returns FALSE when the application has 'halted'.

      Source Code - (smtlib.c)

      {
      #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);
      }
      
       

      smt_active

      #include "smtlib.h"
      Bool
      smt_active (void)
      

      Synopsis

      Returns TRUE if the SMT has active threads, else false.

      Source Code - (smtlib.c)

      {
      #if (defined (SMT_TRACE))
          trace ("smt_active: %d", (active_threads.right != &active_threads));
      #endif
          return (active_threads.right != &active_threads);
      }
      
       

      smt_set_console

      #include "smtlib.h"
      void
      smt_set_console (const QID *qid)
      

      Synopsis

      Tells the SMT kernel to send error events to the specified console queue. There can be just one console queue. If you do not specify a console queue, error events are discarded.

      Source Code - (smtlib.c)

      {
      #if (defined (SMT_TRACE))
          trace ("smt_set_console");
      #endif
          console = *qid;
      }
      
       

      smt_set_timer

      #include "smtlib.h"
      void
      smt_set_timer (const QID *qid)
      

      Synopsis

      Tells the SMT kernel to send alarm events to the specified timer queue. There can be just one timer queue. If you do not specify a timer queue, timer events are discarded.

      Source Code - (smtlib.c)

      {
      #if (defined (SMT_TRACE))
          trace ("smt_set_timer");
      #endif
          timer = *qid;
      }
      
       

      smt_atexit

      #include "smtlib.h"
      int
      smt_atexit (function handler)
      

      Synopsis

      Registers a termination function. The function is defined as a void function without arguments. The termination functions are called in the order that they are declared. Multiple instances of the same function are ignored. Returns 0 if okay, -1 if there was an error. In the case of an error, sets smt_errno to one of:
      SMT NOTREADY smt_init() was not called, or failed
      SMT OUTOFMEMORY Not enough heap memory left
      The kernel executes termination functions before destroying agents and other objects. Thus, termination functions can access the object symbol table ('lookup' functions), but not send or receive events.

      Source Code - (smtlib.c)

      {
          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);
      }
      
       

      smt_shutdown

      #include "smtlib.h"
      void
      smt_shutdown (void)
      

      Synopsis

      Ends the current SMT application. Use this if you detect a fatal error in a thread. Sends a SHUTDOWN event to every thread, so halting the application.

      Source Code - (smtlib.c)

      {
          signal_raised = TRUE;               /*  Signal user shutdown signal      */
          signal_value  = SMT_SIGNAL_USER;    /*  Kernel takes it from here        */
      }
      
       

      agent_declare

      #include "smtlib.h"
      AGENT *
      agent_declare (
          const char *agent_name              /*  Name of agent to declare         */
      )
      

      Synopsis

      Declares a new agent. Typically you'll do this when you are initialising a agent. You must declare the agent before you can create queues, threads, or methods for that agent. The agent name is an arbitrary text, unique within the application. Returns the address of the created AGENT block. If there was an error, returns NULL and sets smt_errno to one of these values:
      SMT NOTREADY smt_init() was not called, or failed
      SMT OUTOFMEMORY Not enough heap memory left
      SMT AGENTEXISTS A agent with this name is already declared
      Agents and threads are stored in the dictionary as follows: a name is built of three parts: s~agent~[thread]. This name is the key into the dictionary and lets us find a agent, or thread by name. Then, all agents are chained into a linked list that is attached to the agents list. Each agent has a sublist of queues, and each queue has a sublist of threads. Each thread has a pointer to the parent queue respectively. This cross-linking lets us browse the list of agents/threads from any point. Names are always stored in lower-case. Sets agent priority to SMT_PRIORITY_NORMAL; sets router flag to FALSE, and max_threads to 0.

      Source Code - (smtlib.c)

      {
          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);
      }
      
       

      agent_lookup

      #include "smtlib.h"
      AGENT *
      agent_lookup (
          const char *agent_name              /*  Name of agent to look for        */
      )
      

      Synopsis

      Checks whether a specific agent exists; returns the address of the agent information block, or NULL if there was an error, setting smt_errno to one of these values:
      SMT NOTREADY smt_init() was not called, or failed
      SMT NOSUCHAGENT Specified agent was not declared

      Source Code - (smtlib.c)

      {
          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          */
      }
      
       

      agent_destroy

      #include "smtlib.h"
      int
      agent_destroy (
          AGENT *agent                        /*  Agent to destroy; null = all     */
      )
      

      Synopsis

      Destroys the agent. Returns 0 when completed. The agent argument points to an agent block, or is null. If null, all agents are destroyed. Returns 0 when completed normally, else returns -1 and sets smt_errno to one of these values:
      SMT NOTREADY smt_init() was not called, or failed

      Source Code - (smtlib.c)

      {
      #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);
      }
      
       

      method_declare

      #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    */
      )
      

      Synopsis

      Declares a new method for an agent. All external events that an agent is prepared to method are declared as methods. The agent must already have been declared using agent declare(). The method name is an arbitrary text, unique within the agent. The event number is the number of the event assigned by the dialog code generator; if you specify the event number as SMT_NULL_EVENT, the method is ignored. This discards any incoming events with that name. The priority may be 0 (meaning normal priority), SMT_PRIORITY_LOW, SMT_PRIORITY_HIGH, or another suitable value. Returns the address of the created METHOD block. If there was an error, returns NULL and sets smt_errno to one of:
      SMT NOTREADY smt_init() was not called, or failed
      SMT OUTOFMEMORY Not enough heap memory left
      SMT NOSUCHAGENT Specified agent was not declared
      SMT METHODEXISTS Method is already declared

      Source Code - (smtlib.c)

      {
          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);
      }
      
       

      method_lookup

      #include "smtlib.h"
      METHOD *
      method_lookup (
          const AGENT *agent,                 /*  Agent to look at                 */
          const char *method_name             /*  Name of method to look for       */
      )
      

      Synopsis

      Checks whether a specific method exists; returns the address of the method information block, or NULL if there was an error, setting smt_errno to one of these values:
      SMT NOTREADY smt_init() was not called, or failed
      SMT NOSUCHMETHOD Specified method was not declared

      Source Code - (smtlib.c)

      {
          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         */
      }
      
       

      method_destroy

      #include "smtlib.h"
      int
      method_destroy (
          METHOD *method                      /*  Method to destroy                */
      )
      

      Synopsis

      Destroys the method. Returns 0 when completed. In case of error, returns -1 and sets smt_errno to one of these values:
      SMT NOTREADY smt_init() was not called, or failed

      Source Code - (smtlib.c)

      {
      #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);
      }
      
       

      queue_create

      #include "smtlib.h"
      QUEUE *
      queue_create (
          AGENT *agent,                       /*  Parent agent block, or null      */
          int    max_events                   /*  Max. events; 0 = no limit        */
      )
      

      Synopsis

      Creates an event queue, and returns a handle to the created queue. Event queues are unnamed but attached to a agent within an agent. Queue can also be 'floating', i.e. not attached to a agent. This is useful for foreign programs. If you specify a agent, the queue is attached to that agent. If the agent argument is null, the queue is left floating. You always refer to a queue using its address (within the owning process) or QID handle (within any process). The current implementation uses a linked list in heap memory, so QID handles are only valid within the process. Future implementations may use other types of shared memory including connections across a communications protocol. Returns a pointer to the created QUEUE block. In case of error, returns null and sets smt_errno to one of these values:
      SMT NOTREADY smt_init() was not called, or failed
      SMT OUTOFMEMORY Not enough heap memory left

      Source Code - (smtlib.c)

      {
          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);
      }
      
       

      queue_lookup

      #include "smtlib.h"
      QUEUE *
      queue_lookup (
          const QID *qid                      /*  Queue to find                    */
      )
      

      Synopsis

      Returns a pointer to the queue information block for the specified queue handle. You can change the max_events field, but you should not change the other fields in the queue information block. In case of error, returns NULL and sets smt_errno to one of these values:
      SMT NOTREADY smt_init() was not called, or failed
      SMT NOSUCHQUEUE The event queue is not defined

      Source Code - (smtlib.c)

      {
          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);
            }
      }
      
       

      queue_destroy

      #include "smtlib.h"
      int
      queue_destroy (
          QUEUE *queue                        /*  Queue to destroy                 */
      )
      

      Synopsis

      Deletes any events in the event queue and then destroys the queue and all its threads. Returns 0 when successfully completed. In case of error, returns -1 and sets smt_errno to one of these values:
      SMT NOTREADY smt_init() was not called, or failed
      SMT NOSUCHQUEUE The event queue is not defined

      Source Code - (smtlib.c)

      {
      #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);
      }
      
       

      queue_deliver

      #include "smtlib.h"
      int
      queue_deliver (
          QUEUE  *queue,                      /*  Queue containing events          */
          THREAD *thread)                     /*  Agent thread to receive event    */
      

      Synopsis

      Tries to deliver an event from a queue to an agent thread. The event, if deliverable, is removed from the queue and stored in the thread-> event block. Returns 1 if an event was delivered, else 0.

      Source Code - (smtlib.c)

      {
          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         */
      }
      
       

      queue_flush

      #include "smtlib.h"
      int
      queue_flush (
          QUEUE *queue                        /*  Queue to flush                   */
      )
      

      Synopsis

      Expires any out-of-date events in the queue: calls event expire() for each event who's timeout has passed. Returns the number of events expired. In case of error, returns -1 and sets smt_errno to one of these values:
      SMT NOTREADY smt_init() was not called, or failed
      SMT NOSUCHQUEUE The event queue is not defined

      Source Code - (smtlib.c)

      {
          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);
      }
      
       

      event_send

      #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     */
      )
      

      Synopsis

      Sends an event to an event queue. The event body - if not null or empty - is always copied, crossing memory boundaries. The accept_event, reject_event, and timeout_events are sent back to the sender event queue as required. These events may be specified as null or empty strings. The timeout may be 0 for none, or a value in milliseconds. Returns 0 when successfully completed. The current implementation provides timeouts accurate to a second only. The target queue may be null, in which case the event is ignored, and not sent. This lets you reply to an event without always checking that the reply queue was specified. The event name and reply event names are always stored in uppercase. In case of error, returns -1 and sets smt_errno to one of these values:
      SMT NOTREADY smt_init() was not called, or failed
      SMT OUTOFMEMORY Not enough heap memory left
      SMT NOSUCHQUEUE The target event queue is not known
      SMT QUEUEISFULL The target event queue is full

      Source Code - (smtlib.c)

      {
          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                        */
      }
      
       

      event_accept

      #include "smtlib.h"
      EVENT *
      event_accept (
          QUEUE *queue,                       /*  Queue to take event from         */
          EVENT *event                        /*  Event, or null for first         */
      )
      

      Synopsis

      Takes an event off an event queue, and sends an 'accept' reply to the original sender, if required. If the specified event is null, takes the first (oldest) event in the queue. Otherwise takes the specified event. Returns the address of the event. You should call event destroy() when you have finished processing the event. In case of error, returns NULL and sets smt_errno to one of these values:
      SMT NOTREADY smt_init() was not called, or failed
      SMT QUEUEISEMPTY The queue was empty

      Source Code - (smtlib.c)

      {
      #if (defined (SMT_TRACE))
          trace ("event_accept");
      #endif
          ASSERT (queue);
          if (!smt_alive)                     /*  If SMT API was not correctly     */
            {                                 /*    initialised, forget it         */
              smt_errno = SMT_NOTREADY;
              return (NULL);
            }
      
          if ((event = event_locate (queue, event)) == NULL)
              return (NULL);
      
          /*  Reply to original sender if necessary                                */
          if (event-> accept_event)
              event send (
                  &event-> sender,            /*  Send back to original sender     */
                  NULL,                       /*  No queue for reply               */
                  event-> accept_event,       /*  Name of event to send            */
                  NULL, 0,                    /*  Body is empty, size is 0         */
                  NULL, NULL, NULL,           /*  No response events               */
                  0);                         /*  No timeout                       */
      
          if (event-> timeout)
              queue-> timed_events++;         /*  One less timed event, maybe      */
          queue-> cur_events--;               /*  One less event in queue          */
      
          node_unlink (event);                /*  Unlink from queue                */
          return (event);
      }
      
       

      event_reject

      #include "smtlib.h"
      int
      event_reject (
          QUEUE *queue,                       /*  Queue to take event from         */
          EVENT *event                        /*  Event, or null for first         */
      )
      

      Synopsis

      Rejects the next event or a specific event on an event queue. Sends a 'rejected' event to the original sender if required, then destroys the event. You can use this to reject one specific message, or in a loop to cancel the entire queue. Returns 0 if the event was successfully rejected, else returns -1 and sets smt_errno to one of:
      SMT NOTREADY smt_init() was not called, or failed
      SMT QUEUEISEMPTY The queue was empty

      Source Code - (smtlib.c)

      {
      #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));
      }
      
       

      event_expire

      #include "smtlib.h"
      int
      event_expire (
          QUEUE *queue,                       /*  Queue to take event from         */
          EVENT *event                        /*  Event, or null for first         */
      )
      

      Synopsis

      Expires the next event or a specific event on an event queue. Sends a 'expired' event to the original sender if required, then destroys the event. You can use this to expire one specific message, or in a loop to cancel the entire queue. Returns 0 if the event was successfully expired, else returns - 1 and sets smt_errno to one of:
      SMT NOTREADY smt_init() was not called, or failed
      SMT QUEUEISEMPTY The queue was empty

      Source Code - (smtlib.c)

      {
      #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));
      }
      
       

      event_discard

      #include "smtlib.h"
      int
      event_discard (
          QUEUE *queue,                       /*  Queue to take event from         */
          EVENT *event                        /*  Event, or null                   */
      )
      

      Synopsis

      Discards the specified event in the specified queue. The event iterate (), event accept () and event discard () calls let a thread manipulate its event queue directly. In such cases the thread takes responsibility for event delivery and acknowledgement. Returns 0 when successfully completed. In case of error, returns -1 and sets smt_errno to one of these values:
      SMT NOTREADY smt_init() was not called, or failed
      SMT QUEUEISEMPTY The queue was empty

      Source Code - (smtlib.c)

      {
      #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);
      }
      
       

      event_iterate

      #include "smtlib.h"
      EVENT *
      event_iterate (
          QUEUE *queue,                       /*  Queue to search                  */
          EVENT *event                        /*  Event, or null for first         */
      )
      

      Synopsis

      Returns the first or next event in the queue. If the 'after' argument is null, returns the first event, else returns the next event. You should not 'walk' the event queue directly, since the implementation may change arbitrarily. Returns a pointer to the next event, or null if no (further) events were found. May set smt_errno to one of:
      SMT NOTREADY smt_init() was not called, or failed
      SMT QUEUEISEMPTY The queue was empty

      Source Code - (smtlib.c)

      {
      #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);
      }
      
       

      event_destroy

      #include "smtlib.h"
      int
      event_destroy (
          EVENT *event                        /*  Event block to destroy           */
      )
      

      Synopsis

      Destroys the specified event, which is assumed not to be linked into any queue. Typically you'll call this after processing an event you received with event accept(). Returns 0 if okay.

      Source Code - (smtlib.c)

      {
      #if (defined (SMT_TRACE))
          trace ("event_destroy: event=%s", event-> name);
      #endif
          node_destroy (event);               /*  Unlink and destroy event         */
          return (0);
      }
      
       

      event_wait

      #include "smtlib.h"
      void
      event_wait (void)
      

      Synopsis

      Suspends processing of an action module list, and waits for an incoming event. The event is received in 'the_external_event' by the next dialog module. When called in the last module in a list, has no effect. This call has no effect if you raise an exception or supply a value in the_next_event.

      Source Code - (smtlib.c)

      {
      #if (defined (SMT_TRACE))
          trace ("event_wait");
      #endif
          break_wanted = BREAK_WAIT_EVENT;
      }
      
       

      thread_create

      #include "smtlib.h"
      THREAD *
      thread_create (
          const char *agent_name,             /*  Name of agent                    */
          const char *thread_name             /*  Create thread with this name     */
      )
      

      Synopsis

      Creates a new thread, and possibly an event queue for the thread. The caller specifies the agent and thread name. The agent must already be declared using agent declare(). If the agent was defined as a router, you can create multiple threads with the same name. These threads then share the same event queue on an anonymous basis. If the agent was not defined as a router, it is illegal to create multiple threads with the same name unless the name is empty (meaning "" or NULL). The function automatically creates an event queue for the thread when required. Returns a pointer to the created THREAD block, or null if there was an error. In that case, sets smt_errno to one of:
      SMT NOTREADY smt_init() was not called, or failed
      SMT OUTOFMEMORY Not enough heap memory left
      SMT NOSUCHAGENT The agent was not declared
      SMT AGENTNOTREADY The agent is not initialised
      SMT TOOMANYTHREADS Tried to exceed the maximum permitted threads
      SMT THREADEXISTS The thread already exists
      Attaches the thread to the active thread list.

      Source Code - (smtlib.c)

      {
          SYMBOL  *dict_entry;                /*  Dictionary symbol                */
          AGENT   *agent;                     /*  Agent information block          */
          QUEUE   *queue;                     /*  Queue information block          */
          THREAD  *thread;                    /*  Created thread block             */
          char    *full_name;                 /*  Full thread name                 */
      
      #if (defined (SMT_TRACE))
          trace ("thread_create: agent=%s thread=%s", agent_name, thread_name);
      #endif
          ASSERT (agent_name);
          if (!smt_alive)                     /*  If SMT API was not correctly     */
            {                                 /*    initialised, forget it         */
              smt_errno = SMT_NOTREADY;
              return (NULL);
            }
      
          /*  Find agent, quit if there was an error                               */
          if ((agent = agent lookup (agent_name)) == NULL)
              return (NULL);
      
          /*  Check that we're allowed to create a new thread                      */
          if (agent-> max_threads > 0
          &&  agent-> max_threads == agent-> cur_threads)
            {
              smt_errno = SMT_TOOMANYTHREADS;
              return (NULL);
            }
      
          /*  Simple check that agent has been initialised                         */
          if (agent-> initialise == NULL)
            {
              smt_errno = SMT_AGENTNOTREADY;
              return (NULL);
            }
      
          /*  Treat a NULL thread name as an empty string                          */
          if (thread_name == NULL)
              thread_name = "";
      
          /*  Check if the thread already exists                                   */
          full_name  = get_entity_name (agent_name, thread_name);
          dict_entry = sym_lookup_symbol (dict, full_name);
          queue      = NULL;                  /*  No queue created yet             */
          if (dict_entry)
            {
              /*  If it's a router, we'll use the same queue                       */
              if (agent-> router)
                  queue = ((THREAD *) (dict_entry-> data))-> queue;
              else
              if (thread_name [0])            /*  Otherwise it's an error if the   */
                {                             /*    thread was given a name        */
                  smt_errno = SMT_THREADEXISTS;
                  return (NULL);
                }
            }
          if (!queue)                        /*   Create new queue in agent        */
              queue = queue create (agent, 0);
      
          /*  Now create entry for the thread                                      */
          dict_entry = sym_create_symbol (dict, full_name, NULL);
          if (dict_entry == NULL)
            {
              if (queue)                      /*  Clean-up nicely                  */
                  queue destroy (queue);
              smt_errno = SMT_OUTOFMEMORY;
              return (NULL);
            }
      
          /*  Allocate a THREAD block and attach it to the queue's thread list     */
          /*  We also allocate the TCB and subdialog stack if that is required     */
          thread = (THREAD *) node_create (&queue-> threads, sizeof (THREAD));
          if (thread)
            {
              thread-> tcb      = agent-> tcb_size > 0?
                                  mem_alloc (agent-> tcb_size):
                                  NULL;
              thread-> LR_stack = agent-> stack_size > 0?
                                  mem_alloc (agent-> stack_size * sizeof (event_t)):
                                  NULL;
            }
          if (thread == NULL)
            {
              sym_delete_symbol (dict, dict_entry);
              if (queue)                      /*  Clean-up nicely                  */
                  queue destroy (queue);
              smt_errno = SMT_OUTOFMEMORY;
              return (NULL);
            }
      
          /*  Point the dictionary entry to the thread information block            */
          dict_entry-> data = thread;
      
          /*  Now initialise the thread fields and list heads                       */
          thread-> symbol     = dict_entry;
          thread-> name       = mem_strdup (thread_name);
          thread-> queue      = queue;
          thread-> thread_id  = agent-> thread_tally++;
          thread-> animate    = agent-> animate;
          thread-> left       = thread;
          thread-> right      = thread;
          thread-> event      = NULL;         /*  Last event for thread            */
      
          cur_threads++;                      /*  Keep count of threads            */
          agent-> cur_threads++;
          if (agent-> top_threads < agent-> cur_threads)
              agent-> top_threads = agent-> cur_threads;
      
          activate_thread (thread);           /*  Move thread to active list       */
          thread-> state    = SMT_THREAD_ACTIVE;
          thread-> LR_state = SMT_NULL_STATE;
          return (thread);
      }
      
       

      thread_lookup

      #include "smtlib.h"
      THREAD *
      thread_lookup (
          const char *agent_name,             /*  Name of agent                    */
          const char *thread_name             /*  Create thread with this name     */
      )
      

      Synopsis

      Checks whether a specific thread exists; returns the address of the thread information block, or NULL if there was an error, setting smt_errno to one of these values:
      SMT NOTREADY smt_init() was not called, or failed
      SMT NOSUCHTHREAD The agent/thread does not exist
      If there are multiple threads (routers) with the same name, returns the earliest thread that was defined.

      Source Code - (smtlib.c)

      {
          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);
      }
      
       

      thread_destroy

      #include "smtlib.h"
      int
      thread_destroy (
          THREAD *thread,                     /*  Thread to destroy                */
          Bool    cleanup                     /*  Delete queue if last thread      */
      )
      

      Synopsis

      Destroys the thread. If this was the last instance of a router thread, destroys the parent queue as well, if the cleanup argument is TRUE. Returns 0 if successfully completed, else returns -1 and sets smt_errno to one of these values:
      SMT NOTREADY smt_init() was not called, or failed
      Removes the thread from the active list if it was attached there. Destroys any event allocated for the thread.

      Source Code - (smtlib.c)

      {
          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);
      }
      
       

      semaph_create

      #include "smtlib.h"
      SEMAPH *
      semaph_create (
          const char *semaph_name,            /*  Name of semaph to create         */
          int   value                         /*  Initial semaphore value          */
      )
      

      Synopsis

      Creates a new semaphore. You must create a semaphore before you can use it. The value argument specifies the number of parties that can access the semaphore (or its related resources) at once. The value must be greated than zero. A 'normal' binary semaphore has an initial value of 1. The semaph name is an arbitrary text, unique within the application. Returns the address of the created SEMAPH block. If there was an error, returns NULL and sets smt_errno to one of these values:
      SMT NOTREADY smt_init() was not called, or failed
      SMT OUTOFMEMORY Not enough heap memory left
      SMT SEMAPHEXISTS A semaphore with this name is already declared

      Source Code - (smtlib.c)

      {
          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);
      }
      
       

      semaph_lookup

      #include "smtlib.h"
      SEMAPH *
      semaph_lookup (
          const char *semaph_name             /*  Name of semaph to look for       */
      )
      

      Synopsis

      Checks whether a specific semaphore exists; returns the address of the semaphore information block, or NULL if there was an error, setting smt_errno to one of these values:
      SMT NOTREADY smt_init() was not called, or failed
      SMT NOSUCHSEMAPH Specified semaphore was not declared

      Source Code - (smtlib.c)

      {
          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         */
      }
      
       

      semaph_destroy

      #include "smtlib.h"
      int
      semaph_destroy (
          SEMAPH *semaph                      /*  Semaph to destroy; null = all    */
      )
      

      Synopsis

      Destroys the semaphore. Returns 0 when completed. The semaph argument points to an semaph block, or is null. If null, all semaphores are destroyed. Returns 0 when completed normally, else returns -1 and sets smt_errno to one of these values:
      SMT NOTREADY smt_init() was not called, or failed

      Source Code - (smtlib.c)

      {
      #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);
      }
      
       

      semaph_wait

      #include "smtlib.h"
      int
      semaph_wait (
          SEMAPH *semaph                      /*  Semaph to wait for               */
      )
      

      Synopsis

      When the semaphore value is > 0, subtracts 1 from the semaphore value. If necessary, suspends the thread until this happens. Threads are re-started on a FIFO basis. Call as last statement in an action module. Returns 0 when completed normally, else returns -1 and sets smt_errno to one of these values:
      SMT NOTREADY smt_init() was not called, or failed

      Source Code - (smtlib.c)

      {
      #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);
      }
      
       

      semaph_signal

      #include "smtlib.h"
      int
      semaph_signal (
          SEMAPH *semaph                      /*  Semaph to signal                 */
      )
      

      Synopsis

      Adds 1 to the semaphore value. Returns 0 when completed normally, else returns -1 and sets smt_errno to one of these values:
      SMT NOTREADY smt_init() was not called, or failed

      Source Code - (smtlib.c)

      {
      #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         */
      }
      
       

      lazy_creat

      #include "smtlib.h"
      int
      lazy_creat (char *path, int mode)
      

      Synopsis

      Calls the standard creat() function without blocking. (Actually, calls the open() function, but with the O_CREAT flag.) Returns a file handle when the call succeeds, else returns -1 and sets errno to the cause of the error. The file is always opened in binary mode, and you must process control characters yourself. We use open() so that we can force O_NONBLOCK. If the call would block, returns -1, sets errno to EAGAIN, and calls recycle module() to re-execute the current dialog module automatically. You can override this behavious by calling recycle module (FALSE) after the return. Sets the global variable "io_completed" to TRUE if the i/o access completed, with or without an error.

      Source Code - (smtlib.c)

      {
          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);
      }
      
       

      lazy_creat_text

      #include "smtlib.h"
      int
      lazy_creat_text (char *path, int mode)
      

      Synopsis

      Calls the standard creat() function without blocking. (Actually, calls the open() function, but with the O_CREAT flag.) Returns a file handle when the call succeeds, else returns -1 and sets errno to the cause of the error. The file is always opened in text mode. We use open() so that we can force O_NONBLOCK. If the call would block, returns -1, sets errno to EAGAIN, and calls recycle module() to re-execute the current dialog module automatically. You can override this behavious by calling recycle module (FALSE) after the return. Sets the global variable "io_completed" to TRUE if the i/o access completed, with or without an error.

      Source Code - (smtlib.c)

      {
          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);
      }
      
       

      lazy_open

      #include "smtlib.h"
      int
      lazy_open (char *path, int flags)
      

      Synopsis

      Calls the standard open() function without blocking. Returns a file handle when the call succeeds, else returns -1 and sets errno to the cause of the error. The file is always opened in binary mode, and you must process control characters yourself. If the call would block, returns -1, sets errno to EAGAIN, and calls recycle module() to re-execute the current dialog module automatically. You can override this behavious by calling recycle module (FALSE) after the return. Sets the global variable "io_completed" to TRUE if the i/o access completed, with or without an error.

      Examples

          handle_input  = lazy_open (filename, O_RDONLY);
          handle_output = lazy_open (filename, O_WRONLY | O_CREAT | O_TRUNC);
          handle_append = lazy_open (filename, O_WRONLY | O_CREAT | O_APPEND);
          if (io_completed && handle < 0)
              have error on file
      

      Source Code - (smtlib.c)

      {
          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);
      }
      
       

      lazy_open_text

      #include "smtlib.h"
      int
      lazy_open_text (char *path, int flags)
      

      Synopsis

      As lazy open(), but opens the file in text mode, on those platforms where this makes a difference.

      Examples

          handle_input  = lazy_open (filename, O_RDONLY);
          handle_output = lazy_open (filename, O_WRONLY | O_CREAT | O_TRUNC);
          handle_append = lazy_open (filename, O_WRONLY | O_CREAT | O_APPEND);
          if (io_completed && handle < 0)
              have error on file
      

      Source Code - (smtlib.c)

      {
          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);
      }
      
       

      lazy_read

      #include "smtlib.h"
      int
      lazy_read (int handle, char *buffer, size_t count)
      

      Synopsis

      Calls the standard read() function without blocking. Returns the number of bytes read when the call succeeds, else returns - 1 and sets errno to the cause of the error. If the call would block, returns -1, sets errno to EAGAIN, and calls recycle module() to re-execute the current dialog module automatically. You can override this behavious by calling recycle module (FALSE) after the return. Sets the global variable "io_completed" to TRUE if the i/o access completed, with or without an error.

      Source Code - (smtlib.c)

      {
          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);
      }
      
       

      lazy_write

      #include "smtlib.h"
      int
      lazy_write (int handle, char *buffer, size_t count)
      

      Synopsis

      Calls the standard write() function without blocking. Returns the number of bytes written when the call succeeds, else returns -1 and sets errno to the cause of the error. If the call would block, returns -1, sets errno to EAGAIN, and calls recycle module() to re-execute the current dialog module automatically. You can override this behavious by calling recycle module (FALSE) after the return. Sets the global variable "io_completed" to TRUE if the i/o access completed, with or without an error.

      Source Code - (smtlib.c)

      {
          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);
      }
      
       

      lazy_close

      #include "smtlib.h"
      int
      lazy_close (int handle)
      

      Synopsis

      Calls the standard close() function without blocking. Returns 0 when the call succeeds, else returns -1 and sets errno to the cause of the error. If the call would block, returns -1, sets errno to EAGAIN, and calls recycle module() to re-execute the current dialog module automatically. You can override this behavious by calling recycle module (FALSE) after the return. Sets the global variable "io_completed" to TRUE if the i/o access completed, with or without an error.

      Source Code - (smtlib.c)

      {
          int rc;
      
      #if (defined (SMT_TRACE))
          trace ("lazy_close: handle=%d", handle);
      #endif
          rc = close (handle);
          set_io_completed (rc);
          return (rc);
      }
      
       

      senderr

      #include "smtlib.h"
      int
      senderr (const QID *to_queue)
      

      Synopsis

      Sends an "ERROR" event to the specified queue, with the value of strerror (errno) as event body. Use this to reply after some i/o access failed. Return values are the same as for event send().

      Examples

          senderr (&thread-> event-> sender);
      

      Source Code - (smtlib.c)

      {
          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                       */
      }
      
       

      sendfmt

      #include "smtlib.h"
      int
      sendfmt (const QID *to_queue, char *name, char *format, ...)
      

      Synopsis

      Sends a text message to the specified queue. The caller can specify a printf()-type format string and insertion values. Return values are the same as for event send().

      Examples

          sendfmt (&console, "INFO", "Error accessing %s file", filename);
      

      Source Code - (smtlib.c)

      {
          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                       */
      }
      
       

      raise_exception

      #include "smtlib.h"
      void
      raise_exception (event_t event)
      

      Synopsis

      Sets the exception_raised flag to TRUE and sets the exception event as specified. To raise an exception without setting the exception event, just do this: 'exception_raised = TRUE;'.

      Source Code - (smtlib.c)

      {
      #if (defined (SMT_TRACE))
          trace ("raise_exception");
      #endif
          _exception_raised    = TRUE;
          _the_exception_event = event;
      }
      
       

      recycle_module

      #include "smtlib.h"
      void
      recycle_module (Bool wanted)
      

      Synopsis

      Tells the SMT kernel to repeat the current action module. This is a simple way to re-attempt an i/o that returned a 'EAGAIN' or 'EWOULDBLOCK' code. The lazy file access functions automatically call this function in case they failed. If you call this function with the wanted argument as FALSE, any previous recycle request is cancelled.

      Source Code - (smtlib.c)

      {
      #if (defined (SMT_TRACE))
          trace ("recycle_module");
      #endif
          if (wanted)
              break_wanted = BREAK_RECYCLE;
          else
              break_wanted = BREAK_CONTINUE;
      }
      
       

      smt_set_step

      #include "smtlib.h"
      void
      smt_set_step (const char *step)
      

      Synopsis

      Sets the current module 'step'. Used to debug applications: suspect modules can set 'steps': the last of which is displayed when there is a SEGV crash. Must be called with a string (constant) value.

      Source Code - (smtlib.c)

      {
          cur_step = (char *) step;
      }
      
       

      smt_crash_report

      #include "smtlib.h"
      char *
      smt_crash_report (void)
      

      Synopsis

      Returns a formatted string showing the current agent, state, module, step, event, and catch report, if any.

      Source Code - (smtlib.c)

      {
          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);
      }
      

      The Standard SMT Agents

      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

      Synopsis

      Defines the standard agent prototypes and methods.

      List of Functions

      List of Symbol Definitions

      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
       

      smtauth_init

      #include "smtdefn.h"
      int
      smtauth_init (void)
      

      Synopsis

      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.
      Sends errors to the SMTOPER agent.

      Source Code - (smtauth.c)

      {
          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);
      }
      
       

      smtecho_init

      #include "smtdefn.h"
      int
      smtecho_init (void)
      

      Synopsis

      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.

      Source Code - (smtecho.c)

      {
          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);
      }
      
       

      smthttp_init

      #include "smtdefn.h"
      int smthttp_init (char *p_rootdir,      /*  Document root directory          */
                        char *p_cgidir)       /*  CGI binary directory             */
      

      Synopsis

      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.

      Source Code - (smthttp.c)

      {
          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);
      }
      
       

      smtftpc_init

      #include "smtdefn.h"
      int
      smtftpc_init (char *p_rootdir)          /*  Server root directory            */
      

      Synopsis

      Initialises the FTP control agent. Returns 0 if initialised okay, -1 if there was an error.

      Source Code - (smtftpc.c)

      {
          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);
      }
      
       

      smtftpd_init

      #include "smtdefn.h"
      int smtftpd_init (void)
      

      Synopsis

      Initialises the FTP data agent. Returns 0 if initialised okay, -1 if there was an error.

      Source Code - (smtftpd.c)

      {
          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);
      }
      
       

      smtrdns_init

      #include "smtdefn.h"
      int smtrdns_init (void)
      

      Synopsis

      Initialises the reverse DNS agent. Returns 0 if initialised okay, -1 if there was an error.

      Source Code - (smtrdns.c)

      {
          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);
      }
      
       

      smtlog_init

      #include "smtdefn.h"
      int
      smtlog_init (void)
      

      Synopsis

      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.
      Sends errors to the SMTOPER agent; does not send reply events.

      Source Code - (smtlog.c)

      {
          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);
      }
      
       

      smtoper_init

      #include "smtdefn.h"
      int
      smtoper_init (void)
      

      Synopsis

      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).
      Does not send reply events.

      Source Code - (smtoper.c)

      {
          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);
      }
      
       

      smtslot_init

      #include "smtdefn.h"
      int
      smtslot_init (void)
      

      Synopsis

      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.
      Sends errors to the SMTOPER agent; see doc for reply events.

      Source Code - (smtslot.c)

      {
          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);
      }
      
       

      smtsock_init

      #include "smtdefn.h"
      int
      smtsock_init (void)
      

      Synopsis

      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).
      Sends errors to the SMTOPER agent; see doc for reply events.

      Source Code - (smtsock.c)

      {
          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);
      }
      
       

      smtsock_trace

      #include "smtdefn.h"
      void smtsock_trace (Bool trace_value)
      

      Synopsis

      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.

      Source Code - (smtsock.c)

      {
          trace_flag = trace_value;
      }
      
       

      smttime_init

      #include "smtdefn.h"
      int
      smttime_init (void)
      

      Synopsis

      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.
      Sends errors to the SMTOPER agent; see doc for reply events.

      Source Code - (smttime.c)

      {
          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);
      }
      
       

      smttran_init

      #include "smtdefn.h"
      int
      smttran_init (void)
      

      Synopsis

      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
      Sends errors to the SMTOPER agent; see the doc for reply events.

      Source Code - (smttran.c)

      {
          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);
      }
      

      The Network Delay Simulator

      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

      Synopsis

      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.

      List of Functions

      List of Symbol Definitions

      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
       

      smtsimu_init

      #include "smtsimu.h"
      int
      smtsimu_init (void)
      

      Synopsis

      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.

      Source Code - (smtsimu.c)

      {
          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);
      }
      
       

      smtsimu_sock_init

      #include "smtsimu.h"
      int
      smtsimu_sock_init (void)
      

      Synopsis

      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.

      Source Code - (smtsimu.c)

      {
          /*  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);
      }
      
       

      smtsimu_smtsock_init

      #include "smtsimu.h"
      int
      smtsimu_smtsock_init (void)
      

      Synopsis

      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.

      Source Code - (smtsimu.c)

      {
          /*  Initialise SMTSOCK agent                                             */
          if (smtsock init ())
              return (-1);
      
          /*  Now initialise the SMTSIMU agent                                     */
          return (smtsimu init ());
      }
      
       

      smtsimu_smtsock_trace

      #include "smtsimu.h"
      void
      smtsimu_smtsock_trace (Bool trace_value)
      

      Synopsis

      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.

      Source Code - (smtsimu.c)

      {
          trace_flag = trace_value;
          smtsock trace (trace_value);
      }
      
       

      smtsimu_write_UDP

      #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      */
      )
      

      Synopsis

      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.

      Source Code - (smtsimu.c)

      {
          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);
      }
      

      The Unattended Process Monitor

      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

      Synopsis

      Defines constants, messages, and prototypes for the UPM (unattended process monitor) agents.

      List of Functions

      List of Symbol Definitions

      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
       

      smtupmc_init

      #include "smtupm.h"
      int
      smtupmc_init (char *p_args, char *p_portname)
      

      Synopsis

      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.

      Source Code - (smtupmc.c)

      {
          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);
      }
      

      | << | < | > | >> iMatix Copyright © 1996-99 iMatix Corporation