/*******************************************************************
 *                                                                 *
 *                           IPC.c                                 *
 *                                                                 *
 *           Inter-Process-Communication Procedures                *
 *                                                                 *
 *              Release  1.0 -- 1988 May 11                        *
 *                                                                 *
 *              Copyright 1988 Peter Goodeve                       *
 *                                                                 *
 *  This source is freely distributable, and may be used freely    *
 *  in any program,  but its functionality should not be modified  *
 *  without prior consultation with the author.  (This is just to  *
 *  prevent proliferation of incompatible variants.  Don't be      *
 *  inhibited from suggesting enhancements!)                       *
 *                                                                 *
 *******************************************************************/

/********************************************************************
 *                                                                  *
 *  Synopsis of usage:                                              *
 *  ========                                                        *
 *                                                                  *
 *    Client:                                                       *
 *                                                                  *
 *          port = GetIPCPort(name);                                *
 *      or  port = FindIPCPort(name)                                *
 *          .....                                                   *
 *          msg = CreateIPCMsg(nitems);                             *
 *          .....                                                   *
 *          PutIPCMsg(msg,port);                                    *
 *          .....                                                   *
 *          DeleteIPCMsg(msg);                                      *
 *          .....                                                   *
 *          DropIPCPort(port);                                      *
 *                                                                  *
 *    Server: [standard Exec procedures in brackets]                *
 *                                                                  *
 *          port = ServeIPCPort(name);                              *
 *          .....                                                   *
 *          [WaitPort(port); or Wait(sigbits);]                     *
 *          .....                                                   *
 *          [msg = GetMsg(port);]                                   *
 *          .....                                                   *
 *          [ReplyMsg(msg);]                                        *
 *          .....                                                   *
 *          ShutIPCPort(port);                                      *
 *          <handle remaining messages on the port>                 *
 *          LeaveIPCPort(port);                                     *
 *                                                                  *
 *    Misc.:                                                        *
 *                                                                  *
 *          UseIPCPort(port);                                       *
 *          CheckIPCPort(port,flags);                               *
 *                                                                  *
 *                                                                  *
 ********************************************************************/

/********************************************************************
 *                                                                  *
 * These procedures provide a mechanism for independent processes   *
 * to communicate with each other through "IPC ports".   These are  *
 * similar to standard Exec Message Ports, except that they are     *
 * not "owned" by any of the processes; any one process can         *
 * declare itself a "server" (provided no other is currently        *
 * claiming this right), and thus becomes temporarily the handler   *
 * of messages passed to this port.  If there is no server, any     *
 * attempt to send a message to a port will return failure, and     *
 * the client may take whatever action is appropriate.  A client    *
 * may safely "Get" a pointer to a named port (even if there is no  *
 * server yet) and can rely on it remaining valid until it "Drops"  *
 * it again. (In contrast to Exec ports, which have no such         *
 * protection.)                                                     *
 *                                                                  *
 * IPC Ports don't appear in the Exec named port list -- they have  *
 * their own.  There is a single publicly available name, itself    *
 * actually a port on the public list (for reasons we'll see later) *
 * to which is added some data structure including the IPC Port     *
 * list.                                                            *
 *                                                                  *
 * These procedures are just one part of a fully developed system.  *
 * (Another is the IPCMessage structure itself -- see IPC.h and     *
 * elsewhere -- which is essentially independent of the IPC Port    *
 * mechanism.)  They form a fairly self contained set, so they've   *
 * initially been written as a single source file, but there's no   *
 * reason they couldn't be split into smaller segments.  Their      *
 * natural home is a resident library in any case, but that can     *
 * wait for the moment.                                             *
 *                                                                  *
 * Other modules will doubtless have to be added.  One such is      *
 * "LoadIPCPort(name)" which, after doing a GetIPCPort(), will      *
 * check the returned port to see if it has a  server; if not, it   *
 * will check to see if there is a "broker"  process serving the    *
 * IPCBasePort, and if so will send a message to that asking for    *
 * the port to be "served".  (The broker is essentially a server    *
 * like any other -- it will probably look up the port in a user    *
 * supplied list and load the specified server; more advanced       *
 * models may check to see if the server is on another machine,     *
 * for example.)                                                    *
 *                   - - - - - - - - - - -                          *
 *                                                                  *
 *   This code has only been tested under Lattice 4.0.  It is       *
 *   intended to be compiler independent, but please check it       *
 *   for problems.  The code is not always optimized!               *
 *                   - - - - - - - - - - -                          *
 *                                                                  *
 *      %%  The fundamental concept of keeping a use-count  %%      *
 *      %%  on a port is due to Matt Dillon.  Thanks Matt!  %%      *
 *                                                                  *
 ********************************************************************/


#include "IPCPorts.h"
    /* this should be included first to suppress defaults in IPC.h */
    /* other modules in processes using IPC will not usually need
       this header -- only IPC.h */

#include "IPC.h"
    /* note -- the above includes prototypes for the procedures below */

#include "exec/memory.h"
#include "exec/tasks.h"

struct Task * FindTask();


/*
 *  IPCBasePort is the node around which the whole IPC system is organized.
 *  It is placed in AmigaExec's public port list by the first program to
 *  use IPC, and remains there ever after.  It is the ONLY public port
 *  created by the IPC system -- all IPCPorts are hung on a list from a
 *  header in the IPCBasePort structure.  It may also be an actual port
 *  served by a "broker" process, which will be sent messages requesting,
 *  for example, that it load a server for a port.
 */

struct IPCBasePort *IPCBasePort = NULL;


/*
 *  OpenIPCBase
 *
 *      returns a pointer to IPCBasePort, creating, initializing and
 *      adding it to the Exec public port list if it doesn't already exist.
 *      This procedure will normally only be called by other IPC system
 *      procedures (except possibly in a program such as a "broker").
 */

struct IPCBasePort * OpenIPCBase()
{
    char * pname = "IPC_Base_Port";
    struct MsgPort * port;

    if (IPCBasePort) return IPCBasePort;
    Forbid();
    if (!(IPCBasePort = (struct IPCBasePort *)FindPort(pname))) {
        IPCBasePort = (struct IPCBasePort *)
            AllocMem(sizeof(struct IPCBasePort), MEMF_CLEAR | MEMF_PUBLIC);
        if (IPCBasePort) {
            port = (struct MsgPort *) IPCBasePort;
            port->mp_Node.ln_Name = IPCBasePort->ipcb_Port.ipp_Name;
            port->mp_Node.ln_Type = NT_MSGPORT;
            AddPort(port);
            NewList(&(IPCBasePort->ipcb_PortList));
            strcpy(IPCBasePort->ipcb_Port.ipp_Name, pname);
        }
    }
    Permit();
    return IPCBasePort;
}

/*** There is NO CloseIPCBase()!! (it stays around forever) ***/


/*
 *  CreateIPCPort is a private procedure which should not be called
 *  directly by a user program.  (Actually it is only called by GetIPCPort
 *  but for clarity it is kept separate.)
 *  The created IPCPort will be added to the list in IPCBasePort
 *  (GetIPCPort won't allow duplicate names) unless the name is NULL,
 *  in which case an "anonymous" port will be created that does not get
 *  onto the list; such anonymous ports can only be accessed by other
 *  processes if they are themselves passed as pointers in messages.
 */

static struct IPCPort * CreateIPCPort(name) char *name;
{
    struct IPCPort * port;
    int psize;

    if (!OpenIPCBase()) return NULL; /* Quick check before we do anything */
    psize = sizeof(struct IPCPort) + (name ? strlen(name) : 0);
        /* psize is actually one byte too big -- do you care? */
    port = (struct IPCPort *)
           AllocMem(psize, MEMF_CLEAR | MEMF_PUBLIC);

    if (port) {
        port->ipp_Size = psize;
        NewList(&(port->ipp_Port.mp_MsgList));
        port->ipp_Port.mp_Node.ln_Type = NT_MSGPORT;
        port->ipp_Port.mp_Flags = PA_IGNORE; /* initially */
        if (name) { /* anonymous port is not put on list */
          port->ipp_Port.mp_Node.ln_Name = port->ipp_Name;
                    /* point to name storage array */
          strcpy(port->ipp_Name, name); /* move name to permanent storage */
          AddHead(&IPCBasePort->ipcb_PortList, port);
        }
    }
    return port;
}


/*
 *  FindIPCPort
 *
 *     Finds the IPCPort with the name supplied as argument if
 *     it has been previously created.
 *     Returns pointer to port if it exists -- null otherwise;
 *     registers a new connection to the port (i.e. increments UseCount).
 *     (Connection must be terminated when program is done by the procedure
 *     DropIPCPort.)
 *     If the current server has set the IPP_NOTIFY flag in the port, the
 *     server task will be signalled using the port signal bit.  NOTE that
 *     WaitPort will NOT detect these signals, because no message is
 *     actually sent; the program must do a Wait on this bit, and should do
 *     a GetMsg as usual, but if the message pointer is null it should
 *     then call, for example, CheckIPCPort.
 */

struct IPCPort * FindIPCPort(name) char *name;
{
    struct IPCPort * port;
    if (!OpenIPCBase()) return NULL; /* Quick check before we do anything */
    if (!name) return NULL; /* port could be anonymous */
    Forbid();
    port = (struct IPCPort *)FindName(&IPCBasePort->ipcb_PortList, name);
    if (port) port->ipp_UseCount++;
    Permit();
    if (port && port->ipp_Flags & IPP_NOTIFY)
        /* Server wants to be notified */
        Signal(port->ipp_Port.mp_SigTask,
               1<<port->ipp_Port.mp_SigBit);
    return port;
}


/*
 *  GetIPCPort
 *
 *     Returns a pointer to IPCPort with the name supplied as an argument;
 *     unlike FindIPCPort, it always returns pointer to port -- this is
 *     created if it doesn't exist; registers a new connection to the port
 *     (use DropIPCPort when done).  It will notify a server if requested
 *     (see FindIPCPort).
 */

struct IPCPort * GetIPCPort(name) char *name;
{
    struct IPCPort * port;
    Forbid();
    port = FindIPCPort(name);
    if (!port) {
        port = CreateIPCPort(name);
        port->ipp_UseCount++;
    }
    Permit();
    return port;
}


/*
 *  UseIPCPort
 *
 *     Registers another connection to a port (normally when the port
 *     was passed as a pointer from another process).
 *     (Use DropIPCPort when done.)
 */

void UseIPCPort(port) struct IPCPort * port;
{
    port->ipp_UseCount++;
    if (port->ipp_Flags & IPP_NOTIFY) /* Server wants to be notified */
        Signal(port->ipp_Port.mp_SigTask,
               1<<port->ipp_Port.mp_SigBit);
}


/*
 *  DropIPCPort
 *
 *     Terminate a connection to a port established by FindIPCPort,
 *     GetIPCPort, or UseIPCPort.  Port will be destroyed if there are
 *     no other connections left.
 *     If the IPP_NOTIFY flag is set in the port, the server will be
 *     signalled when this procedure is called (see FindIPCPort).
 */

void DropIPCPort(port) struct IPCPort * port;
{
    if (!port) return; /* to save the client some trouble
                          (in a cleanup procedure) */
    Forbid();
    if (--port->ipp_UseCount == 0) {
        /* an anonymous port is NOT on list -- ALL others MUST be! */
        if (port->ipp_Port.mp_Node.ln_Name) Remove(port);
        Permit();
        FreeMem(port, port->ipp_Size);
    }
    else {
        if (port->ipp_Flags & IPP_NOTIFY) /* Server wants to be notified */
            Signal(port->ipp_Port.mp_SigTask,
                   1<<port->ipp_Port.mp_SigBit);
        Permit();
    }
}


/*
 *  ServeIPCPort
 *
 *     Registers calling task as the server on the named port (which is
 *     created if it doesn't exist); null is returned if the port already
 *     has a server, otherwise a pointer to it is returned.  At the same
 *     time the port is given the server as its SigTask and a suitable
 *     signal bit is allocated.
 */

struct IPCPort * ServeIPCPort(name) char *name;
{
    struct IPCPort * port;

    port = GetIPCPort(name);
    if (port) {
        Forbid();
        if ((port->ipp_Flags & (IPP_SERVED | IPP_SHUT))
         || (port->ipp_Port.mp_SigBit = AllocSignal(-1)) == -1) {
            DropIPCPort(port);
            port = NULL;
        }
        else {
            port->ipp_Port.mp_Flags = PA_SIGNAL;
            port->ipp_Port.mp_SigTask = FindTask(0);
            port->ipp_Flags |= IPP_SERVED;
        }
        Permit();
    }
    return port;
}

/*
 *  ShutIPCPort
 *
 *     ONLY the current server may call this procedure.
 *     It prevents more messages being sent to this port, but
 *     does not end server connection; remaining messages can be dealt
 *     with before finally calling LeaveIPCPort.
 *     Note that it does NOT inhibit the server being signalled if
 *     IPP_NOTIFY is set and another client connects.  (At the moment
 *     there is no mechanism to reopen a shut port without Leaving
 *     first; this may be possible in a future revision.)
 */

void ShutIPCPort(port) struct IPCPort * port;
{
    port->ipp_Flags |= IPP_SHUT; /* Prevent other servers connecting */
    port->ipp_Flags &= ~IPP_SERVED; /* prevent messages from landing */
    port->ipp_Port.mp_Flags = PA_IGNORE; /* someone might use PutMsg! */
}

/*
 *  LeaveIPCPort
 *
 *     ONLY the current server may call this procedure.
 *     Disconnects the server process from the port; another process
 *     can then become a server if it desires.  If there are no other
 *     connections, the port is removed.
 */

void LeaveIPCPort(port) struct IPCPort * port;
{
    FreeSignal(port->ipp_Port.mp_SigBit);
    port->ipp_Port.mp_SigTask = NULL;
    port->ipp_Flags &= ~(IPP_SHUT | IPP_SERVED | IPP_SERVER_FLAGS);
    DropIPCPort(port);
}


/*
 *  CheckIPCPort
 *
 *     Returns the number of current connections to this port (including
 *     the server); a call by the server (only) will also set the (user
 *     settable) port flags to the value in the second argument --
 *     currently the only valid flag is IPP_NOTIFY.
 */

CheckIPCPort(port, flags)
    struct IPCPort *port;
    USHORT flags;
{
    if (port->ipp_Port.mp_SigTask == FindTask(0))
        /* only server can change flags */
        port->ipp_Flags = (port->ipp_Flags & ~IPP_SERVER_FLAGS) |
                          (flags & IPP_SERVER_FLAGS);
    return (int) port->ipp_UseCount;
}


/*
 *  PutIPCMsg
 *
 *     Sends an IPCMessage to an IPCPort; if the port has no server or
 *     is shut, the message is not sent and the function returns FALSE;
 *     otherwise it returns TRUE. (Other port flags to be added later
 *     may affect these actions.)
 *     Note that a ReplyPort should be supplied in the message as usual
 *     (except for the rare possible usage where a message is not to
 *     be replied; in this case, the IPC_TRANSFER flag must be set in
 *     both the message and all the items it contains, and the ReplyPort
 *     must be NULL).
 */

PutIPCMsg(port,msg)
    struct IPCPort *port;
    struct IPCMessage *msg;
{
    Forbid(); /* we do this the straightforward way -- it's very quick */
    if (port->ipp_Flags & IPP_SERVED) {
        PutMsg(port, msg);
        Permit();
        return TRUE;
    }
    Permit();
    return FALSE;
}


/*
 *  CreateIPCMsg
 *
 *     Creates a standard IPCMessage block (in MEMF_PUBLIC) with the
 *     number of IPCItems supplied as argument. (Special cases -- like
 *     in-line data -- you will have to handle yourself, and you always
 *     have to manage the data blocks yourself).
 */

struct IPCMessage * CreateIPCMsg(nitems)
{
    int msgsize;
    struct IPCMessage * mp;
    msgsize = sizeof(struct IPCMessage) +
                  (nitems -1)*sizeof(struct IPCItem);
    mp = (struct IPCMessage *)
         AllocMem(msgsize, MEMF_CLEAR | MEMF_PUBLIC);
    if (mp) {
        mp->ipc_Msg.mn_Length = msgsize - sizeof(struct Message);
        mp->ipc_ItemCount = nitems;
    }
    return  mp;
}


/*
 *  DeleteIPCMsg
 *
 *     Deletes a standard IPCMessage block;  you must first have disposed
 *     of any attached data as appropriate.
 */

void DeleteIPCMsg(msg) struct IPCMessage *msg;
{
    FreeMem(msg, sizeof(struct Message) + msg->ipc_Msg.mn_Length);
}


            /*********************************************/
