From ba066c34dde204aa192d03a23a81356374d93731 Mon Sep 17 00:00:00 2001 From: chatty Date: Wed, 7 Apr 1993 11:50:31 +0000 Subject: Initial revision --- comm/Address.cc | 350 +++++++++++++++ comm/Address.h | 118 ++++++ comm/Channel.cc | 402 ++++++++++++++++++ comm/Channel.h | 114 +++++ comm/Datagram.cc | 166 ++++++++ comm/Datagram.h | 40 ++ comm/Imakefile | 99 +++++ comm/Message.cc | 98 +++++ comm/Message.h | 32 ++ comm/MsgBuffer.cc | 707 +++++++++++++++++++++++++++++++ comm/MsgBuffer.h | 112 +++++ comm/MsgStream.cc | 506 ++++++++++++++++++++++ comm/MsgStream.h | 70 +++ comm/Multiplexer.cc | 463 ++++++++++++++++++++ comm/Multiplexer.h | 83 ++++ comm/OLD/Agent.cc | 386 +++++++++++++++++ comm/OLD/Agent.h | 82 ++++ comm/OLD/Event.cc | 121 ++++++ comm/OLD/Event.h | 92 ++++ comm/OLD/PortServer.cc | 410 ++++++++++++++++++ comm/OLD/PortServer.h | 48 +++ comm/OLD/PortServerReq.cc | 60 +++ comm/OLD/PortServerReq.h | 45 ++ comm/OLD/ReqMgr.cc | 184 ++++++++ comm/OLD/ReqMgr.h | 81 ++++ comm/OLD/ReqMgr.l | 95 +++++ comm/OLD/ReqMgr.y | 285 +++++++++++++ comm/OLD/Server.cc | 395 +++++++++++++++++ comm/OLD/Server.h | 77 ++++ comm/OLD/Service.cc | 328 +++++++++++++++ comm/OLD/Service.h | 100 +++++ comm/OLD/SimpleMessage.cc | 47 +++ comm/OLD/SimpleMessage.h | 23 + comm/OLD/TextStream.cc | 1030 +++++++++++++++++++++++++++++++++++++++++++++ comm/OLD/TextStream.h | 157 +++++++ comm/OLD/dgram.cc | 717 +++++++++++++++++++++++++++++++ comm/OLD/dgram.h | 102 +++++ comm/OLD/portserv.cc | 653 ++++++++++++++++++++++++++++ comm/OLD/porttest.cc | 72 ++++ comm/OLD/reqgen.cc | 34 ++ comm/Socket.cc | 274 ++++++++++++ comm/Socket.h | 60 +++ comm/Stream.cc | 124 ++++++ comm/Stream.h | 33 ++ comm/TimeOut.cc | 69 +++ comm/TimeOut.h | 32 ++ comm/doc.main | 396 +++++++++++++++++ comm/error.cc | 261 ++++++++++++ comm/error.h | 58 +++ comm/global.h | 28 ++ comm/version.h | 75 ++++ 51 files changed, 10394 insertions(+) create mode 100644 comm/Address.cc create mode 100644 comm/Address.h create mode 100644 comm/Channel.cc create mode 100644 comm/Channel.h create mode 100644 comm/Datagram.cc create mode 100644 comm/Datagram.h create mode 100644 comm/Imakefile create mode 100644 comm/Message.cc create mode 100644 comm/Message.h create mode 100644 comm/MsgBuffer.cc create mode 100644 comm/MsgBuffer.h create mode 100644 comm/MsgStream.cc create mode 100644 comm/MsgStream.h create mode 100644 comm/Multiplexer.cc create mode 100644 comm/Multiplexer.h create mode 100644 comm/OLD/Agent.cc create mode 100644 comm/OLD/Agent.h create mode 100644 comm/OLD/Event.cc create mode 100644 comm/OLD/Event.h create mode 100644 comm/OLD/PortServer.cc create mode 100644 comm/OLD/PortServer.h create mode 100644 comm/OLD/PortServerReq.cc create mode 100644 comm/OLD/PortServerReq.h create mode 100644 comm/OLD/ReqMgr.cc create mode 100644 comm/OLD/ReqMgr.h create mode 100644 comm/OLD/ReqMgr.l create mode 100644 comm/OLD/ReqMgr.y create mode 100644 comm/OLD/Server.cc create mode 100644 comm/OLD/Server.h create mode 100644 comm/OLD/Service.cc create mode 100644 comm/OLD/Service.h create mode 100644 comm/OLD/SimpleMessage.cc create mode 100644 comm/OLD/SimpleMessage.h create mode 100644 comm/OLD/TextStream.cc create mode 100644 comm/OLD/TextStream.h create mode 100644 comm/OLD/dgram.cc create mode 100644 comm/OLD/dgram.h create mode 100644 comm/OLD/portserv.cc create mode 100644 comm/OLD/porttest.cc create mode 100644 comm/OLD/reqgen.cc create mode 100644 comm/Socket.cc create mode 100644 comm/Socket.h create mode 100644 comm/Stream.cc create mode 100644 comm/Stream.h create mode 100644 comm/TimeOut.cc create mode 100644 comm/TimeOut.h create mode 100644 comm/doc.main create mode 100644 comm/error.cc create mode 100644 comm/error.h create mode 100644 comm/global.h create mode 100644 comm/version.h (limited to 'comm') diff --git a/comm/Address.cc b/comm/Address.cc new file mode 100644 index 0000000..789a370 --- /dev/null +++ b/comm/Address.cc @@ -0,0 +1,350 @@ +/* + * The Unix Channel + * + * by Michel Beaudouin-Lafon + * + * Copyright 1990-1993 + * Laboratoire de Recherche en Informatique (LRI) + * + * Addresses + * + * $Id$ + * $CurLog$ + */ + +#include "Address.h" +#include "error.h" + +#include +#include +#include +#include +#include +#include + +/*?class UchAddress +This class is a virtual base class: no objects of this class are ever created. +It implements the Unix type \typ{^{struct sockaddr}}, used for creating sockets. +Addresses are used mainly as arguments of constructors for the class +\typ{UchSocket} and its derived classes. +All its member functions are virtual. +These functions exist for all the derived classes but are only described here. +An address can be valid or invalid, depending on the constructor being able to create the address or not. +There are currently two derived classes, for Unix domain addresses and Internet domain addresses. + +The class \typ{^{pUchAddress}} implements smart pointers to addresses. +Smart pointers behave like pointers but they manage a reference count on the +pointed to objects for an automatic reclaim of dynamically allocated objects. +?*/ + +/*? +Construct an invalid address. +?*/ +UchAddress :: UchAddress () +: Valid (FALSE) +{ +} + +/*?nodoc?*/ +UchAddress :: ~UchAddress () +{ +} + +// virtual functions for class address +// + +/*? +Return one of \var{AF_UNSPEC}, \var{AF_UNIX} or \var{AF_INET}. +Other values may be defined if supported by the system. +?*/ +int +UchAddress :: Family () +{ + return AF_UNSPEC; +} + +/*?nextdoc?*/ +int +UchAddress :: Length () +{ + return 0; +} + +/*? +Return the address structure and its length. +\typ{^{SockAddr}} is a typedef for \typ{^{struct sockaddr}}. +?*/ +SockAddr* +UchAddress :: GetSockAddr () +{ + return 0; +} + +/*?nodoc?*/ +char* +UchAddress :: StrRepr (char* buf) +{ + strcpy (buf, "virtual address !"); + return buf; +} + +#ifdef UNIX_SOCK + +// constructors for Unix addresses +// a Unix domain address is just a filename +// -> some problems: check for access, when to unlink it ? +// to do : generate name from a template (for uniqueness) +// + +/*?nodoc?*/ +UchUnixAddress :: UchUnixAddress () +: UchAddress () +{ + // nothing +} + +/*? +Create a Unix domain address associated to file \var{filename}. +The file is not unlinked when the address is destroyed; +it should be unlinked by the application whenever the last socket using this address is closed. +?*/ +UchUnixAddress :: UchUnixAddress (const char* filename) +: UchAddress () +{ + Addr.sun_family = AF_UNIX; + strcpy (Addr.sun_path, filename); + Valid = TRUE; // should check access to file ??? + // unlink if already there ? +} + +/*?nodoc?*/ +UchUnixAddress :: ~UchUnixAddress () +{ + // how to unlink the file ?? +} + +/*?nodoc?*/ +int +UchUnixAddress :: Family () +{ + return AF_UNIX; +} + +/*?nodoc?*/ +int +UchUnixAddress :: Length () +{ + if (Valid) + return (Addr.sun_path + strlen (Addr.sun_path)) - (char*) &Addr; + return 0; +} + +/*?nodoc?*/ +SockAddr* +UchUnixAddress :: GetSockAddr () +{ + return (SockAddr*) &Addr; +} + +/*?nodoc?*/ +char* +UchUnixAddress :: StrRepr (char* buf) +{ + sprintf (buf, "%s", Addr.sun_path); + return buf; +} + +#endif /* UNIX_SOCK */ + +// constructors for inet addresses +// inet address specified by: +// hostname / port# (ANY if hostname is NIL, LOOPBACK if hostname is "") +// hostid / port# (predefined hostids ANYADDR, ...) +// todo: +// hostname / service name ?? +// +/*?nodoc?*/ +UchInetAddress :: UchInetAddress () +: UchAddress () +{ + // nothing +} + +/*? +Construct an inet address, from a hostname and a port number. +The address is valid only if the host is valid. +If \var{host} is the empty string, the loopback host is used; +if it is the nil pointer, the wildcard is used. +If the port number is 0, it will be allocated by the system. +?*/ +UchInetAddress :: UchInetAddress (const char* name, sword port) +: UchAddress () +{ + struct hostent *host; + + if (name && *name) { + host = gethostbyname (name); + if (! host) + return; + memcpy (&Addr.sin_addr, host->h_addr, host->h_length); + } else + Addr.sin_addr.s_addr = name ? INADDR_LOOPBACK : INADDR_ANY; + Addr.sin_family = AF_INET; + Addr.sin_port = htons (port); + Valid = TRUE; +} + +/*? +Construct an inet address, from a host id (internet address) and a port number. +If the port number is 0, it is assigned by the system. +The host ids \var{^{ANYADDR}}, \var{^{LOOPBACK}} and \var{^{BROADCAST}} are predefined. +\var{ANYADDR} is used to receive messages from anywhere. +\var{LOOPBACK} is the local host address. +\var{BROADCAST} makes it possible to send data to all the hosts of a local area network. +?*/ +UchInetAddress :: UchInetAddress (lword host, sword port) +: UchAddress () +{ + Addr.sin_family = AF_INET; + Addr.sin_port = htons (port); + Addr.sin_addr.s_addr = host; + Valid = TRUE; +} + +/*?nodoc?*/ +UchInetAddress :: ~UchInetAddress () +{ +} + +/*?nodoc?*/ +int +UchInetAddress :: Family () +{ + return AF_INET; +} + +/*?nodoc?*/ +int +UchInetAddress :: Length () +{ + return sizeof (Addr); +} + +/*?nodoc?*/ +SockAddr* +UchInetAddress :: GetSockAddr () +{ + return (SockAddr*) &Addr; +} + +/*?nodoc?*/ +char* +UchInetAddress :: StrRepr (char* buf) +{ + struct hostent *host; + char *hname; + unsigned long saddr; + + saddr = Addr.sin_addr.s_addr; + host = gethostbyaddr ((char*) &saddr, sizeof (long), AF_INET); + if (host) + hname = host->h_name; + else + if (saddr == INADDR_ANY) + hname = "ANY"; + else if (saddr == INADDR_LOOPBACK) + hname = "LOOPBACK"; + else if (saddr == INADDR_BROADCAST) + hname = "BROADCAST"; + else + hname = "???"; + sprintf (buf, "%s::%d", hname, ntohs (Addr.sin_port)); + return buf; +} + +/*? +This is a global function (static member of class \typ{UchAddress}). +It creates an object of a derived class of \typ{UchAddress} from a generic address +(thus is cannot be replaced by a constructor). +A generic address is the following union of address structures +(it is typically returned by system calls like \fun{recvfrom}): +\begin{ccode} +typedef union { + struct sockaddr sa; // default + struct sockaddr_un su; // Unix + struct sockaddr_in si; // inet +} GEN_ADDR; +\end{ccode} +?*/ +UchAddress* +UchAddress :: Decode (GEN_ADDR* addr, int alen) +{ + switch (addr->sa.sa_family) { +#ifdef UNIX_SOCK + case AF_UNIX : + addr->su.sun_path [alen] = 0; + return new UchUnixAddress (addr->su.sun_path); +#endif + case AF_INET : + return new UchInetAddress (addr->si.sin_addr.s_addr, addr->si.sin_port); + + default : + return 0; + } +} + +/*? +This is a global function (static member of class \typ{UchInetAddress}. +It returns the internet address of the local host. +This is different from the predefined address \var{LOOPBACK}: +\var{LOOPBACK} is the same constant on each machine, so that it cannot +be communicated across the network. +The value returned by \fun{LoopBack} is the unique internet address of the local host. +Thus it can be communicated to another host across the network. +?*/ +lword +UchInetAddress :: LoopBack () +{ + static lword loopback = 0; + + if (loopback) + return loopback; + + struct hostent *host; + char name [128]; + + gethostname (name, sizeof (name)); + host = gethostbyname (name); + if (! host) { + Error (ErrFatal, "address", "cannot get address of local host"); + return 0; + } + return loopback = * ((lword *) host->h_addr); +} + +#ifdef DOC +// fake entries for inline functions + +/*? +Return TRUE if the address is valid. +?*/ +bool +UchAddress :: IsValid () +{} + +/*? +Return the host number of the address. +?*/ +lword +UchInetAddress :: Host () +{} + +/*? +Return the port number of the address. +?*/ +sword +UchInetAddress :: Port () +{} + +#endif /* DOC */ + diff --git a/comm/Address.h b/comm/Address.h new file mode 100644 index 0000000..b220339 --- /dev/null +++ b/comm/Address.h @@ -0,0 +1,118 @@ +/* + * The Unix Channel + * + * by Michel Beaudouin-Lafon + * + * Copyright 1990-1993 + * Laboratoire de Recherche en Informatique (LRI) + * + * Addresses + * + * $Id$ + * $CurLog$ + */ + +#ifndef Address_H_ +#define Address_H_ + +#include "cplus_bugs.h" +#include "global.h" +#include "ccu/SmartPointer.h" + +#include +#include +#include + +#ifdef UNIX_SOCK +#include +#endif + +#ifndef AF_UNIX +#define AF_UNIX AF_UNSPEC +#endif + + +typedef struct sockaddr SockAddr; + +/* this union is useful for adresses returned by system calls */ + +typedef union { + struct sockaddr sa; // default +#ifdef UNIX_SOCK + struct sockaddr_un su; // unix +#endif + struct sockaddr_in si; // inet +} GEN_ADDR; + + +class UchAddress : public CcuSmartData { +protected: + bool Valid; + +public: + UchAddress (); + ~UchAddress (); + + bool IsValid () const { return Valid; } +virtual int Family (); // AF_UNSPEC, AF_UNIX, AF_INET +virtual int Length (); +virtual SockAddr* GetSockAddr (); +virtual char* StrRepr (char* buf); + +static UchAddress* Decode (GEN_ADDR*, int); +}; + +PointerClass (pUchAddress, UchAddress) + +class UchInetAddress : public UchAddress { +protected: + struct sockaddr_in Addr; + +public: + UchInetAddress (); + UchInetAddress (lword, sword = 0); // hostid, port# + UchInetAddress (const char*, sword = 0); // hostname, port# + ~UchInetAddress (); + + int Family (); + int Length (); + SockAddr* GetSockAddr (); +inline sword Port () { return Addr.sin_port; } +inline lword Host () { return Addr.sin_addr.s_addr; } + char* StrRepr (char* buf); + +static lword LoopBack (); +}; + +#ifndef INADDR_LOOPBACK +#define INADDR_LOOPBACK (INET_ADDR::LoopBack ()) +#endif + +#define ANYADDR ((lword) INADDR_ANY) +#define LOOPBACK ((lword) INADDR_LOOPBACK) +#define BROADCAST ((lword) INADDR_BROADCAST) + + + +#ifdef UNIX_SOCK + +class UchUnixAddress : public UchAddress { +protected: + struct sockaddr_un Addr; + +public: + UchUnixAddress (); + UchUnixAddress (const char*); + ~UchUnixAddress (); + + int Family (); + int Length (); + SockAddr* GetSockAddr (); + char* StrRepr (char* buf); +}; + +#endif /* UNIX_ADDR */ + + +#endif /* Address_H_ */ + diff --git a/comm/Channel.cc b/comm/Channel.cc new file mode 100644 index 0000000..b8178aa --- /dev/null +++ b/comm/Channel.cc @@ -0,0 +1,402 @@ +/* + * The Unix Channel + * + * by Michel Beaudouin-Lafon + * + * Copyright 1990-1993 + * Laboratoire de Recherche en Informatique (LRI) + * + * File descriptors, channels + * + * $Id$ + * $CurLog$ + */ + +#include "Channel.h" +#include "MsgBuffer.h" +#include "error.h" + +#include +#include +#include + +CH_HANDLER UchChannel::ReadHandler = 0; +CH_HANDLER UchChannel::WriteHandler = 0; + +// +// this section implements refcounts for file descriptors, so that they +// are closed only when the last occurence is deleted +// +static int Refs [NFILE]; +static int Inited = 0; + +/*?nodoc?*/ +void +_AddRef (int fd) +{ + if (fd < 0) + return; + if (! Inited) { + Inited = 1; + for (int i = 0; i < NFILE; i++) + Refs [i] = 0; + Refs [0] = Refs [1] = Refs [2] = 1; + } + Refs [fd]++; +#ifdef SMART_DEBUG + dbgprintf ("AddRef %d -> %d\n", fd, Refs [fd]); +#endif +#ifdef _TRACE2 + _TRACE2 (fd, Refs [fd]); +#endif +} + +/*?nodoc?*/ +void +_DelRef (int fd) +{ + if (fd < 0) + return; + if (--Refs [fd] == 0) + close (fd); +#ifdef SMART_DEBUG + dbgprintf ("DelRef %d -> %d\n", fd, Refs [fd]); +#endif +#ifdef _TRACE2 + _TRACE2 (fd, Refs [fd]); +#endif +} + +/*?nodoc?*/ +char* +UchFilDes :: StrRepr (char* buf) +{ + sprintf (buf, "%d", Fd); + return buf; +} + +/*?class UchFilDes +This class implements objects representing Unix file descriptors. +When the associated file descriptor is -1, the \typ{UchFilDes} is said to be closed, +else it is said to be opened. +Because several objects of this class can refer to the same file descriptor, +the destructor closes the file descriptor only if it is the last object referring to this file descriptor. +This is implemented with the help of a reference count, the overloading of \fun{operator =} +and the definition of the constructor \fun{UchFilDes(UchFilDes &)}. +All member functions described here are inline for maximum efficiency. +?*/ + +#ifdef DOC + +/*? +Construct a closed file descriptor. +?*/ +UchFilDes :: UchFilDes () +{ } + +/*? +Construct an open file descriptor for the Unix file descriptor \var{fd}. +In particular, this makes it possible to pass an integer where a \typ{UchFilDes} is normally expected. +This is most useful when using Unix file descriptors. +?*/ +UchFilDes :: UchFilDes (int fd) +{ } + +/*? +This conversion operator makes it possible to pass a \typ{UchFilDes} argument where +an integer is expected. This is most useful for system calls. +?*/ +int +UchFilDes :: operator int () +{ } + +/*? +Return the Unix file descriptor, or -1 if it is closed. +?*/ +int +UchFilDes :: FilDes () +{ } + +/*? +Open the object on file descriptor \var{fd}. +If it was already opened, it is closed first. +?*/ +void +UchFilDes :: Open (int fd) +{ } + +/*? +Close a file descriptor. If it is opened, this actually closes the file descriptor +only if it was the last reference to it. +\fun{Close} is automatically called by the destructor. +?*/ +void +UchFilDes :: Close () +{ } + +/*? +Return TRUE if the object is opened, FALSE else. +?*/ +bool +UchFilDes :: Opened () +{ } + +/*? +Read at most \var{n} bytes into buffer \var{b} and return the number of bytes actually read. +This is the Unix \fun{read} system call. +For efficiency, this function does not test whether the file descriptor is opened. +?*/ +int +UchFilDes :: Read (byte* b, int n) +{ } + +/*? +Write at most \var{n} bytes of buffer \var{b} and return the number of bytes actually written. +This is the Unix \fun{write} system call. +For efficiency, this function does not test whether the file descriptor is opened. +?*/ +int +UchFilDes :: Write (byte* b, int n) +{ } + +#endif /* DOC */ + +/*?class UchChannel +A \typ{UchChannel} is basically a file descriptor, with more strict semantics than a \typ{UchFilDes}. +A channel is immutable: once initialized, it cannot change its file descriptor. +It has a mode, of type \typ{^{IOMODE}}, with possible values +\var{IONone}, \var{IORead}, \var{IOWrite}, \var{IOReadWrite}, +\var{IOSelect}, \var{IOReadSelect}, \var{IOWriteSelect}, \var{IOAll}. +\index{IOMODE :: IONone}\index{IOMODE :: IORead}\index{IOMODE :: IOWrite}\index{IOMODE :: IOReadWrite}\index{IOMODE :: IOSelect}\index{IOMODE :: IOReadSelect}\index{IOMODE :: IOWriteSelect}\index{IOMODE :: IOAll} +Before being initialized, a UchChannel.has mode \var{IONone} and is said to be closed. + +A UchChannel.has a number of virtual functions. +The destructor is virtual; this is useful for classes that store pointers to objects +of a class derived from \typ{UchChannel} (like in the class \typ{UchSocket}). +The functions \fun{HandleSelect}, \fun{HandleRead} and \fun{HandleWrite} +are intended to give a channel the ability to automatically handle input/output, +for instance for use with the \fun{select} system call. +Such capabilities are used and thus illustrated in class \typ{UchMultiplexer}. + +The class \typ{^{pUchChannel}} implements smart pointers to channels. +Smart pointers behave like pointers but they manage a reference count on the +pointed to objects for an automatic reclaim of dynamically allocated objects. +This is very useful especially in the class \typ{UchMultiplexer} because arrays of +(smart) pointers to buffers are used. +?*/ + +/*? +Construct a closed channel. +?*/ +UchChannel :: UchChannel () +: Fd (), + Mode (IONone) +{ +} + +/*? +Construct an open channel on file descriptor \var{fd} with mode \var{io}. +?*/ +UchChannel :: UchChannel (int fd, IOMODE io) +: Fd (fd), + Mode (io) +{ +} + +/*?nodoc?*/ +UchChannel :: UchChannel (const UchChannel& ch) +: Fd (ch.Fd), + Mode (ch.Mode) +{ +} + +#ifdef DOC +/*? +Open the channel on file descriptor \var{fd}. +A channel can only be opened once, and it cannot be closed. +This function is useful when the channel was created with the default contructor, +in order to associate a file descriptor to it. +Because a channel is immutable, this can be done only once. +?*/ +void +UchChannel :: Open (int fd) +{ } + +/*?nextdoc?*/ +IOMODE +UchChannel :: IOMode () +{ +} + +/*? +Return/set the mode of the channel. +?*/ +void +UchChannel :: SetMode (IOMODE io) +{ } + +#endif /* DOC */ + + +/*?nodoc?*/ +UchChannel :: ~UchChannel () +{ + // nothing special here +} + +/*?nodoc?*/ +UchChannel* +UchChannel :: Copy () const +{ + return new UchChannel (*this); +} + +/*?nextdoc?*/ +void +UchChannel :: AddNotify (UchMultiplexer&) +{ +} + +/*? +These virtual functions are called whenever a channel is added to (resp. removed from) +a multiplexer. The default implementation does nothing. +?*/ +void +UchChannel :: RemoveNotify (UchMultiplexer&) +{ +} + +/*?nextdoc?*/ +void +UchChannel :: HandleWrite () +{ + if (! UchChannel::WriteHandler) + Error (ErrFatal, "UchChannel::HandleWrite", ErrorTable [ErrShouldImplement]); + else + (* UchChannel::WriteHandler) (this); +} + +/*? +These virtual functions are called by a \typ{UchMultiplexer} when data can be written +to the channel or read from the channel. +They are are normally redefined in derived classes. +The default implementation is the following: +if the static members \var{UchChannel::ReadHandler} (resp. \var{UchChannel::WriteHandler}) is set, +it is called by \fun{HandleRead} (resp. \fun{HandleWrite}); +else an error message is output. +These members are public so that they can be assigned directly at any time. +By default they are not set. +The type of these static members is \typ{^{CH_HANDLER}}: +pointer to void function with one argument of type \typ{UchChannel*}. +?*/ +void +UchChannel :: HandleRead () +{ + if (! UchChannel::ReadHandler) + Error (ErrFatal, "UchChannel::HandleRead", ErrorTable [ErrShouldImplement]); + else + (* UchChannel::ReadHandler) (this); +} + +/*? +This virtual function is called by a \typ{UchMultiplexer} before making a \fun{select} call. +It is intended to handle any background task or buffered input/output associated to the channel. +If it returns TRUE, the channel set scan functions will return before performing the select call. +See the class \typ{UchMultiplexer} for more details. +The default implementation does nothing but returning FALSE. +?*/ +bool +UchChannel :: HandleSelect () +{ + return FALSE; +} + +#ifdef DOC + +/*?nextdoc?*/ +int +UchChannel :: Read (byte* b, int n) +{ } + +/*? +The Unix \fun{read} and \fun{write} system calls, as in class \typ{UchFilDes}. +?*/ +int +UchChannel :: Write (byte* b, int n) +{ } + +#endif /* DOC */ + +/*?nextdoc?*/ +int +UchChannel :: Read (UchMsgBuffer& b) +{ + int l = b.FreeLength (); + if (! l) + return -2; + int n = read (Fd, (char*) b.Free (), l); + if (n > 0) + b.More (n); + return n; +} + +/*? +The Unix \fun{read} and \fun{write} system calls applied to a \typ{UchMsgBuffer}. +They return the number of bytes transferred, -1 if a system error occurred, +-2 if the buffer is empty when writing or full when reading. +?*/ +int +UchChannel :: Write (UchMsgBuffer& b) +{ + int l = b.BufLength (); + if (! l) + return -2; + int n = write (Fd, (const char*) b.Buffer (), l); + if (n > 0) + b.Flush (n); + return n; +} + +/*?nextdoc?*/ +bool +UchChannel :: ReadBuffer (UchMsgBuffer& b) +{ + int n; + errno = 0; + while ((n = Read (b)) > 0); + return bool (n == -2); +} + +/*? +These functions repeatedly call \fun{Read} or \fun{Write} until the whole buffer is +transferred or a system error occurred. In case of an error, they return FALSE, else TRUE. +?*/ +bool +UchChannel :: WriteBuffer (UchMsgBuffer& b) +{ + int n; + errno = 0; + while ((n = Write (b)) > 0); + return bool (n == -2); +} + +/*? +This function is intended for sockets that accept connections. +It returns a dynamically allocated new channel that is opened on the accepted connection. +If it fails, it returns 0. +?*/ +UchChannel* +UchChannel :: Accept () +{ + int fd; + + errno = 0; + if (Fd < 0) + return (UchChannel*) 0; + + if ((fd = accept (Fd, 0, 0)) < 0) + return (UchChannel*) 0; + + return new UchChannel (fd); +} + diff --git a/comm/Channel.h b/comm/Channel.h new file mode 100644 index 0000000..302d05d --- /dev/null +++ b/comm/Channel.h @@ -0,0 +1,114 @@ +/* + * The Unix Channel + * + * by Michel Beaudouin-Lafon + * + * Copyright 1990-1993 + * Laboratoire de Recherche en Informatique (LRI) + * + * File descriptors, channels + * + * $Id$ + * $CurLog$ + */ + +#ifndef Channel_H_ +#define Channel_H_ + +#include "cplus_bugs.h" +#include "global.h" +#include "ccu/SmartPointer.h" + +// we need read and write system calls +#include + +class UchMsgBuffer; + +#ifndef NFILE +#define NFILE 20 +#endif + +enum IOMODE { IONone = 0, IORead = 1, IOWrite = 2, + IOReadWrite = 3, IOSelect = 4, IOReadSelect = 5, + IOWriteSelect = 6, IOAll = 7 }; + +// Declarations for inlines +// +extern void _AddRef (int); +extern void _DelRef (int); + +extern char* StrReprBuf; + +class UchFilDes { +protected: + int Fd; +public: +inline UchFilDes () : Fd (-1) { } +inline UchFilDes (const UchFilDes& fd) { _AddRef (Fd = fd.Fd); } +inline UchFilDes (int fd) { _AddRef (Fd = fd); } +inline ~UchFilDes () { _DelRef (Fd); } + +inline operator int () const { return Fd; } +inline UchFilDes& operator = (const UchFilDes& fd) { _DelRef (Fd); Fd = fd.Fd; _AddRef (Fd); return *this; } +inline int FilDes () { return Fd; } +inline void Open (int fd) { _DelRef (Fd); _AddRef (Fd = fd); } +inline void Close () { _DelRef (Fd); Fd = -1; } +inline bool Opened () { return bool (Fd >= 0); } +inline int Read (byte* b, int n) { return read (Fd, (char*) b, n); } +inline int Write (byte* b, int n) { return write (Fd, (const char*) b, n); } + char* StrRepr (char* = StrReprBuf); +}; + +// This class defines channels, ie file descriptors with handlers for read/write. +// The file descriptor of a channel is immutable: once created, it cannot change; +// this is crucial for channel sets, for instance. +// However, because Copy and operator= are defined,immutability can be +// potentially bypassed... +// UchChannel also derives from CcuSmartData, for building smart pointers +// +class UchChannel; +typedef void (* CH_HANDLER) (UchChannel *); +class UchMultiplexer; + +class UchChannel : public CcuSmartData { +friend class UchMultiplexer; +protected: + UchFilDes Fd; + IOMODE Mode; + + void AddNotify (UchMultiplexer&); + void RemoveNotify (UchMultiplexer&); + +public: + UchChannel (); + UchChannel (int, IOMODE = IOReadWrite); + UchChannel (const UchChannel& ch); + ~UchChannel (); + +inline void Open (int fd) { if (Fd < 0) Fd.Open (fd); } +inline IOMODE IOMode () const { return Mode; } +inline void SetMode (IOMODE io) { Mode = io; } + int Read (UchMsgBuffer&); + int Write (UchMsgBuffer&); + bool ReadBuffer (UchMsgBuffer&); + bool WriteBuffer (UchMsgBuffer&); + UchChannel* Accept (); +virtual UchChannel* Copy () const; +virtual void HandleWrite (); +virtual void HandleRead (); +virtual bool HandleSelect (); + +inline int FilDes () const { return Fd; } +inline int Read (byte* b, int n) { return read (Fd, (char*) b, n); } +inline int Write (byte* b, int n) { return write (Fd, (const char*) b, n); } +inline operator int () { return Fd; } +inline char* StrRepr (char* s = StrReprBuf) { return Fd.StrRepr (s); } + +static CH_HANDLER ReadHandler; +static CH_HANDLER WriteHandler; +}; + +PointerClass (pUchChannel, UchChannel) + +#endif /* Channel_H_ */ + diff --git a/comm/Datagram.cc b/comm/Datagram.cc new file mode 100644 index 0000000..b88deb5 --- /dev/null +++ b/comm/Datagram.cc @@ -0,0 +1,166 @@ +/* + * The Unix Channel + * + * by Michel Beaudouin-Lafon + * + * Copyright 1990-1993 + * Laboratoire de Recherche en Informatique (LRI) + * + * Datagrams + * + * $Id$ + * $CurLog$ + */ + +#include "Datagram.h" +#include "MsgBuffer.h" +#include +#include + +/*?class UchDatagram +A datagram socket can send to and receive from any other datagram socket, +unless it is connected. +Thus, establishing a datagram connection is simple. + +UchDatagram sockets are not reliable: messages can be lost, duplicated, or be received in a different order. +They keep the message boundaries: when \var{n} bytes are written, you will read at most \var{n} bytes; +but if you ask to read less than \var{n} bytes, then the end of the message will be lost. + +When a datagram socket is not connected, you must provide an address when you send a message; +when a message is read, the address of the sender can be retrieved (with function \fun{From}). +When a datagram socket is connected, messages can only be sent to and read from the +connected address. The \fun{Read} and \fun{Write} calls can be used in this case. + +Before any data can be sent or received, the socket must be set up with \fun{Setup}. +?*/ + +/*?nextdoc?*/ +UchDatagram :: UchDatagram () +: UchSocket (), + FAddr (0) +{ +} + +/*? +These constructors are similar to those of the class \typ{UchSocket}. +?*/ +UchDatagram :: UchDatagram (UchAddress* bound, UchAddress* connected) +: UchSocket (bound, connected), + FAddr (0) +{ +} + +/*?nodoc?*/ +UchDatagram :: UchDatagram (const UchDatagram& d) +: UchSocket (d), + FAddr (d.FAddr) +{ +} + +/*?nodoc?*/ +UchDatagram :: ~UchDatagram () +{ +} + +#ifdef DOC +/*? +Return the address of the sender of the last received message. +?*/ +UchAddress* +UchDatagram :: From () +{ } + +#endif /* DOC */ + +/*?nodoc?*/ +UchChannel* +UchDatagram :: Copy () const +{ + return new UchDatagram (*this); +} + +/*?nodoc?*/ +int +UchDatagram :: SockType () +{ + return SOCK_DGRAM; +} + +/*? +Send \var{len} bytes of \var{buf} on the datagram, to a destination address \var{to}. +Return the number of bytes actually transferred, or -1 if a system error occurred. +If the datagram is connected, you must use \fun{Write} instead. +?*/ +int +UchDatagram :: Send (byte* buf, int len, UchAddress& to) +{ + return sendto (FilDes (), (char*) buf, len, 0, to.GetSockAddr (), to.Length ()); +} + +/*? +Receive at most \var{len} bytes into \var{buf}. +Return the number of bytes actually transferred, or -1 if a system error occurred. +The address of the sender can be retrieved with \fun{From}. +If the socket is connected, you must use \fun{Read} instead. +?*/ +int +UchDatagram :: Receive (byte* buf, int len) +{ + GEN_ADDR addr; + int alen = sizeof (addr); + int ret; + + ret = recvfrom (FilDes (), (char*) buf, len, 0, &addr.sa, &alen); + if (ret < 0) + return ret; + + FAddr = UchAddress::Decode (&addr, alen); + return ret; +} + +/*? +This is equivalent to \fun{Send(buf, len, From ())}: +it sends \fun{len} bytes to the sender of the last received message. +If there is no such sender, it returns -1. +?*/ +int +UchDatagram :: Reply (byte* buf, int len) +{ + if (! FAddr) + return -1; + return sendto (FilDes (), (char*) buf, len, 0, FAddr->GetSockAddr (), FAddr->Length ()); +} + +/*?nextdoc?*/ +int +UchDatagram :: Send (UchMsgBuffer& buf, UchAddress& to, bool peek) +{ + int n = Send (buf.Buffer (), buf.BufLength (), to); + if (! peek) + buf.Flush (n); + return n; +} + +/*?nextdoc?*/ +int +UchDatagram :: Receive (UchMsgBuffer& buf) +{ + int n = Receive (buf.Free (), buf.FreeLength ()); + if (n > 0) + buf.More (n); + return n; +} + +/*? +The same functions but with a \typ{UchMsgBuffer} argument instead of a byte pointer and size. +As usual, if \var{peek} is TRUE the buffer is not flushed. +?*/ +int +UchDatagram :: Reply (UchMsgBuffer& buf, bool peek) +{ + int n = Reply (buf.Buffer (), buf.BufLength ()); + if (! peek) + buf.Flush (n); + return n; +} + diff --git a/comm/Datagram.h b/comm/Datagram.h new file mode 100644 index 0000000..3ac6b36 --- /dev/null +++ b/comm/Datagram.h @@ -0,0 +1,40 @@ +/* + * The Unix Channel + * + * by Michel Beaudouin-Lafon + * + * Copyright 1990-1993 + * Laboratoire de Recherche en Informatique (LRI) + * + * Datagrams + * + * $Id$ + * $CurLog$ + */ + +#ifndef Datagram_H_ +#define Datagram_H_ + +#include "cplus_bugs.h" +#include "Socket.h" + +class UchDatagram : public UchSocket { +protected: + pUchAddress FAddr; +public: + UchDatagram (); + UchDatagram (UchAddress*, UchAddress*); + ~UchDatagram (); + UchDatagram (const UchDatagram& d); + UchChannel* Copy () const; + int SockType (); + int Send (byte*, int, UchAddress&); + int Receive (byte*, int); +inline UchAddress* From () { return FAddr; } + int Reply (byte*, int); + int Send (UchMsgBuffer& b, UchAddress& a, bool = FALSE); + int Receive (UchMsgBuffer& b); + int Reply (UchMsgBuffer& b, bool = FALSE); +}; + +#endif /* Datagram_H_ */ diff --git a/comm/Imakefile b/comm/Imakefile new file mode 100644 index 0000000..ee86832 --- /dev/null +++ b/comm/Imakefile @@ -0,0 +1,99 @@ +# +# The Unix Channel +# +# by Michel Beaudouin-Lafon +# +# Copyright 1990-1993 +# Laboratoire de Recherche en Informatique (LRI) +# +# Imakefile +# +# $Id$ +# $CurLog$ +# + + CXXFLAGS = $(CXXOPTIONS) -I. -I$(LOCINCL) -DUNIX_SOCK + + cc = $(CXXSUFFIX) + SRC = error.$(cc) MsgBuffer.$(cc) Message.$(cc) Channel.$(cc) \ + TimeOut.$(cc) Multiplexer.$(cc) Address.$(cc) \ + Socket.$(cc) Datagram.$(cc) Stream.$(cc) MsgStream.$(cc) dgram.$(cc) \ + Event.$(cc) Server.$(cc) Service.$(cc) Agent.$(cc) TextStream.$(cc) \ + PortServer.$(cc) PortServerReq.$(cc) + + HDR = error.h MsgBuffer.h Message.h Channel.h TimeOut.h Multiplexer.h \ + Address.h Socket.h Datagram.h Stream.h MsgStream.h dgram.h \ + Event.h Server.h Service.h Agent.h TextStream.h PortServer.h + + + UCHOBJ = error.o MsgBuffer.o Message.o Channel.o TimeOut.o Multiplexer.o \ + Address.o Socket.o Datagram.o Stream.o MsgStream.o dgram.o \ + Event.o Server.o Service.o Agent.o TextStream.o \ + PortServer.o PortServerReq.o + + CHANOBJ = error.o MsgBuffer.o Message.o Channel.o Multiplexer.o + + UCHHDR = version.h \ + $(LOCINCL)/ccu/SmartPointer.h $(LOCINCL)/ccu/List.h \ + $(LOCINCL)/ccu/bool.h \ + $(LOCINCL)/ccu/word.h $(LOCINCL)/ccu/IdTable.h \ + $(LOCINCL)/ccu/String.h \ + $(LOCINCL)/ccu/Time.h $(LOCINCL)/ccu/Timer.h\ + global.h error.h MsgBuffer.h Message.h \ + Channel.h Multiplexer.h TimeOut.h \ + Address.h Socket.h Datagram.h Stream.h MsgStream.h dgram.h \ + Event.h Server.h Service.h Agent.h TextStream.h PortServer.h + + CHANHDR = version.h \ + $(LOCINCL)/ccu/bool.h \ + $(LOCINCL)/ccu/word.h \ + $(LOCINCL)/ccu/SmartPointer.h $(LOCINCL)/ccu/List.h \ + $(LOCINCL)/ccu/Time.h $(LOCINCL)/ccu/Timer.h\ + global.h error.h MsgBuffer.h Message.h \ + Channel.h Multiplexer.h + + LLIB = $(LOCLIB)/libCcu.a + + LEX = lex + YACC = yacc # or bison -y + + DOC = ../../DOC/UCH + DISTRDOC = ../../DOC.distr + +CxxRule () + +# default target is 'chan' or 'chan comm' +all : UchTarget + +comm : $(LOCLIB)/libUch.a $(LOCBIN)/portserv $(LOCBIN)/porttest $(LOCINCL)/uch.h + +chan : $(LOCLIB)/libChan.a $(LOCINCL)/chan.h + +local : libUch.a libChan.a uch.h chan.h portserv porttest + +LibraryTarget (Uch, $(UCHOBJ)) +LibraryTarget (Chan, $(CHANOBJ)) + +YaccTarget (ReqMgr,ReqMgr.yacc) +LexTarget (ReqMgr,ReqMgr.lex) +ReqMgr.lex.o: ReqMgr.lex.cc ReqMgr.yacc.h + +ProgramTarget (portserv, portserv.o, libUch.a) +ProgramTarget (porttest, porttest.o, libUch.a) +ProgramTarget (test, test.o, libUch.a) +ProgramTarget (reqgen, reqgen.o ReqMgr.o ReqMgr.yacc.o ReqMgr.lex.o, ) + +GenHeaderTarget (uch.h, $(UCHHDR)) +GenHeaderTarget (chan.h, $(CHANHDR)) + +CopyLibsTarget ($(LOCLIB),Uch) +CopyLibsTarget ($(LOCLIB),Chan) + +CopyTarget ($(LOCINCL)/uch.h, uch.h) +CopyTarget ($(LOCINCL)/chan.h, chan.h) +CopyTarget ($(LOCBIN)/portserv, portserv) +CopyTarget ($(LOCBIN)/porttest, porttest) + +DocRule ("The Unix Channel") +TeXRule () +DistrDocRule (UCH) diff --git a/comm/Message.cc b/comm/Message.cc new file mode 100644 index 0000000..54cd771 --- /dev/null +++ b/comm/Message.cc @@ -0,0 +1,98 @@ +/* + * The Unix Channel + * + * by Michel Beaudouin-Lafon + * + * Copyright 1990-1993 + * Laboratoire de Recherche en Informatique (LRI) + * + * Messages + * + * $Id$ + * $CurLog$ + */ + +#include "cplus_bugs.h" + +#include "Message.h" + +/*?class UchMessage +\typ{UchMessage} is the virtual base class for messages. +Each subclass must redefine the virtual functions \fun{WriteTo} and \fun{ReadFrom}. +Messages can be added to buffers and can be retrieved from buffers. +?*/ + +/*?nodoc?*/ +UchMessage :: UchMessage () +{ + // nothing +} + +/*?nodoc?*/ +UchMessage :: ~UchMessage () +{ +} + +/*? +This virtual function must write a message into a buffer. +Usually, the implementation of this function in a derived class starts by calling +the same function of its base class, because a derived class inherits the fields +of its base class and adds its own fields. +Data is usually appended to the buffer by the \fun{UchMsgBuffer::Append} functions or operator <<. +In class \typ{UchMessage}, this function does nothing. +?*/ +void +UchMessage :: WriteTo (UchMsgBuffer&) +{ + // nothing +} + +/*? +This virtual function must extract data from the buffer to create the message. +At most \var{len} bytes should be read from the buffer. +Usually, the implementation of this function in a derived class starts by calling +the same function of its base class, because a derived class inherits the fields +of its base class and adds its own fields. +Data is usually retrieved from the buffer by the \fun{UchMsgBuffer::Get} functions or operator >>. +In class \typ{UchMessage}, this function does nothing. +?*/ +void +UchMessage :: ReadFrom (UchMsgBuffer&, lword) +{ +} + +/*? +This virtual function can be redefined in derived classes to support the handling of messages +after they have been loaded from a buffer. The function \typ{MyClient}::\fun{NewMessage} +may then look like: +\begin{ccode} +void +MyClient :: NewMessage (UchMsgBuffer& buffer, bool ask) +{ + MyRequest* m = 0; + lword type; + buffer.MsgPeek (&type); + switch (type) { + case MyFirstReqType: + m = new MyFirstRequest; + break; + case ... + ... + } + if (m) { + buffer.Get (m); + m->Activate (); + return TRUE; + } + return FALSE; +} +\end{ccode} +However, this virtual function is only provided as a facility and is not used +by \uch. Redefining it is not mandatory. +?*/ +void +UchMessage :: Activate () +{ +} + + diff --git a/comm/Message.h b/comm/Message.h new file mode 100644 index 0000000..a254f1e --- /dev/null +++ b/comm/Message.h @@ -0,0 +1,32 @@ +/* + * The Unix Channel + * + * by Michel Beaudouin-Lafon + * + * Copyright 1990-1993 + * Laboratoire de Recherche en Informatique (LRI) + * + * Messages + * + * $Id$ + * $CurLog$ + */ + +#ifndef Message_H_ +#define Message_H_ + +#include "global.h" + +class UchMsgBuffer; + +class UchMessage { +public: + UchMessage (); +virtual ~UchMessage (); +virtual void WriteTo (UchMsgBuffer&); +virtual void ReadFrom (UchMsgBuffer&, lword); +virtual void Activate (); +}; + +#endif /* Message_H_ */ + diff --git a/comm/MsgBuffer.cc b/comm/MsgBuffer.cc new file mode 100644 index 0000000..0aefd6c --- /dev/null +++ b/comm/MsgBuffer.cc @@ -0,0 +1,707 @@ +/* + * The Unix Channel + * + * by Michel Beaudouin-Lafon + * + * Copyright 1990-1993 + * Laboratoire de Recherche en Informatique (LRI) + * + * Messages, buffers + * + * $Id$ + * $CurLog$ + */ + +#include "MsgBuffer.h" +#include "Message.h" + +#include +#include +#include + +// management of buffers +// The buffer is said to be allocated if Begin is non zero. +// There is a special constructor that builds an allocated buffer with Begin = 0 : +// UchMsgBuffer (UchMsgBuffer&, int len) +// This buffer can only be read from and flushed +// This is useful for creating a fake buffer that is actually a pointer to +// a subpart of the original one. Thus, the new buffer can be safely read from +// without eating too far away +// + +/*?class UchMsgBuffer +A \typ{UchMsgBuffer} is an array of bytes of a given size that can be partially filled. +The buffer may grow whenever bytes are appended to it. +The initial size, the increments and the maximum sizes can be defined. +If the buffer exceeds its maximal size, it will be deleted when it becomes empty +in order to give it a smaller size. + +Data can be appended and extracted from the buffer by \fun{operator <<} and \fun{operator >>}. +When extracting data, the buffer may not contain enough bytes. +We say that a ``get error'' has occurred and the buffer remembers it until it is modified +by the functions \fun{Append} or \fun{More}, or by \fun{operator <<}. + +The class \typ{^{pUchMsgBuffer}} implements smart pointers to buffers. +Smart pointers behave like pointers but they manage a reference count on the +pointed to objects for an automatic reclaim of dynamically allocated objects. + +\fig{buffer}{Some operations on a buffer.} +?*/ + +static int padding [] = { 0, 1, 2, 3}; +inline int pad (int sz) { return sz + padding [sz & 03]; } + +/*? +Construct an empty buffer. +The buffer will be allocated with a default size whenever data is appended to it. +This default size is currently 128 bytes. +?*/ +UchMsgBuffer :: UchMsgBuffer () +{ + Begin = Start = Stop = End = 0; + GetErr = FALSE; + GrowSize = MinSize = 128; + MaxSize = 0; +} + +/*? +Construct a buffer with \var{sz} bytes. +?*/ +UchMsgBuffer :: UchMsgBuffer (int sz) +{ + MinSize = GrowSize = sz = pad (sz); + Begin = Start = Stop = new byte [sz]; + End = Begin + sz; + GetErr = FALSE; + MaxSize = 0; +} + +/*? +Construct a buffer with minimum \var{sz} bytes, growing by \var{grow} bytes, +until a maximum size of \var{max} bytes. +?*/ +UchMsgBuffer :: UchMsgBuffer (int sz, int grow, int max) +{ + MinSize = sz = pad (sz); + Begin = Start = Stop = new byte [sz]; + End = Begin + sz; + GetErr = FALSE; + GrowSize = grow; + MaxSize = max; +} + +/*? +Construct a so called fake buffer, i.e. a buffer whose contents is made of the +first \var{l} characters of buffer \var{b} (or the whole buffer if \var{l} is zero). +The contents of the buffers is actually shared until the fake buffer is destructed +or data is appended to it. +Fake buffers are mainly used to extract data without exceeding a given size and +without making a copy. +Note that the contents of the fake buffer can be overwritten if you read and then append to +the original buffer. +The rule of thumb is to avoid modifying the original buffer while there is a fake buffer on it. +?*/ +UchMsgBuffer :: UchMsgBuffer (const UchMsgBuffer& b, int l) +{ + Begin = 0; + Start = b.Start; + if (l && Start + l <= b.Stop) + End = Stop = Start + l; + else + End = Stop = b.Stop; + GetErr = FALSE; + GrowSize = b.GrowSize; + MinSize = b.MinSize; + MaxSize = b.MaxSize; +} + +/*?nodoc?*/ +UchMsgBuffer :: ~UchMsgBuffer () +{ + Delete (); +} + +/*? +Delete a buffer, i.e. free its internal byte buffer. +The buffer can still be used after this function has been called (it will reallocate the byte buffer). +The destructor calls this function. +?*/ +void +UchMsgBuffer :: Delete () +{ + if (Begin) + delete Begin; + Begin = Start = Stop = End = 0; +} + +/*? +Insure that a UchMsgBuffer.has at least \var{sz} free bytes. +The buffer may be reallocated with a new size to provide the necessary space. +?*/ +void +UchMsgBuffer :: NeedSize (int sz) +{ + // brand new buffer + if (! Begin) { + if (End) // ! Begin && End => fake buffer + return; + if ((sz = pad (sz)) < MinSize) + sz = MinSize; + Begin = Start = Stop = new byte [sz]; + End = Begin + sz; + return; + } + + // if enough space, do nothing + if (End - Stop >= sz) + return; + + // try to compact + int l = Stop - Start; + if (l && (Start - Begin) + (End - Stop) >= sz) { + memcpy (Begin, Start, l); + Start = Begin; + Stop = Start + l; + return; + } + + // sigh! allocate new + sz = pad (sz + ((l < GrowSize) ? GrowSize : l)); + byte* b = new byte [sz]; + if (l) + memcpy (b, Start, l); + delete Begin; + Begin = Start = b; + Stop = Start + l; + End = Begin + sz; +} + +/*? +Discard the \var{n} first bytes of a buffer, or the whole buffer if \var{n} is negative. +If the buffer becomes empty and had exceeded its maximum size, it is reset. +?*/ +void +UchMsgBuffer :: Flush (int n) +{ +//printf ("UchMsgBuffer %x : Flush (%d)\n", this, n); + if (! Begin) + if (! End) // ! Begin && End => fake buffer + return; + if (n < 0 || Start + n >= Stop) { + Start = Stop = Begin; + // free if too big + if (MaxSize > 0 && End - Start >= MaxSize) + Delete (); + } else + Start += n; +//printf ("flushed\n"); +} + +/*? +Flush the buffer until \var{p}. +This can be used when the UchMsgBuffer.has been accessed through \fun{Buffer ()} +and you want to flush until a given position in the buffer. +?*/ +void +UchMsgBuffer :: Flush (byte* p) +{ + if (p >= Start && p <= Stop) + Flush (p - Start); +} + +/*? +Set the minimum, increment and maximum size of a buffer (in bytes). +If an argument is negative, the corresponding size is not changed. +If an argument is zero, a default value is used: +128 for minimum and increment, 0 for maximum. +Sizes are rounded to the nearest multiple of 4. +If the maximum size is 0, there is no upper bound on the size of the buffer. +Note that the buffer can always grow as big as necessary to hold the appended data; +the maximum size is only used to reallocate the buffer whenever it becomes empty. +?*/ +void +UchMsgBuffer :: SetSizes (int min, int grow, int max) +{ + if (min >= 0) + MinSize = pad (min); + if (grow >= 0) + GrowSize = pad (grow); + if (max >= 0) + MaxSize = pad (grow); + if (min == 0) + MinSize = 128; + if (grow == 0) + GrowSize = 128; +} + +//----- append operations +// + +/*? +Append the \var{n} first bytes of \var{buf} to the buffer. +All append functions call \fun{NeedSize} so that appending always succeeds. +?*/ +void +UchMsgBuffer :: Append (const byte* buf, int n) +{ + GetErr = FALSE; + if (! Begin || End - Stop < n) + NeedSize (n); + memcpy (Stop, buf, n); + Stop += n; +} + +/*?nextdoc?*/ +void +UchMsgBuffer :: Append (byte b) +{ + GetErr = FALSE; + if (! Begin || End - Stop < 1) + NeedSize (1); + *Stop++ = b; +} + +#ifdef DOC + +/*?nextdoc?*/ +void +UchMsgBuffer :: Append (char c) +{ } + +/*?nextdoc?*/ +void +UchMsgBuffer :: Append (lword l) +{ } + +/*? +Append a short word, a long word, a byte or a character to a buffer. +The buffer is extended as necessary. +?*/ +void +UchMsgBuffer :: Append (sword s) +{ } + +#endif /* DOC */ + +/*? +Append a string to a buffer. +If the second argument is \var{TRUE}, the terminating null byte is appended, else it is not. +If the string is the null pointer, nothing is appended to the buffer. +?*/ +void +UchMsgBuffer :: Append (const char* s, bool nullbyte) +{ + if (! s) + return; + GetErr = FALSE; + int l = strlen (s) + (nullbyte ? 1 : 0); + if (! Begin || End - Stop < l) + NeedSize (l); + memcpy (Stop, s, l); + Stop += l; +} + +/*? +Append a message \var{msg} to the buffer. +The virtual function \fun{WriteTo} of the buffer is called. +The length of the message is automatically computed and prepended to the message itself. +?*/ +void +UchMsgBuffer :: Append (UchMessage& msg) +{ + GetErr = FALSE; + + // reserve space for length + if (! Begin || End - Stop < lwsize) + NeedSize (lwsize); + // save current position (saving Start is wrong because it may move) + lword l = Stop - Start; + // skip space for length (this means that the length includes the 4 bytes of the length itself) + Stop += lwsize; + + // append message to buffer + msg.WriteTo (*this); + + // now patch the buffer ... + // retrieve position + byte* p = Start + l; + // compute length + lword len = (Stop - Start) - l; + // and patch + memcpy (p, &len, lwsize); +} + +//----- get operations +// + +/*? +Extract exactly \var{n} bytes from the buffer and copy them into \var{b}. +Return TRUE if the bytes were extracted, else return FALSE and mark the buffer with the get error flag. +If \var{peek} is TRUE, the buffer is not flushed after extracting the data +(i.e. the extracted data is still in the buffer). +?*/ +bool +UchMsgBuffer :: Get (byte* b, int n, bool peek) +{ +//printf ("UchMsgBuffer %x : Get %d bytes\n", this, n); + if (GetErr) + return FALSE; + if (Stop - Start < n) { + GetErr = TRUE; +//printf ("Get failed : %d bytes\n", Stop - Start); + return FALSE; + } + memcpy (b, Start, n); + if (! peek) + Flush (n); + return TRUE; +} + +/*?nextdoc?*/ +bool +UchMsgBuffer :: Get (byte* b, bool peek) +{ +//printf ("UchMsgBuffer %x : Get byte\n", this); + if (GetErr) + return FALSE; + if (Stop - Start < 1) { + GetErr = TRUE; +//printf ("Get failed : %d bytes\n", Stop - Start); + return FALSE; + } + *b = *Start; + if (! peek) + Flush (1); + return TRUE; +} + +#ifdef DOC + +/*?nextdoc?*/ +bool +UchMsgBuffer :: Get (char* c, bool peek) +{ +} + +/*?nextdoc?*/ +bool +UchMsgBuffer :: Get (sword* s, bool peek) +{ } + +/*? +Extract a long word, a short word, a byte, or a character from the buffer into the argument. +Return TRUE if the data was actually extracted, else return FALSE and mark the buffer with the get error flag. +If \var{peek} is TRUE, the buffer is not flushed after extracting the data. +?*/ +bool +UchMsgBuffer :: Get (lword* l, bool peek) +{ +} + +#endif /* DOC */ + +/*?nextdoc?*/ +int +UchMsgBuffer :: Get (char* str, int n, char delim, bool peek) +{ + char ds [2]; + ds [0] = delim; + ds [1] = 0; + return Get (str, n, ds, peek); +} + +/*? +Get at most \var{n} characters from the buffer into \var{str}. +If \var{n} is negative, no limit other than the size of the buffer +is imposed. +If \var{delim} is non null, it is a delimiter or a string of delimiters: +the buffer is transferred until a delimiter is encountered. +A null character always terminates the transfer. +If \var{peek} is TRUE, the buffer is not flushed after extracting the data. +The number of characters actually transferred is returned. +\var{str} is always terminated by a null character. +?*/ +int +UchMsgBuffer :: Get (char* str, int n, const char* delim, bool peek) +{ + if (GetErr) + return 0; + if (n < 0 || Stop - Start < n) + n = Stop - Start; + if (! n) + return 0; + + int i = 0; + char*q = str; + for (char* p = (char*) Start; *p && n; n--, i++) { + if (delim) { + for (const char *s = delim; *s; s++) + if (*s == *p) + break; + if (*s) + break; + } + *q++ = *p++; + } + *q = '\0'; + if (! peek) + Flush (i); + return i; +} + +/*? +Get a message from the buffer. +The message must be prefixed by its length, as done by \fun{Append(UchMessage*)}. +The virtual function \fun{ReadFrom} of the message is called to convert the buffer into a message. +If there is not enough data in the buffer, FALSE is returned. +If \fun{ReadFrom} reads past the end of the message, +FALSE is returned and the get error flag is set. +?*/ +bool +UchMsgBuffer :: Get (UchMessage* msg) +{ + if (GetErr || (Stop - Start < lwsize)) + return FALSE; + + // get length + lword l = Stop - Start; + lword len = 0; + memcpy ((byte*) &len, Start, lwsize); + + // check that buffer is big enough + if (l < len) + return FALSE; + + // read from buffer + Start += lwsize; + msg->ReadFrom (*this, len); + + // align to length + lword rl = l - (Stop - Start); + if (rl == len) { + return TRUE; + } else + if (rl < len) { + Flush ((int) len - (int) rl); + return TRUE; + } else { + GetErr = TRUE; + return FALSE; + } +} + +//------ peek operations + +/*?nextdoc?*/ +bool +UchMsgBuffer :: Peek (byte* b, lword offset) +{ + if (Stop - Start < offset + 1) + return FALSE; + *b = *(Start + offset); + return TRUE; +} + +/*?nextdoc?*/ +bool +UchMsgBuffer :: Peek (sword* sw, lword offset) +{ + if (Stop - Start < offset + swsize) + return FALSE; + memcpy (sw, Start + offset, swsize); + return TRUE; +} + +/*?nextdoc?*/ +bool +UchMsgBuffer :: Peek (lword* lw, lword offset) +{ + if (Stop - Start < offset + lwsize) + return FALSE; + memcpy (lw, Start + offset, lwsize); + return TRUE; +} + +/*? +These functions extract a byte, a short word, a long word, a byte string +at \var{offset} bytes from the start of the buffer. +They return FALSE if the data to peek falls outside the buffer, else TRUE. +?*/ +bool +UchMsgBuffer :: Peek (int sz, byte* b, lword offset) +{ + if (Stop - Start < offset + sz) + return FALSE; + memcpy (b, Start + offset, sz); + return TRUE; +} + +#ifdef DOC + +/*?nextdoc?*/ +bool +UchMsgBuffer :: MsgPeek (byte* b, lword offset) +{ } + +/*?nextdoc?*/ +bool +UchMsgBuffer :: MsgPeek (sword* sw, lword offset) +{ } + +/*?nextdoc?*/ +bool +UchMsgBuffer :: MsgPeek (lword* lw, lword offset) +{ } + +/*? +These function are similar to the \fun{Peek} functions; +they must be used when the buffer contains a message: +a message is always prefixed by its length, thus changing the origin of the offset. +Note that the length itself can be extracted with \fun{Peek(lword*, 0)}. +?*/ +bool +UchMsgBuffer :: MsgPeek (int sz, byte* b, lword offset) +{ } + +#endif /* DOC */ + +//----- miscellaneous +// + +/*? +Extend the buffer's contents by \var{n} bytes. +This function is useful when data has been appended to the buffer without +using one of the \fun{Append} functions. +For instance, to read \var{n} bytes from a Unix file descriptor into a buffer, +you would do: +\begin{ccode} +b.NeedSize (n); +int nb = read (fd, (char*) Free (), n); +if (nb > 0) + b.More (n); +\end{ccode} +?*/ +void +UchMsgBuffer :: More (int n) +{ + GetErr = FALSE; + if (End) // Begin == 0 for fake buffers + Stop += n; +} + +/*? +Discard the \var{n} last bytes of the buffer. +This is useful to remove erroneous data that has just been appended to the buffer. +This is in some sense the reverse operation of \fun{More}. +?*/ +void +UchMsgBuffer :: Discard (int n) +{ + if (! Begin) + if (! End) // ! Begin && End => fake buffer + return; + if (Stop - n <= Start) { + Start = Stop = Begin; + // free if too big + if (MaxSize > 0 && End - Start >= MaxSize) + Delete (); + } else + Stop -= n; + +} + +#ifdef DOC +// fake entries for inline functions + +/*?nextdoc?*/ +byte* +UchMsgBuffer :: Buffer () +{ } + +/*? +Return the address of the beginning of the buffer and the length of the buffer. +This is used in conjunction with \fun{Flush} to extract data from the buffer, as in the following example: +\begin{ccode} +byte* p = b.Buffer (); +int n = b.BufLength (); +n = write (fd, (const char*) p, n); +if (n > 0) + b.Flush (n); +\end{ccode} +?*/ +int +UchMsgBuffer :: BufLength () +{ } + +/*?nextdoc?*/ +byte* +UchMsgBuffer :: Free () +{ } + +/*? +Return the address of the first free byte in the buffer and the number of free bytes. +This may be used in conjunction with \fun{NeedSize} and \fun{More} +to append data to the buffer. +?*/ +int +UchMsgBuffer :: FreeLength () +{ } + +/*? +Grow the buffer if its free size is less than its grow size. +This function can be called when a buffer is full. +?*/ +void +UchMsgBuffer :: Grow () +{ } + +/*? +This operator is equivalent to the \fun{Append} operations. +The type of the data can be a long word, a short word, a byte, a character, or a string; +for strings, the null character is included. +The operator returns the buffer so that several items can be added. +?*/ +UchMsgBuffer& +UchMsgBuffer :: operator << (type data) +{ } + +/*?nextdoc?*/ +UchMsgBuffer& +UchMsgBuffer :: operator >> (type* data) +{ } + +/*? +This operator is equivalent to the \fun{Get} operations. +The type of the data can be a long word, a short word, a byte, or a character. +The operator returns the buffer so that several items can be read. +Errors can be tested by the functions \fun{Error} and \fun{Ok}. +?*/ +UchMsgBuffer& +UchMsgBuffer :: operator >> (type& data) +{ } + +/*?nextdoc?*/ +bool +UchMsgBuffer :: Error () +{ } + +/*? +Test the state of the buffer, as set by the get functions. +\fun{Error} returns TRUE if a get error has occurred. +\fun{Ok} is the negation of \fun{Error}. +?*/ +bool +UchMsgBuffer :: Ok () +{ } + +/*? +Reset the error flag. +The error flag is automatically reset by the functions \fun{Append} and \fun{More}, +and by \fun{operator <<}. +?*/ +void +UchMsgBuffer :: ResetError () +{ } + +#endif /* DOC */ + diff --git a/comm/MsgBuffer.h b/comm/MsgBuffer.h new file mode 100644 index 0000000..0f6a55c --- /dev/null +++ b/comm/MsgBuffer.h @@ -0,0 +1,112 @@ +/* + * The Unix Channel + * + * by Michel Beaudouin-Lafon + * + * Copyright 1990-1993 + * Laboratoire de Recherche en Informatique (LRI) + * + * Messages, buffers + * + * $Id$ + * $CurLog$ + */ + +#ifndef Buffer_H_ +#define Buffer_H_ + +#include "cplus_bugs.h" +#include "global.h" +#include "ccu/SmartPointer.h" + +class UchMessage; + +// Buffer is the buffer itself, End points after its last byte +// Start and Stop limit its content +// UchMsgBuffer derives from CcuSmartData to be able to have smart pointers (pUchMsgBuffer) +// a buffer grows and shrinks automatically +// +class UchMsgBuffer : public CcuSmartData { +protected: + byte* Begin; + byte* Start; + byte* Stop; + byte* End; + bool GetErr; + int GrowSize, MaxSize, MinSize; + +public: + UchMsgBuffer (); + UchMsgBuffer (int); + UchMsgBuffer (int, int, int); + UchMsgBuffer (const UchMsgBuffer&, int = 0); // construct a fake buffer + ~UchMsgBuffer (); + + void Delete (); + void NeedSize (int n); + void Grow () { NeedSize (GrowSize); } + void Flush (int n = -1); + void Flush (byte*); + void SetSizes (int, int, int); + + void Append (const byte*, int); + void Append (byte); + void Append (char c) { Append ((byte) c); } + void Append (sword s) { Append ((const byte*) &s, swsize); } + void Append (lword l) { Append ((const byte*) &l, lwsize); } + void Append (const char*, bool = TRUE); + void Append (UchMessage&); + + bool Get (byte*, int, bool = FALSE); + bool Get (byte*, bool = FALSE); + bool Get (char* c, bool peek = FALSE) { return Get ((byte*) c, peek); } + bool Get (lword* l, bool peek = FALSE) { return Get ((byte*) l, lwsize, peek); } + bool Get (sword* s, bool peek = FALSE) { return Get ((byte*) s, swsize, peek); } + int Get (char*, int, char, bool = FALSE); + int Get (char*, int, const char* = 0, bool = FALSE); + bool Get (UchMessage*); + + bool Peek (byte*, lword = 0); + bool Peek (sword*, lword = 0); + bool Peek (lword*, lword = 0); + bool Peek (int, byte*, lword = 0); + bool MsgPeek (byte* b, lword o = 0) { return Peek (b, o+lwsize); } + bool MsgPeek (sword* sw, lword o = 0) { return Peek (sw, o+lwsize); } + bool MsgPeek (lword* lw, lword o = 0) { return Peek (lw, o+lwsize); } + bool MsgPeek (int sz, byte* b, lword o = 0) { return Peek (sz, b, o+lwsize); } + + byte* Buffer () { return Start; } + int BufLength () { return Stop - Start; } + byte* Free () { return Stop; } + int FreeLength () { return End - Stop; } + void More (int); + void Discard (int); + + bool Error () { return GetErr; } + bool Ok () { return GetErr ? FALSE : TRUE; } + void ResetError () { GetErr = FALSE; } + + UchMsgBuffer& operator << (lword l) { Append (l); return *this; } + UchMsgBuffer& operator << (sword s) { Append (s); return *this; } + UchMsgBuffer& operator << (byte b) { Append (b); return *this; } + UchMsgBuffer& operator << (char c) { Append (c); return *this; } + UchMsgBuffer& operator << (const char* s) { Append (s, TRUE); return *this; } + + UchMsgBuffer& operator >> (lword& l) { Get (&l); return *this; } + UchMsgBuffer& operator >> (sword& s) { Get (&s); return *this; } + UchMsgBuffer& operator >> (byte& b) { Get ((byte*) &b, FALSE); return *this; } + UchMsgBuffer& operator >> (char& c) { Get ((byte*) &c, FALSE); return *this; } + UchMsgBuffer& operator >> (char* s) { Get (s, -1); return *this; } + +#ifdef DOC + UchMsgBuffer& operator << (type data); + UchMsgBuffer& operator >> (type& data); + UchMsgBuffer& operator >> (type* data); +#endif + +friend UchMsgBuffer& DbgPrint (UchMsgBuffer&, int); +}; + +PointerClass (pUchMsgBuffer, UchMsgBuffer) + +#endif /* Buffer_H_ */ diff --git a/comm/MsgStream.cc b/comm/MsgStream.cc new file mode 100644 index 0000000..07cb5c1 --- /dev/null +++ b/comm/MsgStream.cc @@ -0,0 +1,506 @@ +/* + * The Unix Channel + * + * by Michel Beaudouin-Lafon + * + * Copyright 1990-1993 + * Laboratoire de Recherche en Informatique (LRI) + * + * UchMessage streams + * + * $Id$ + * $CurLog$ + */ + +#include "MsgStream.h" +#include "Message.h" +#include "error.h" + +// #define DEBUG + +#ifdef DEBUG +#include +#define DBG(inst) inst +#else +#define DBG(inst) +#endif + +/*?class UchMsgStream +An object of class \typ{UchMsgStream} is a stream that sends and receives messages: +we call it a message stream. + +Messages are sent with the function \fun{Send}. +Output messages are normally buffered to increase performance, but synchronous mode is available. +The output buffer can be flushed by the application, or by the stream itself if needed +(for instance when the buffer is full, or when a synchronous communication is needed). + +Incoming messages are handled by the virtual function \fun{HandleRead} of class \typ{UchChannel}: +it is redefined so that incoming bytes are packed into messages. +When a full message is available, \fun{HandleRead} calls the virtual +function \fun{NewMessage}. In the class \typ{UchMsgStream}, this virtual function does +nothing but discarding the message. + +Messages that need an answer are handled in the following way: +the sender calls the function\fun{Ask}, and is blocked until the answer is received; +any incoming messages are stored for later processing. +When the answer arrives, \fun{Ask} calls the virtual function \fun{ConvertAnswer}. +The returned value of \fun{ConvertAnswer} is then returned by \fun{Ask}, +thus returning to the application the reply to its question. + +On the receiver's side, the following happens: +when a message sent with \fun{Ask} is received, \fun{NewMessage} is called as usual, +but an argument indicates that it is a question that needs an answer. +The receiver must then use \fun{Reply} to send its answer. +Messages can be sent before replying, but it is not possible to send a question +on a message stream that is waiting for an answer: +this would result in a deadlock since the other party is already waiting for an answer +and is buffering other incoming messages. + +The default output mode is buffered (i.e. asynchronous). +The buffered output is automatically flushed when a question is sent, +or when the output buffer is full, or explicitly with \fun{Flush}, or with the second argument of \fun{Send}. +The output mode can be switched to a synchronous mode where each message is sent immediately. +% It can also be switched to a locked synchronous mode: this is a synchronous mode where +% the sender waits for each message to be processed before proceeding its execution. +% This is mainly useful for debugging purposes. + +\medskip +To use a message stream, a program needs to derive the class \typ{UchMsgStream} +in order to redefine the virtual functions \fun{NewMessage} and \fun{ConvertAnswer}. +\fun{NewMessage} treats the incoming message, and will probably call \fun{Send} and \fun{Ask}. +The top level of the program needs just call \fun{HandleRead} in a forever loop. + +Most of the time, a program will use several message streams +(for instance to manage several clients). +In this case a channel set is the best way to implement the application: +the definitions in the class \typ{UchMsgStream} of the functions +\fun{HandleRead}, \fun{HandleWrite} and \fun{HandleSelect} make it easy +to use this class in combination with the class \typ{UchMultiplexer}. +The virtual function \fun{HandeWrite} of channels is redefined to flush the output buffer. +The virtual function \fun{HandleSelect} of channels is redefined to handle the incoming messages +that were buffered while waiting for an answer. +As for the single stream situation, you need just derive the class \fun{UchMsgStream} to +redefine the virtual functions \fun{NewMessage} and \fun{ConvertAnswer}. +?*/ + +// ---- no byte swapping ... +// ---- no locked sync mode ... + +/*?nextdoc?*/ +UchMsgStream :: UchMsgStream () +: UchStream (), InBuffer (), OutBuffer (), Buffered () +{ + OutSize = 128; + State = WAITING; + BufferedMessages = FALSE; + WaitingForAnswer = FALSE; + WaitingReply = FALSE; + Sync = FALSE; +} + +/*? +These constructors are similar to those of class \typ{UchSocket}. +?*/ +UchMsgStream :: UchMsgStream (UchAddress* bindTo, UchAddress* connectTo) +: UchStream (bindTo, connectTo), InBuffer (), OutBuffer (), Buffered () +{ + OutSize = 128; + State = WAITING; + BufferedMessages = FALSE; + WaitingForAnswer = FALSE; + WaitingReply = FALSE; + Sync = FALSE; +} + +// *** this copy constructor might be automatically generated +/*?nodoc?*/ +UchMsgStream :: UchMsgStream (const UchMsgStream& ms) +: UchStream (*(UchMsgStream*)&ms), InBuffer (), OutBuffer (), Buffered (*(UchMsgBuffer*)&ms.Buffered) +{ + OutSize = ms.OutSize; + State = ms.State; + BufferedMessages = ms.BufferedMessages; + WaitingForAnswer = ms.WaitingForAnswer; + WaitingReply = ms.WaitingReply; + Sync = ms.Sync; +} + +/*?nextdoc?*/ +void +UchMsgStream :: InputBuffer (int min, int grow, int max) +{ + InBuffer.SetSizes (min, grow, max); +} + +/*? +Set the input and output buffer sizes. +Default sizes are used if these functions are not called. +?*/ +void +UchMsgStream :: OutputBuffer (int min, int grow, int max) +{ + InBuffer.SetSizes (min, grow, max); + OutSize = max; +} + +/*?nodoc?*/ +UchMsgStream :: ~UchMsgStream () +{ + Flush (); + InBuffer.Delete (); + OutBuffer.Delete (); + Buffered.Delete (); +} + +/*?nodoc?*/ +UchChannel* +UchMsgStream :: Copy () const +{ + return new UchMsgStream (*this); +} + + +// read stream into input buffer +// delete the stream when an oef is received +// return the number of bytes read +// +/*?hidden?*/ +int +UchMsgStream :: ReadInput () +{ + if (! InBuffer.BufLength ()) + InBuffer.NeedSize (128); + int n = Read (InBuffer); + if (n <= 0) { + if (n == -2) { +//printf ("ReadInput: growing\n", n); + InBuffer.Grow (); + n = Read (InBuffer); + } +//printf ("ReadInput: %d bytes\n", n); + if (n < 0) + SysError (ErrWarn, "UchMsgStream::HandleRead"); + if (n <= 0) + Delete (); + } + return n; +} + +/*? +This virtual function is called when an end of file is read, +meaning that the communication is terminated. +It is also called if an error occurs while reading. +You can check the value of the global variable \var{errno}: +if it is non zero, \fun{Delete} was called because of an error. +By default this function does nothing. +?*/ +void +UchMsgStream :: Delete () +{ + // nothing +} + +// process the input buffer +// waitAnswer indicates whether we are waiting for an answer +// this functions uses a very simple automaton: +// WAITING: nothing in the buffer +// GOT_TYPE: read the 1 byte header mark of a message +// MSG / ASK / ANS for messages, questions, answers +// SYNC / ASYNC / OK for sync management +// GOT_LENGTH: read the 4 byte header fo a message +// DONE: a full message is in the buffer +// +// WaitingReply is true if a question has been received and Reply has not been called yet +// WaitingForAnswer is true when Ask has been called and the answer is not yet there +// +/*?hidden?*/ +void +UchMsgStream :: ProcessInput (UchMsgBuffer& buf, bool waitAnswer) +{ +DBG (printf ("ProcessInput: ");) + for (;;) { + switch (State) { + case WAITING: +DBG (printf ("WAITING\n");) + buf.Get ((byte*) &InType); +DBG (if (buf.Error ()) printf (" buf empty\n");) + if (buf.Error ()) + return; + WaitingReply = FALSE; + switch (InType) { + case ASK : +DBG (printf (" waiting reply\n");) + WaitingReply = TRUE; + State = GOT_TYPE; + break; + case ANS : +DBG (printf (" answer\n");) + case MSG : +DBG (if (InType != ANS) printf (" msg\n");) +DBG (printf (" got type\n");) + State = GOT_TYPE; + break; + case SYNC : + case ASYNC : + case OK : + default : +DBG (printf (" unknown !!\n");) + State = WAITING; + } + if (State != GOT_TYPE) + break; + // fallthrough + + case GOT_TYPE: +DBG (printf ("GOT_TYPE\n");) + if (! buf.Peek ((lword*) &InLength)) + return; + buf.NeedSize ((int) InLength - buf.BufLength ()); + State = GOT_LENGTH; + // fallthrough + + case GOT_LENGTH: +DBG (printf ("GOT_LENGTH\n");) + if (buf.BufLength () < InLength) + return; + State = DONE; + // fallthrough + + case DONE: +DBG (printf ("DONE\n");) + if (waitAnswer) { + if (InType == ANS) { +DBG (printf ("got answer\n");) + WaitingForAnswer = FALSE; + // answer still in the buffer + // the answer is converted and + // the buffer is flushed in Ask + return; + } else { + // store incoming message in a separate buffer + BufferedMessages = TRUE; + Buffered.Append (InType); + Buffered.Append (buf.Buffer (), InLength); +DBG (printf ("buffering\n");) + } + } else { + if (InType == MSG || InType == ASK) { +DBG (printf ("new message\n");) + // pass a fake buffer to the handler + UchMsgBuffer fake (buf, InLength); + if (! NewMessage (fake, WaitingReply)) + return; + // *** this return breaks the assumption that + // *** ProcessInput empties the buffer. + // *** this is assumed in HandleRead/HandleSelect + // *** because BufferedMessages is reset to FALSE; + } +DBG (else printf ("discarding\n");) + } + buf.Flush (InLength); + State = WAITING; + // fallthrough + } + } +} + +/*?nodoc?*/ +void +UchMsgStream :: HandleRead () +{ + if (BufferedMessages) { + ProcessInput (Buffered, FALSE); + BufferedMessages = FALSE; + } + int n = ReadInput (); + if (n <= 0) + return; + ProcessInput (InBuffer, FALSE); +} + +/*?nodoc?*/ +void +UchMsgStream :: HandleWrite () +{ + Flush (); +} + +/*?nodoc?*/ +bool +UchMsgStream :: HandleSelect () +{ + if (BufferedMessages) { + ProcessInput (Buffered, FALSE); + BufferedMessages = FALSE; + } + return FALSE; +} + + +/*? +This virtual function is called whenever a complete message is in the buffer. +The buffer contains exactly one message, so that you can use \com{buf.Get(&msg)} +to extract the message from the buffer. +\var{ask} is TRUE if the message was sent with \fun{Ask}. +In this case an answer must be sent back with \fun{Reply}. +Messages can be sent before replying; they will be buffered by the receiver for later processing; +thus, you cannot use \fun{Ask} before replying. +\fun{NewMessage} function must return TRUE if it handled the message, else FALSE. +If it returns FALSE, it will be called again with the same arguments next time data arrives on this channel. +In the class \typ{UchMsgStream} this function does nothing and returns TRUE; +if \var{ask} is TRUE, it replies immediately with an empty message. +?*/ +bool +UchMsgStream :: NewMessage (UchMsgBuffer&, bool ask) +{ + if (ask) { + UchMessage dummy; + + Reply (dummy); + } + return TRUE; +} + +/*? +Send a message. +If \var{flush} is TRUE, the output buffer will be flushed. +This also happens when the message stream is in synchronous mode, +or if the output UchMsgBuffer.has exceeded its flush size (see \fun{FlushSize}). +?*/ +void +UchMsgStream :: Send (UchMessage& msg, bool flush) +{ + OutBuffer.Append ((byte) MSG); + OutBuffer.Append (msg); + if (flush || Sync || OutBuffer.BufLength () >= OutSize) + Flush (); +} + +/*? +Send a message and wait for an answer. +Incoming messages that are received while waiting for the answer are kept for later processing. +The answer message is returned by calling the virtual function \fun{ConvertAnswer}. +?*/ +void* +UchMsgStream :: Ask (UchMessage& msg) +{ + if (WaitingReply) { + Error (ErrWarn, "UchMsgStream::Ask", "cannot ask before replying"); + return 0; + } + OutBuffer.Append ((byte) ASK); + OutBuffer.Append (msg); + Flush (); + WaitingForAnswer = TRUE; + + do { +#ifdef DEBUG + printf ("waiting for answer\n"); +#endif + int n = ReadInput (); +#ifdef DEBUG + printf (" got %d bytes\n", n); +#endif + if (n <= 0) + return 0; + ProcessInput (InBuffer, TRUE); + } while (WaitingForAnswer); + + // ProcessInput did not flush so that we can convert the answer + // *** If ProcessInput would return a void*, we could do all this stuff in it ... + UchMsgBuffer fake (InBuffer, InLength); + void* res = ConvertAnswer (fake); + InBuffer.Flush (InLength); + State = WAITING; + return res; +} + +/*? +This function must be used instead of \fun{Send} to send a reply to a message sent by \fun{Ask}. +?*/ +void +UchMsgStream :: Reply (UchMessage& msg) +{ + if (! WaitingReply) { + Error (ErrWarn, "UchMsgStream::Reply", "out of phase reply discarded"); + return; + } + OutBuffer.Append ((byte) ANS); + OutBuffer.Append (msg); + Flush (); + WaitingReply = FALSE; +} + + +/*? +This function is called by \fun{Ask} when the answer is in the buffer, +in order to convert it into an object usable by the application. +?*/ +void* +UchMsgStream :: ConvertAnswer (UchMsgBuffer&) +{ + return 0; +} + +/*? +Flush the output buffer. +This function is called automatically when the buffer exceeds its flush size, +or after each message when this stream is in synchronous mode. +It is also called from \fun{Ask} to wait for the answer. +?*/ +void +UchMsgStream :: Flush () +{ + if (! OutBuffer.BufLength ()) + return; + int n = Write (OutBuffer); + OutBuffer.Flush (n); +} + +/*? +Send a buffer containing a message. +If \var{flush} is TRUE, the output buffer will be flushed. +This also happens when the message stream is in synchronous mode, +or if the output UchMsgBuffer.has exceeded its flush size (see \fun{FlushSize}). +The buffer {\em must} contain a converted message. +This can be used for instance from inside \fun{NewMessage} to resend +the incoming message to another client, without having to convert +the buffer to a message. +?*/ +void +UchMsgStream :: Send (UchMsgBuffer& buf, bool flush) +{ + OutBuffer.Append ((byte) MSG); + OutBuffer.Append (buf.Buffer (), buf.BufLength ()); + if (flush || Sync || OutBuffer.BufLength () >= OutSize) + Flush (); +} + +#ifdef DOC +/*? +This function defines the size of the output buffer that triggers automatic flushing +in asynchronous mode. By default the flush size is the maximum size of the +output buffer. As a consequence, it is changed by \fun{OutBuffer}. +?*/ +void +UchMsgStream :: FlushSize (int n) +{ } + +/*?nextdoc?*/ +void +UchMsgStream :: SetSyncMode (bool s) +{ } + +/*? +A message stream can be in synchronous or asynchronous mode. +In asynchronous mode output is buffered while in synchronous mode it is not. +Synchronous mode is usually less efficient than asynchronous mode +because it makes more system calls to transfer data; +however synchronous mode can be useful for debugging applications. +?*/ +bool +UchMsgStream :: GetSyncMode () +{ } + +#endif /* DOC */ + diff --git a/comm/MsgStream.h b/comm/MsgStream.h new file mode 100644 index 0000000..c2b8e1f --- /dev/null +++ b/comm/MsgStream.h @@ -0,0 +1,70 @@ +/* + * The Unix Channel + * + * by Michel Beaudouin-Lafon + * + * Copyright 1990-1993 + * Laboratoire de Recherche en Informatique (LRI) + * + * UchMessage streams + * + * $Id$ + * $CurLog$ + */ + +#ifndef MsgStream_H_ +#define MsgStream_H_ + +#include "Stream.h" +#include "ccu/SmartPointer.h" +#include "MsgBuffer.h" +class UchMessage; + +class UchMsgStream : public UchStream { +private: + +protected: + UchMsgBuffer InBuffer; + int InSize; + UchMsgBuffer OutBuffer; + int OutSize; +enum STATE { WAITING, GOT_TYPE, GOT_LENGTH, DONE}; +enum TYPE { MSG = 1, ASK, ANS, SYNC, ASYNC, OK }; + STATE State; + bool BufferedMessages; + UchMsgBuffer Buffered; + bool WaitingForAnswer; + bool WaitingReply; + int InLength; + byte InType; + bool Sync; + + void ProcessInput (UchMsgBuffer&, bool); + int ReadInput (); +public: + UchMsgStream (); + UchMsgStream (const UchMsgStream&); + UchMsgStream (UchAddress*, UchAddress*); + ~UchMsgStream (); + + void InputBuffer (int min, int grow, int max); + void OutputBuffer (int min, int grow, int max); +inline void FlushSize (int n) { OutSize = n; } + UchChannel* Copy () const; + void HandleRead (); + void HandleWrite (); + bool HandleSelect (); +inline bool GetSyncMode () { return Sync; } +inline void SetSyncMode (bool s) { Sync = s; Flush (); } +virtual bool NewMessage (UchMsgBuffer&, bool); +virtual void* ConvertAnswer (UchMsgBuffer&); +virtual void Delete (); + void Send (UchMessage&, bool = FALSE); + void* Ask (UchMessage&); + void Reply (UchMessage&); + void Flush (); + void Send (UchMsgBuffer&, bool = FALSE); +}; + +#endif /* MsgStream_H_ */ + diff --git a/comm/Multiplexer.cc b/comm/Multiplexer.cc new file mode 100644 index 0000000..497e041 --- /dev/null +++ b/comm/Multiplexer.cc @@ -0,0 +1,463 @@ +/* + * The Unix Channel + * + * by Michel Beaudouin-Lafon + * + * Copyright 1990-1993 + * Laboratoire de Recherche en Informatique (LRI) + * + * Channel sets, or multiplexers + * + * $Id$ + * $CurLog$ + */ + +#include "Multiplexer.h" +#include "TimeOut.h" + +#include +#include +#include + +#include +#include +#include + +#if defined(__hpux) && !defined(__GNUG__) +#define FD_SET_TYPE(x) ((int*) x) +#else +#define FD_SET_TYPE(x) (x) +#endif + +extern int errno; + +#ifndef FD_SET +#ifndef NFDBITS +#define FD_SET(n, p) ((p)->fds_bits[0] |= (1 << (n))) +#define FD_CLR(n, p) ((p)->fds_bits[0] &= ~(1 << (n))) +#define FD_ISSET(n, p) ((p)->fds_bits[0] & (1 << (n))) +#define FD_ZERO(p) ((p)->fds_bits[0] = 0) +#else +#define FD_SET(n, p) ((p)->fds_bits[(n)/NFDBITS] |= (1 << ((n) % NFDBITS))) +#define FD_CLR(n, p) ((p)->fds_bits[(n)/NFDBITS] &= ~(1 << ((n) % NFDBITS))) +#define FD_ISSET(n, p) ((p)->fds_bits[(n)/NFDBITS] & (1 << ((n) % NFDBITS))) +#define FD_ZERO(p) memset((char *)(p), 0, sizeof(*(p))) +#endif +#endif + + +/*?class UchMultiplexer +An object of class \typ{UchMultiplexer} is a set of channels. +Some of the channels in the set are active, others can be inactive. +A channel set can be scanned so that the functions \fun{HandleRead} +and \fun{HandleWrite} are called for each active channel that is ready to read or write. +The functions \fun{HandleSelect} of each channel is called before actually scanning +so that each channel can perform a background task or buffer input/output. +This provides a level of abstraction over the Unix \fun{select} system call. +Note that the functions that take an integer file descriptor as argument can be passed +a \typ{FILDES} or a \typ{UchChannel} because the corresponding conversion operators are defined. + +The class \typ{^{pCHAN_SET}} implements smart pointers to channel sets. +Smart pointers behave like pointers but they manage a reference count on the +pointed to objects for an automatic reclaim of dynamically allocated objects. +?*/ + +// constructor: initialize states +// + +/*? +Create an empty channel set. +?*/ +UchMultiplexer :: UchMultiplexer () +: Channels (new pUchChannel [NFILE]), + Timers (), + ReadCount (0), + WriteCount (0), + SelectCount (0), + Looping (FALSE) +{ + FD_ZERO (&ReadMask); + FD_ZERO (&WriteMask); + FD_ZERO (&SelectMask); +} + +/*?nodoc?*/ +UchMultiplexer :: ~UchMultiplexer () +{ +#ifdef CPLUS_BUG4 + delete [NFILE] Channels; +#else + delete [] Channels; +#endif + Channels = 0; +} + +/*? +Add a channel to the set. +Note that the argument is a pointer to the channel; no copy is made by this function. +If a channel with the same file descriptor as the one being added is already in the set, +the old channel is first removed, thus calling \fun{RemoveNotify} for that channel. +The virtual function \fun{AddNotify} is called before actually adding the channel +to the set. +?*/ +void +UchMultiplexer :: Add (UchChannel* chan) +{ + int fd = chan->FilDes (); + if (fd < 0) + return; + pUchChannel ochan = Channels [fd]; + if (ochan) + Remove (fd); + chan->AddNotify (*this); + AddNotify (chan); + Channels [fd] = chan; + SetMasks (fd, chan->IOMode ()); +} + +pUchChannel NIL_CHAN (0); + +/*? +Remove a channel from the set. +The virtual function \fun{RemoveNotify} is called before actually removing the channel +from the set. +?*/ +void +UchMultiplexer :: Remove (int fd) +{ + if (fd < 0) + return; + UchChannel* ch = Channels [fd]; + if (ch) { + ch->RemoveNotify (*this); + RemoveNotify (ch); + SetMasks (fd, IONone); + Channels [fd] = 0; + } +} + +/*? +Remove all channels from this channel set. +This function calls \fun{Remove} for all channels of the set. +This is a way of exiting from \fun{LoopScan}. +?*/ +void +UchMultiplexer :: RemoveAll () +{ + for (int fd = 0; fd < NFILE; fd++) + Remove (fd); +} + +/*? +Change the mode of a channel in the set. +Mode \var{IONone} makes the channel inactive (without removing it). +The virtual function \fun{SetModeNotify} is called before actually changing +the mode of the channel. +?*/ +void +UchMultiplexer :: SetMode (int fd, IOMODE mode) +{ + if (fd < 0) + return; + + SetModeNotify (Channels [fd], mode); + SetMasks (fd, mode); + Channels [fd] -> SetMode (mode); +} + + +// update the masks when channel fd changes its mode +// +/*?hidden?*/ +void +UchMultiplexer :: SetMasks (int fd, IOMODE mode) +{ + if (mode & IORead) { + if (! FD_ISSET (fd, &ReadMask)) { + ReadCount ++; + FD_SET (fd, &ReadMask); + } + } else { + if (FD_ISSET (fd, &ReadMask)) { + FD_CLR (fd, &ReadMask); + ReadCount --; + } + } + + if (mode & IOWrite) { + if (! FD_ISSET (fd, &WriteMask)) { + WriteCount ++; + FD_SET (fd, &WriteMask); + } + } else { + if (FD_ISSET (fd, &WriteMask)) { + FD_CLR (fd, &WriteMask); + WriteCount --; + } + } + + if (mode & IOSelect) { + if (! FD_ISSET (fd, &SelectMask)) { + SelectCount ++; + FD_SET (fd, &SelectMask); + } + } else { + if (FD_ISSET (fd, &SelectMask)) { + FD_CLR (fd, &SelectMask); + SelectCount --; + } + } + +} + +/*?nextdoc?*/ +void +UchMultiplexer :: AddNotify (UchChannel*) +{ + // nothing +} + +/*?nextdoc?*/ +void +UchMultiplexer :: RemoveNotify (UchChannel*) +{ + // nothing +} + +/*? +This function calls the select handler of each channel of the set in select mode. +It returns TRUE as soon as one select handler returns true, +else it returns FALSE when the select handlers have been called. +?*/ +bool +UchMultiplexer :: HandleSelect () +{ + int fd, nfd; + + if (fd0 >= NFILE) + fd0 = 0; + for (fd = fd0++, nfd = SelectCount; nfd; (fd < NFILE) ? fd++ : (fd = 0)) { + if (! FD_ISSET (fd, &SelectMask)) + continue; + if (Channels [fd] -> HandleSelect ()) + return TRUE; + nfd--; + if (! Looping) + return FALSE; + } + return FALSE; +} + +/*? +These virtual functions are called by the functions \fun{Add}, \fun{Remove} +(and thus \fun{RemoveAll}) and \fun{SetMode}. +They provide hooks for application that need to take special action when the +set changes. They are called before the corresponding action is actually +carried out. +By default these functions do nothing. +?*/ +void +UchMultiplexer :: SetModeNotify (UchChannel*, IOMODE) +{ + // nothing +} + +/*?nextdoc?*/ +int +UchMultiplexer :: Scan (bool nointr, bool poll) +{ + fd_set rmsk, wmsk; + int nfd, ret = -1; + register int fd; + struct timeval tout; + struct timeval* timeout = 0; + + if (poll) { + timeout = &tout; + tout.tv_sec = tout.tv_usec = 0; + } + + while (ret <= 0) { + CcuCoreTimer::Fire (&Timers); + + if (ReadCount == 0 && WriteCount == 0 && SelectCount == 0) + return 0; + + for (fd = 0, nfd = SelectCount; nfd; fd++) { + if (! FD_ISSET (fd, &SelectMask)) + continue; + UchChannel* ch = Channels [fd]; + if (ch && ch->HandleSelect ()) + return -2; + nfd--; + } + +//printf ("Scan: read %x, write %x, TimeOut %d", ReadMask, WriteMask, TimeOut); + rmsk = ReadMask; + wmsk = WriteMask; + if (!poll && TimeOut != -1) { + CcuTimeStamp now; + Millisecond delay = TimeOut - now; +//printf (", timeout in %d ms", delay); + tout.tv_sec = delay / 1000; + tout.tv_usec = 1000 * (delay % 1000); + timeout = &tout; + } +//printf ("\n"); + + ret = select (NFILE, FD_SET_TYPE(&rmsk) /*read*/, FD_SET_TYPE(&wmsk) /*write*/, 0 /*except*/, timeout); + if (ret < 0 || ret == 0 && poll) { +//printf ("select failed: %d, errno %d\n", ret, errno); + if (nointr && ret == -1 && errno == EINTR) + continue; + return ret; + } + } + + for (fd = 0, nfd = ret; nfd; fd++) { +//printf ("Scan: handle %d: r %d, w %d\n", fd, FD_ISSET (fd, &rmsk), FD_ISSET (fd, &wmsk)); + if (FD_ISSET (fd, &wmsk)) { + nfd --; + UchChannel* ch = Channels [fd]; + if (ch) + ch->HandleWrite (); + } + if (FD_ISSET (fd, &rmsk)) { + nfd--; + UchChannel* ch = Channels [fd]; + if (ch) + ch->HandleRead (); + } + } + + return ret; +} + +/*? +Scan the channels in the set and call the channel's handlers. +First the select handler (\fun{UchChannel::HandleSelect}) of each channel with mode +\var{IOSelect} is called. +If it returns TRUE, \fun{Scan} exits immediately, while \fun{LoopScan} loops immediately. +If no select handler returns TRUE, the system call \fun{select} is used to poll or +wait for the channels that are ready. +When the select returns normally, the write handlers \fun{HandleWrite} of the +channels in mode \var{IOWrite} that are ready to write are called, and +the read handlers (\fun{HandleRead}) of the channels in mode \fun{IORead} +that are ready to read are called. +If \var{nointr} is TRUE, ignore the interrupted system calls, else return an error +whenever the \fun{select} system call is interrupted. +If \var{poll} is TRUE, the call is non-blocking, else it is blocking. +\fun{Scan} calls \fun{select} only once; +it returns -2 if a select handler returns, it returns 0 if the channel set has no active channels, +else it returns the return code of \fun{select}. +\fun{LoopScan} calls \fun{select} repeatedly until \var{LoopEnd} is called, or +an error occurs, the channel set has no more active channel +This can occur because the select, read or write handler of a channel may remove +the channel or change its mode. +This is usually done when the read handler detects an end of file. +\fun{LoopScan} returns 0 when there are no more active channel in the set, +it returns -1 when an error occured (the code is in \var{errno}), +and it returns 1 if \fun{LoopEnd} was called. +?*/ +int +UchMultiplexer :: LoopScan (bool nointr) +{ + fd_set rmsk, wmsk; + register int nfd, fd; + Looping = TRUE; + + for (fd0 = 0; Looping; fd0 < NFILE ? fd0++ : (fd0 = 0)) { + CcuCoreTimer::Fire (&Timers); + + if (ReadCount == 0 && WriteCount == 0 && SelectCount == 0) + return 0; + + for (fd = fd0, nfd = SelectCount; nfd; (fd < NFILE) ? fd++ : (fd = 0)) { + if (! FD_ISSET (fd, &SelectMask)) + continue; + UchChannel* ch = Channels [fd]; + if (ch && ch->HandleSelect ()) + break; + nfd--; + if (! Looping) + return 1; + } + if (nfd) // a handler returned TRUE. + continue; + + rmsk = ReadMask; + wmsk = WriteMask; + struct timeval tv; + struct timeval* timeout = 0; + if (TimeOut != -1) { + CcuTimeStamp now; + Millisecond delay = TimeOut - now; + tv.tv_sec = delay / 1000; + tv.tv_usec = 1000 * (delay % 1000); + timeout = &tv; + } + nfd = select (NFILE, FD_SET_TYPE(&rmsk) /*read*/, FD_SET_TYPE(&wmsk) /*write*/, 0 /*except*/, timeout); + if (nfd < 0) { + if (nointr && nfd == -1 && errno == EINTR) + continue; + return nfd; + } + + for (fd = fd0; nfd; (fd < NFILE) ? fd++ : (fd = 0)) { + if (FD_ISSET (fd, &wmsk)) { + nfd--; + Channels [fd] -> HandleWrite (); + if (! Looping) + return 1; + } + if (FD_ISSET (fd, &rmsk)) { + nfd--; + Channels [fd] -> HandleRead (); + if (! Looping) + return 1; + } + } + } + return 1; +} + +// string repr +// +/*?nodoc?*/ +char* +UchMultiplexer :: StrRepr (char* buf) +{ + sprintf (buf, "R:%ux W:%ux", ReadMask, WriteMask); + return buf; +} + +#ifdef DOC +// fake entries for inline functions + +/*? +This array operator returns a (smart) pointer to the channel corresponding to file descriptor \var{fd}. +If there is no such channel in this channel set, the operator returns NIL. +?*/ +pUchChannel +UchMultiplexer :: operator [] (int fd) +{ } + +/*? +Add a channel to the set. +Here the argument is a reference, and a dynamically allocated copy of it is actually added. +Compare with previous function. +?*/ +void +UchMultiplexer :: Add (const UchChannel& ch) +{ } + +/*? +This function makes \fun{LoopScan} exit immediately. +Thus it must be called from within a channel's handler. +?*/ +void +UchMultiplexer :: LoopEnd () +{ } + +#endif /* DOC */ + diff --git a/comm/Multiplexer.h b/comm/Multiplexer.h new file mode 100644 index 0000000..0c30bfd --- /dev/null +++ b/comm/Multiplexer.h @@ -0,0 +1,83 @@ +/* + * The Unix Channel + * + * by Michel Beaudouin-Lafon + * + * Copyright 1990-1993 + * Laboratoire de Recherche en Informatique (LRI) + * + * Channel sets, or multiplexers + * + * $Id$ + * $CurLog$ + */ + +#ifndef Multiplexer_H_ +#define Multiplexer_H_ + +#include "Channel.h" +#include +#include "ccu/Timer.h" + +extern char* StrReprBuf; + +// This class defines channel sets for multiplexing i/o +// Because of the coercion defined for FILDES -> int, +// most arguments of type ints can be FILDES, UchChannel, ... +// An array of pointers to channels is kept in the object; +// we use smart pointers to handle deletion cleanly. +// The masks for reading/writing are kept consistent with the IOMode of +// the channels, as long as it is not changed by a direct access to the channel +// operator[] returns a pointer and cannot be used as an lhs of an assignment. +// +class UchMultiplexer : public CcuSmartData { +friend class UchBaseTimeOut; + +protected: + pUchChannel* Channels; + CcuTimerSet Timers; + fd_set ReadMask; + fd_set WriteMask; + fd_set SelectMask; + short ReadCount; + short WriteCount; + short SelectCount; + bool Looping; + Millisecond TimeOut; + int fd0; // used by HandleSelect and LoopScan + + void SetMasks (int, IOMODE); +inline CcuTimerSet* GetTimerSet () { return &Timers; } +inline void SetTimeOut (Millisecond t) { TimeOut = t; } +inline void SuppressTimeOut () {TimeOut = -1; } + +public: + UchMultiplexer (); + ~UchMultiplexer (); + +inline pUchChannel operator [] (int fd) { if (fd < 0) return pUchChannel (0); else return Channels [fd]; } + + void Add (UchChannel*); +inline void Add (const UchChannel& ch) { Add (ch.Copy ()); } + void Remove (int); + void RemoveAll (); + void SetMode (int, IOMODE); + + bool HandleSelect (); +virtual int Scan (bool nointr = TRUE, bool poll = FALSE); +virtual int LoopScan (bool nointr = TRUE); +inline void LoopEnd () { Looping = FALSE; } + + char* StrRepr (char* = StrReprBuf); + +protected: +/*? public ?*/ +virtual void AddNotify (UchChannel*); +virtual void RemoveNotify (UchChannel*); +virtual void SetModeNotify (UchChannel*, IOMODE); +}; + +PointerClass (pUchMultiplexer, UchMultiplexer) + +#endif /* Multiplexer_H_ */ + diff --git a/comm/OLD/Agent.cc b/comm/OLD/Agent.cc new file mode 100644 index 0000000..3e4bf8a --- /dev/null +++ b/comm/OLD/Agent.cc @@ -0,0 +1,386 @@ +/* + * The Unix Channel + * + * by Michel Beaudouin-Lafon + * + * Copyright 1990-1993 + * Laboratoire de Recherche en Informatique (LRI) + * + * Agents, by Stephane Chatty + * + * $Id$ + * $CurLog$ + */ + +#include "Agent.h" + +/*?class UchAgent +The class \typ{UchAgent} derives from \typ{UchStream}. +It is associated to a channel set to monitor the connected agents in parallel. +Whenever a new agent connects, the virtual function \fun{HandleNew} is called. +This function must return a pointer to an object of class \typ{UchRemoteAgent}. + +Because the agents may be on different machines with different architectures, +the byte swapping is always done by the ``server'' (ie. the agent that did not initiate +the communication), by convention. (NOTE: byte swapping is not currently implemented) +?*/ + +/*?class UchRemoteAgent +Class \typ{UchRemoteAgent} derives from \typ{UchMsgStream}. +It is still an abstract class because the virtual functions \fun{NewMessage} and \fun{ConvertAnswer} +of \typ{UchMsgStream} are not defined in \typ{UchRemoteAgent}. +Instances of \fun{UchRemoteAgent} only know that they may belong to an instance of \typ{UchAgent}. +Thus the virtual function \fun{Delete} is redefined to achieve a clean removal from the server's list. +?*/ + +/*? +Construct an empty server port. +?*/ +UchAgent :: UchAgent () +: UchStream (), + RemoteAgents (), + ChanSet (0) +{ +} + +/*?nodoc?*/ +UchAgent :: UchAgent (const UchAgent& sp) +: UchStream (sp), + RemoteAgents (), + ChanSet (0) +{ +} + +/*? +Construct a server port bound to address \var{a}. +When using Internet domain addresses, \var{a} should refer to +the wildcard address so that clients can connect from any machine. +This can be done with the instruction \com{new UchInetAddress(ANYADDR)}. +?*/ +UchAgent :: UchAgent (UchAddress* a) +: UchStream (a, 0), + RemoteAgents (), + ChanSet (0) +{ +} + +/*?nodoc?*/ +UchAgent :: ~UchAgent () +{ + UchRemoteAgent* cl = 0; + + while (cl = RemoteAgents.First ()) + cl->Delete (); + ChanSet = 0; // will delete it if necessary +} + +/*?nodoc?*/ +UchChannel* +UchAgent :: Copy () const +{ + return new UchAgent (*this); +} + +/*? +Remove a client from the set of clients connected to the server. +This is normally done automatically whenever the client disconnects itself, +as a side effect of destroying the client. +?*/ +void +UchAgent :: RemoveRemoteAgent (UchRemoteAgent* cl) +{ + if (RemoteAgents.Remove (cl)) { + cl->MyLocalAgent = 0; + HandleRemove (cl); + } else + Error (ErrWarn, "UchAgent::RemoveRemoteAgent", "not owned by this server"); +} + +/*? +Bind the socket and listen to it. +Initialize the channel set of the server to \var{cs}, or create a new one if \var{cs} is 0. +The channel set is used by the server to read and process messages from its clients: +whenever a client connects to the server, an instance of \typ{UchRemoteAgent} is added to the channel set. +Whenever data is readable from a client, it is read in an input buffer and processed as soon +as a full message is received. +Whenever a client closes the connection, it is removed from the channel set of the server. +This function returns TRUE if all went well, else it calls \fun{SysError} and returns FALSE. +You may want to pass your own channel set if you are already multiplexing +i/o on that channel set. For instance, you may have several \typ{UchAgent} objects +in your application. +A smart pointer to the channel set is actually kept in the server, so that it can be shared +securely. +?*/ +bool +UchAgent :: Setup (UchMultiplexer* cs) +{ + if (Listen () < 0) { + SysError (ErrWarn, "UchAgent::Setup"); + return FALSE; + } + SetMode (IORead); + if (!cs) + cs = new UchMultiplexer; + ChanSet = cs; + ChanSet->Add (this); + + return TRUE; +} + +/*? +This function removes the agent's registration channel from its channel set. +This has the effect of ignoring any incoming connections. +This function will also make \fun{Run} exit if there is no other +channel in the channel set. +Unless you added your own channels to this channel set, +this will be the case if there is no remote agent currently connected. +This is the only way to exit properly from \fun{Run}. +?*/ +void +UchAgent :: Unlisten () +{ + if (ChanSet) + ChanSet->Remove (*this); +} + + +/*?nodoc?*/ +void +UchAgent :: HandleRead () +{ +// cannot redefine Accept to return UchRemoteAgent*, so this does not work : +// UchRemoteAgent* cl = Accept (); +// need to delete the channel created by Accept, so this does not work either : +// UchRemoteAgent* cl = new UchRemoteAgent (Accept ()); +// so we do this (less elegant ...) : + UchChannel* ch = Accept (); + if (! ch) { + SysError (ErrWarn, "UchAgent::HandleRead: Accept"); + return; + } + UchRemoteAgent* ra = new UchRemoteAgent (this, ch); + delete ch; + + ra->SetMode (IOReadSelect); + RemoteAgents.Append (ra); + if (ChanSet) + ChanSet->Add (ra); +} + +/*? +This virtual function is called whenever a new agent connects to this one. +?*/ +void +UchAgent :: HandleNew (UchRemoteAgent*) +{ +} + +/*? +This virtual function is called whenever a remote agent is removed (\fun{RemoveRemoteAgent}). +It is a hook for the application to take whatever action; the default action is to do nothing. +When this function is called, the client is already removed from the client list of the server. +?*/ +void +UchAgent :: HandleRemove (UchRemoteAgent*) +{ +} + +// default error functions +// +/*?nextdoc?*/ +bool +UchAgent :: SysError (errtype how, const char* who, int excl1, int excl2) +{ + return ::SysError (how, who, excl1, excl2); +} + +/*? +Each server port class can have its own error handling routines. +Their default action is to call the global functions \fun{Error} and \fun{SysError}. +They can be called from the following functions: +\fun{Error} can be called from +\fun{RemoveRemoteAgent} when the client does not belong to this server; +\fun{SysError} can be called from +\fun{Setup} when the socket could not be setup correctly, +and from \fun{HandleRead} +when the connection could not be accepted. +?*/ +void +UchAgent :: Error (errtype how, const char* who, const char* what) +{ + ::Error (how, who, what); +} + +/*? +Setup the server if necessary, then scan and process its channel set +by calling \fun{LoopScan} for it. +To have a server active, you need at least to initialize it (and thus know its address), +and then run it. +If you have added your own channels to the channel set of the server, +their \fun{HandleRead} and \fun{HandleWrite} functions will be called normally by \fun{Run}. +?*/ +void +UchAgent :: Run () +{ + if (! ChanSet) + if (! Setup ()) + Error (ErrFatal, "UchAgent::Run", "could not setup"); + if (ChanSet) + ChanSet->LoopScan (); +} + +/*? +Send a message to all the agents currently connected to this one. +If \var{flush} is TRUE, the output buffer of each remote agent is flushed. +?*/ +void +UchAgent :: Broadcast (UchMessage& msg, bool flush) +{ + CcuListIterOf li (RemoteAgents); + while (++li) + (*li)->Send (msg, flush); +} + +/*? +Send a message to all the agents currently connected to this one, except \var{exclude}. +If \var{flush} is TRUE, the output buffer of each client is flushed. +?*/ +void +UchAgent :: Broadcast (UchMessage& msg, UchRemoteAgent* excl, bool flush) +{ + CcuListIterOf li (RemoteAgents); + while (++li) + if (*li != excl) + (*li)->Send (msg, flush); +} + +/*?nextdoc?*/ +void +UchAgent :: Broadcast (UchMsgBuffer& buf, bool flush) +{ + CcuListIterOf li (RemoteAgents); + while (++li) + (*li)->Send (buf, flush); +} + +/*? +These functions are similar to the previous ones, except that they take +a buffer instead of a message. The buffer {\em must} contain a converted message. +It is more efficient to broadcast a buffer than a message because there is less +message conversion overhead. +?*/ +void +UchAgent :: Broadcast (UchMsgBuffer& buf, UchRemoteAgent* excl, bool flush) +{ + CcuListIterOf li (RemoteAgents); + while (++li) + if (*li != excl) + (*li)->Send (buf, flush); +} + +#ifdef DOC +//fake entries for inline functions + +/*? +Return the channel set used by the server. +This is useful if you want to add your own channels to the channel set. +?*/ +UchMultiplexer* +UchAgent :: GetChanSet () +{ +} +#endif + +UchRemoteAgent* +UchAgent :: Contact (UchAddress* a) +{ + if (!a) + return 0; + UchRemoteAgent* ra = new UchRemoteAgent (this, a); + if (ra) + ChanSet->Add (ra); + return ra; +} + +/*?nodoc?*/ +UchRemoteAgent :: UchRemoteAgent (UchAgent* a) +: UchMsgStream (), + MyLocalAgent (a) +{ +} + +/*?nodoc?*/ +UchRemoteAgent :: UchRemoteAgent (const UchRemoteAgent& cl) +: UchMsgStream (cl), + MyLocalAgent (cl.MyLocalAgent) +{ +} + +/*? +Construct a new agent connected to this one on channel \var{ch}. +?*/ +UchRemoteAgent :: UchRemoteAgent (UchAgent* a, UchChannel* ch) +: UchMsgStream (), + MyLocalAgent (a) +{ + UchChannel::Open (ch->FilDes ()); +} + +UchRemoteAgent :: UchRemoteAgent (UchAgent* a, UchAddress* addr) +: UchMsgStream (addr, 0), + MyLocalAgent (a) +{ +} + + +/*?nodoc?*/ +UchRemoteAgent :: ~UchRemoteAgent () +{ + if (MyLocalAgent) { + UchAgent* s = MyLocalAgent; + s->Error (ErrWarn, "~UchRemoteAgent", "remote agent still connected; deleting anyway ...\n"); + s->RemoveRemoteAgent (this); + if (s->ChanSet) + s->ChanSet->Remove (*this); // calls the destructor if no more refs + } +} + +/*?nodoc?*/ +UchChannel* +UchRemoteAgent :: Copy () const +{ + return new UchRemoteAgent (*this); +} + +/*? +This function must be used to delete a remote agent explicitly. +It is not safe to use the operator delete. +This function is called when an end of file is read from the client; +this means that you usually do not need to call it. +?*/ +void +UchRemoteAgent :: Delete () +{ + if (MyLocalAgent) { + UchAgent* s = MyLocalAgent; + s->RemoveRemoteAgent (this); + if (s->ChanSet) + s->ChanSet->Remove (*this); // calls the destructor if no more refs + } +} + + + +#ifdef DOC +//fake entries for inline functions + +/*? +Return the server corresponding to a given client. +?*/ +UchAgent* +UchRemoteAgent :: GetAgent () +{ +} + +#endif /* DOC */ + diff --git a/comm/OLD/Agent.h b/comm/OLD/Agent.h new file mode 100644 index 0000000..e7db419 --- /dev/null +++ b/comm/OLD/Agent.h @@ -0,0 +1,82 @@ +/* + * The Unix Channel + * + * by Michel Beaudouin-Lafon + * + * Copyright 1990-1993 + * Laboratoire de Recherche en Informatique (LRI) + * + * Agents, by Stephane Chatty + * + * $Id$ + * $CurLog$ + */ + +#ifndef Agent_H_ +#define Agent_H_ + +#include "ccu/List.h" +#include "MsgStream.h" +#include "Multiplexer.h" +#include "error.h" + +class UchRemoteAgent; + +class UchAgent : public UchStream { +friend class UchRemoteAgent; + +protected: + CcuListOf RemoteAgents; + pUchMultiplexer ChanSet; + + void HandleRead (); + +public: + UchAgent (); + UchAgent (const UchAgent&); + UchAgent (UchAddress*); + ~UchAgent (); + + UchChannel* Copy () const; + + bool Setup (UchMultiplexer* cs = 0); +inline UchMultiplexer* GetMultiplexer () { return ChanSet; } + + UchRemoteAgent* Contact (UchAddress*); + void RemoveRemoteAgent (UchRemoteAgent*); + void Run (); + void Unlisten (); + + void Broadcast (UchMessage&, bool = FALSE); + void Broadcast (UchMessage&, UchRemoteAgent*, bool = FALSE); + void Broadcast (UchMsgBuffer&, bool = FALSE); + void Broadcast (UchMsgBuffer&, UchRemoteAgent*, bool = FALSE); + +virtual void HandleNew (UchRemoteAgent*); +virtual void HandleRemove (UchRemoteAgent*); +virtual bool SysError (errtype, const char*, int = 0, int = 0); +virtual void Error (errtype, const char*, const char*); +}; + + +class UchRemoteAgent : public UchMsgStream { +friend class UchAgent; + +protected: + UchAgent *MyLocalAgent; + +public: + UchRemoteAgent (UchAgent*); + UchRemoteAgent (const UchRemoteAgent& cl); + UchRemoteAgent (UchAgent*, UchAddress*); + UchRemoteAgent (UchAgent*, UchChannel* ch); + ~UchRemoteAgent (); + + UchChannel* Copy () const; + void Delete (); + +inline UchAgent* GetLocalContact () { return MyLocalAgent; } +}; + + +#endif /* Agent_H_ */ diff --git a/comm/OLD/Event.cc b/comm/OLD/Event.cc new file mode 100644 index 0000000..5fdef7c --- /dev/null +++ b/comm/OLD/Event.cc @@ -0,0 +1,121 @@ +/* + * The Unix Channel + * + * by Michel Beaudouin-Lafon + * + * Copyright 1993 + * Centre d'Etudes de la Navigation Aerienne (CENA) + * + * Events, by Stephane Chatty + * + * $Id$ + * $CurLog$ + */ + +#include "Event.h" +#include + +UchEventFeatureList :: UchEventFeatureList (const UchEventFeatureList& base, int nbf, UchEventFeature* f) +: CcuListOf (base) +{ + Load (nbf, f); +} + +UchEventFeatureList :: UchEventFeatureList (int nbf, UchEventFeature* f) +: CcuListOf () +{ + Load (nbf, f); +} + +void +UchEventFeatureList :: Load (int nbf, UchEventFeature* f) +{ + while (nbf-- > 0) + Append (f++); +} + +UchEventFeatureList :: ~UchEventFeatureList () +{ +} + + +CcuListOf * UchEventType::AllTypes = 0; + +void +UchEventType :: ClassInit () +{ + AllTypes = new CcuListOf ; +} + +UchEventType :: UchEventType (const char* name, int nbf, UchEventFeature f []) +: Name (name), +#if 0 + Features (), +#endif + NbFeatures (nbf), + FeaturesOffsets (new int [nbf]) +{ + if (!AllTypes) + ClassInit (); + AllTypes->Append (this); + int offset = 0; + for (int i = 0; i < nbf; ++i) { + offset += f[i].Size; + FeaturesOffsets [i] = offset; + } +} + +UchEventType :: UchEventType (const char* name, const UchEventFeatureList& fl) +: Name (name), +#if 0 + Features (), +#endif + NbFeatures (fl.Length ()), + FeaturesOffsets (new int [NbFeatures]) +{ + if (!AllTypes) + ClassInit (); + AllTypes->Append (this); + int offset = 0; + int i = 0; + CcuListIterOf f = fl; + while (++f) { + offset += (*f)->Size; + FeaturesOffsets [i] = offset; + ++i; + } +} + +UchEventType :: ~UchEventType () +{ + AllTypes->Remove (this); + delete [] FeaturesOffsets; +} + +UchEvent :: UchEvent (const UchEventType* t) +: Type (*t) +{ + Data = new char [t->GetTotalSize ()]; +} + +UchEvent :: ~UchEvent () +{ + delete [] Data; +} + +void +UchEvent :: SetFeature (int i, const void* data) +{ + int offset = Type.GetOffset (i); + int size = Type.GetSize (i); + memcpy (Data+offset, data, size); +} + +void +UchEvent :: GetFeature (int i, void* data) +{ + int offset = Type.GetOffset (i); + int size = Type.GetSize (i); + memcpy (data, Data+offset, size); +} + diff --git a/comm/OLD/Event.h b/comm/OLD/Event.h new file mode 100644 index 0000000..27f3907 --- /dev/null +++ b/comm/OLD/Event.h @@ -0,0 +1,92 @@ +/* + * The Unix Channel + * + * by Michel Beaudouin-Lafon + * + * Copyright 1993 + * Centre d'Etudes de la Navigation Aerienne (CENA) + * + * Events, by Stephane Chatty + * + * $Id$ + * $CurLog$ + */ + +#ifndef UchEvent_H_ +#define UchEvent_H_ + +#include "cplus_bugs.h" +#include "global.h" +#include "ccu/String.h" +#include "ccu/List.h" + +#if 0 +class UchEventFeature { +protected: + CcuString Name; + UchEventFeatureType& Type; + +public: +inline UchEventFeature (const char* n, UchEventFeatureType& t) : Name (n), Type (t) {} +inline ~UchEventFeature () {} +}; +#endif + +struct UchEventFeature { + const char* Name; + int Size; +}; + + +class UchEventFeatureList : public CcuListOf { +private: + void Load (int, UchEventFeature*); + +public: + UchEventFeatureList (int, UchEventFeature*); + UchEventFeatureList (const UchEventFeatureList&, int, UchEventFeature*); + ~UchEventFeatureList (); +}; + + +class UchEventType { +private: +static void ClassInit (); + +protected: +static CcuListOf * AllTypes; + CcuString Name; +#if 0 + CcuListOf Features; +#endif + int NbFeatures; + int* FeaturesOffsets; + +public: + UchEventType (const char*, int, UchEventFeature []); + UchEventType (const char*, const UchEventFeatureList&); + ~UchEventType (); +inline int operator == (const UchEventType& evt) const { return (this == &evt); } +inline int GetTotalSize () const { return FeaturesOffsets [NbFeatures - 1]; } +inline int GetOffset (int i) const { return i ? FeaturesOffsets [i-1] : 0; } +inline int GetSize (int i) const { return FeaturesOffsets [i] - (i ? FeaturesOffsets [i-1] : 0); } +#if 0 +inline void AddFeature (const char* fn, UchEventFeatureType& t) { Features->Append (new UchEventFeature (fn, t); } +inline void RemoveFeature () { } +#endif +}; + +class UchEvent { +protected: + const UchEventType& Type; + char* Data; + +public: + UchEvent (const UchEventType*); + ~UchEvent (); + void SetFeature (int, const void*); + void GetFeature (int, void*); +inline const UchEventType& GetType () const { return Type; } +}; + +#endif /* UchEvent_H_ */ diff --git a/comm/OLD/PortServer.cc b/comm/OLD/PortServer.cc new file mode 100644 index 0000000..db4b020 --- /dev/null +++ b/comm/OLD/PortServer.cc @@ -0,0 +1,410 @@ +/* + * The Unix Channel + * + * by Michel Beaudouin-Lafon + * + * Copyright 1990-1993 + * Laboratoire de Recherche en Informatique (LRI) + * + * Port server: client side + * + * $Id$ + * $CurLog$ + */ + +#include "PortServer.h" +#include "PortServerReq.h" +#include "error.h" +#include "Datagram.h" +#include "ccu/Timer.h" + +#include +#include +// sometimes, cuserid is declared in stdio.h ... +#include +#include + +/*?class UchPortServer +The class \typ{UchPortServer} described here implements the communication with the port server. +Both servers and clients need to use this class. +The server registers its address with a well-known key in the port server, +while a client uses that key to retrieve the server's address. +?*/ + +/*? +Construct a link to the port server, given a service name and a host name. +The service name must be present in the service database +(usually the file \com{/etc/services} or a yellow pages map). +The host name identifies the machine on which the port server lives. +If it is the null pointer, the loopback host is assumed. +?*/ +UchPortServer :: UchPortServer (const char* serv, const char* host) +: Name () +{ + /* get service name */ + struct servent* service = getservbyname (serv, 0); + sword portno; + if (service) { + portno = ntohs (service->s_port); + } else { + const char* port = getenv (serv); + if (port) + portno = atoi (port); + else + Error (ErrFatal, "getservbyname", "service not available"); + } + + /* open datagram socket */ + if (host) + Serv = new UchDatagram (0, new UchInetAddress (host, portno)); + else + Serv = new UchDatagram (0, new UchInetAddress (LOOPBACK, portno)); + if (!Serv->Setup ()) + SysError (ErrFatal, "Setup"); + Name = serv; +} + +/*?nodoc?*/ +UchPortServer :: ~UchPortServer () +{ + delete Serv; + Serv = 0; +} + +static void +SendRequest (UchDatagram* to, UchPortServerReq req) +{ + UchMsgBuffer buf (256); + buf.Append (req); + to->Write (buf); +} + +/*? +Register a server address in the port server. +The actual key that is stored in the port server is computed by \fun{MakeKey(key)}. +This function is to be used by servers that want to register themselves. +If the host field of \var{addr} is \var{LOOPBACK} or \var{ANYADDR}, the actual host id +is registered instead. This makes it possible to retrieve a valid address from another host. +\var{id} is used as an identication key when the server asks to be removed. +If it is 0, the process number of the server (\fun{getpid()}) is used. +?*/ +void +UchPortServer :: Register (const char* key, UchInetAddress& addr, int id) +{ + long host = addr.Host (); + if (host == LOOPBACK || host == ANYADDR) + host = UchInetAddress::LoopBack (); + if (! id) + id = getpid (); + UchPortServerReq req (PortServRegister, host, addr.Port (), id, MakeKey (key)); + SendRequest (Serv, req); +} + +/*? +Remove a registered service from the port server. +This function should be called by a server when it exits. +\var{id} authenticates the server so that any other process is unlikely +to be able to remove the service. +If it is 0, the process number of the server (\fun{getpid()}) is used. +If a server fails to remove itself, the association will stay forever in the port server, +unless it is overwritten by the registration of the same key. +?*/ +void +UchPortServer :: Remove (const char* key, UchInetAddress& addr, int id) +{ + long host = addr.Host (); + if (host == LOOPBACK || host == ANYADDR) + host = UchInetAddress :: LoopBack (); + if (! id) + id = getpid (); + UchPortServerReq req (PortServRemove, host, addr.Port (), id, MakeKey (key)); + SendRequest (Serv, req); +} + +#if 0 +static jmp_buf JmpTimeOut; + +static void +Timeout (int) +{ + longjmp (JmpTimeOut, 1); +} +#else +static bool expired = FALSE; +static void +Expire (Millisecond) +{ + expired = TRUE; +} +#endif + +/*? +Get a server address from the port server. +Wait for \var{timeout} seconds before giving up: +the port server may not respond because it is not present (the communication uses datagrams). +0 is returned if the function failed (because of the timeout or because +the given key is not registered); +else a newly allocated address is returned. +If the host field of the retrieved address is the same as the host id, +it is replaced by the loopback host. +This function is to be used by clients to retrieve the address of the server they want to connect to. +?*/ +UchInetAddress* +UchPortServer :: Inquire (const char* key, int timeout) +{ + UchPortServerReq req (PortServInquire, 0, 0, 0, MakeKey (key)); + SendRequest (Serv, req); + UchMsgBuffer buf (256); +#if 0 + if (setjmp (JmpTimeOut)) { + alarm (0); + Error (ErrWarn, "UchPortServer::Inquire", "timeout"); + return 0; + } + signal (SIGALRM, Timeout); + alarm (timeout); + int n = Serv->Receive (buf); + alarm (0); +#else + expired = FALSE; + CcuTimer timer (timeout * 1000, Expire); + int n = Serv->Receive (buf); + if (expired) { + Error (ErrWarn, "UchPortServer::Inquire", "timeout"); + return 0; + } +#endif + if (n <= 0) { + SysError (ErrWarn, "Inquire"); + return 0; + } + if (! buf.Get (&req)) { + Error (ErrWarn, "UchPortServer::Inquire", "could not read answer"); + return 0; + } + switch (req.Type) { + case PortServAnswer : + return new UchInetAddress (req.Host == UchInetAddress::LoopBack () ? LOOPBACK : req.Host, req.Port); + case PortServFail : + return 0; + } + Error (ErrWarn, "UchPortServer::Inquire", "unexpected answer type"); + return 0; +} + +/*? +Retrieve all entries in the port server that match a given key. +The key is a regular expression in the form of \com{ed(1)}. +For each match, the function \fun{f} is called with four arguments: +the matched string, its associated host and port numbers, and the original key. +If it returns 0, \fun{Match} returns immediately. +The type \typ{^{PortServMatchFun}} is declared like this:\\ +\com{typedef int (* PortServMatchFun) (const char*, lword, sword, const char*)}\\ +\fun{Match} waits for \var{timeout} seconds before giving up while waiting an answer: +the port server may not respond because it is not present (the communication uses datagrams). +The function returns the total number of expected answers (it can be 0), or -1 if an error occurs. +?*/ +int +UchPortServer :: Match (const char* key, PortServMatchFun f, int timeout) +{ + int nb = 0; + int call = 1; + + UchPortServerReq req (PortServMatch, 0, 0, 0, key); + SendRequest (Serv, req); + UchMsgBuffer buf (1024); + for (;;) { + buf.Flush (); +#if 0 + if (setjmp (JmpTimeOut)) { + alarm (0); + Error (ErrWarn, "UchPortServer::Match", "timeout"); + return 0; + } + signal (SIGALRM, Timeout); + alarm (timeout); + int n = Serv->Receive (buf); + alarm (0); +#else + expired = FALSE; + CcuTimer timer (timeout * 1000, Expire); + int n = Serv->Receive (buf); + if (expired) { + Error (ErrWarn, "UchPortServer::Match", "timeout"); + return 0; + } +#endif + if (n <= 0) { + SysError (ErrWarn, "Match"); + break; + } + if (! buf.Get (&req)) { + Error (ErrWarn, "Match", "could not read message"); + continue; + } + switch (req.Type) { + case PortServMatch: + if (call) + call = f (req.Key, req.Host, req.Port, key); + nb++; + break; + case PortServEndMatch: + if (nb != req.Ident) + Error (ErrWarn, "Match", "lost answers"); + return req.Ident; + default: + Error (ErrWarn, "Match", "unexpected answer type"); + return -1; + } + } + return nb; +} + +/*? +Ask the port server to dump its state. +This is not normally used unless for debugging. +Note that this is different from the save feature of the port server. +\fun{Dump} uses a different formats, and always uses the standard output of the port server. +?*/ +void +UchPortServer :: Dump () +{ + UchPortServerReq req (PortServDump); + SendRequest (Serv, req); +} + +/*? +Ask the port server to quit. +If the port server was configured to save its state, it does so before exiting. +NOTE: because the state cannot be reloaded from a file, this function +is dangerous: it makes the port server loose its associations. +?*/ +void +UchPortServer :: Quit () +{ + UchPortServerReq req (PortServQuit); + SendRequest (Serv, req); +} + +/*? +Build the actual key that will be stored in the port server. +The key is made of a set of components normally separated by colons. +Each component can be a literal string or a meta-character: +\com{\%h} for hostname, \com{\%s} for service name, \com{\%u} for user name. +A sample key is ``\com{\%s:\%u:foo}''. +\com{\%u} and \com{\%h} are useful when a given service needs a different server, +and thus a different association in the port server, +for different users and/or hosts. +\com{\%s} is useful if the same port server is used for different services. +?*/ +char* +UchPortServer :: MakeKey (const char* key) +{ + static char buffer [256]; + char* p = buffer; + + for (const char* q = key; *q; q++) { + if (*q == '%') { + switch (*++q) { + case 'h' : + if (gethostname (p, sizeof (buffer) - (p - buffer)) < 0) { + SysError (ErrWarn, "UchPortServer::MakeKey"); + strcpy (p, "???"); + } + break; + case 'u' : { + char* l = getlogin (); + if (! l) + l = cuserid (0); + if (! l) { + Error (ErrWarn, "UchPortServer::MakeKey", "getlogin failed"); + strcpy (p, "???"); + } + strcpy (p, l); + } + break; + case 's' : + strcpy (p, Name); + break; + default : + *p++ = '%'; + *p++ = *q; + *p = 0; + break; + } + p += strlen (p); + } else + *p++ = *q; + } + *p = 0; + + return buffer; +} + +#ifdef DOC +/*?nextdoc?*/ +void +PortServerRegister (const char* service, const char* name, UchAddress* addr) +{ } +#endif /* DOC */ + +/*? +This global function can be used for simple registration to the portserver. +The key used is ``\com{\%s:\%u:name}'', and the default identifier is the process number. +It connects to the local port server. +This function is overloaded for an easy use with \fun{UchAddress::GetAddr} +because it returns an \typ{UchAddress*}. +Be sure however that the address is an internet address (and not a Unix address for instance). +?*/ +void +PortServerRegister (const char* service, const char* name, UchInetAddress& addr) +{ + UchPortServer ps (service); + char key [256]; + strcpy (key, "%s:%u:"); + strcat (key, name); + ps.Register (key, addr, getpid ()); +} + +#ifdef DOC +/*?nextdoc?*/ +void +PortServerRemove (const char* service, const char* name, UchAddress* addr) +{ +} +#endif /* DOC */ + +/*? +This global function can be used for a simple removal from the portserver. +The key used is ``\com{\%s:\%u:name}'', and the default identifier is the process number. +It connects to the local port server. +This function is overloaded for an easy use with \fun{UchAddress::GetAddr} +because it returns an \typ{UchAddress*}. +Be sure however that the address is an internet address (and not a Unix address for instance). +?*/ +void +PortServerRemove (const char* service, const char* name, UchInetAddress& addr) +{ + UchPortServer ps (service); + char key [256]; + strcpy (key, "%s:%u:"); + strcat (key, name); + ps.Remove (key, addr, getpid ()); +} + +/*? +This global function can be used for a simple inquiry to the portserver. +The key used is ``\com{\%s:\%u:name}''. +It connects to the port server on the given host (default is local). +?*/ +UchInetAddress* +PortServerInquire (const char* service, const char* name, const char* host) +{ + UchPortServer ps (service, (host && *host) ? host : 0); + char key [256]; + strcpy (key, "%s:%u:"); + strcat (key, name); + return ps.Inquire (key); +} + + diff --git a/comm/OLD/PortServer.h b/comm/OLD/PortServer.h new file mode 100644 index 0000000..1cd8ff5 --- /dev/null +++ b/comm/OLD/PortServer.h @@ -0,0 +1,48 @@ +/* + * The Unix Channel + * + * by Michel Beaudouin-Lafon + * + * Copyright 1990-1993 + * Laboratoire de Recherche en Informatique (LRI) + * + * Port server : client side + * + * $Id$ + * $CurLog$ + */ + +#ifndef PortServer_H_ +#define PortServer_H_ + +#include "Socket.h" +#include "ccu/String.h" +class UchDatagram; + +typedef int (* PortServMatchFun) (const char*, lword, sword, const char*); + +class UchPortServer { +protected: + UchDatagram* Serv; + CcuString Name; + +public: + UchPortServer (const char*, const char* = 0); + ~UchPortServer (); + + void Register (const char*, UchInetAddress&, int = 0); + void Remove (const char*, UchInetAddress&, int = 0); + UchInetAddress* Inquire (const char*, int = 10); + int Match (const char*, PortServMatchFun, int = 10); + void Dump (); + void Quit (); + char* MakeKey (const char*); +}; + +extern void PortServerRegister (const char*, const char*, UchInetAddress&); +extern void PortServerRemove (const char*, const char*, UchInetAddress&); +inline void PortServerRegister (const char* s, const char* h, UchAddress* a) { PortServerRegister (s, h, * ((UchInetAddress*) a)); } +inline void PortServerRemove (const char* s, const char* h, UchAddress* a) { PortServerRemove (s, h, * ((UchInetAddress*) a)); } +extern UchInetAddress* PortServerInquire (const char*, const char*, const char* = 0); + +#endif /* PortServer_H_ */ diff --git a/comm/OLD/PortServerReq.cc b/comm/OLD/PortServerReq.cc new file mode 100644 index 0000000..5628408 --- /dev/null +++ b/comm/OLD/PortServerReq.cc @@ -0,0 +1,60 @@ +/* + * The Unix Channel + * + * by Michel Beaudouin-Lafon + * + * Copyright 1990-1993 + * Laboratoire de Recherche en Informatique (LRI) + * + * Port server requests + * + * $Id$ + * $CurLog$ + */ + +#include "PortServerReq.h" + +UchPortServerReq :: UchPortServerReq () +: UchMessage (), + Type (0), + Host (0), + Port (0), + Ident (0), + Key (0) +{ +} + +UchPortServerReq :: UchPortServerReq (sword t) +: Type (t), + Host (0), + Port (0), + Ident (0), + Key (0) +{ +} + +UchPortServerReq :: UchPortServerReq (sword t, lword h, sword p, lword i, const char* k) +: Type (t), + Host (h), + Port (p), + Ident (i), + Key (k) +{ +} + +UchPortServerReq :: ~UchPortServerReq () +{ +} + +void +UchPortServerReq :: ReadFrom (UchMsgBuffer& b, lword) +{ + b >> Type >> Host >> Port >> Ident; + Key = b.BufLength () ? (const char*) b.Buffer () : 0; +} + +void +UchPortServerReq :: WriteTo (UchMsgBuffer& b) +{ + b << Type << Host << Port << Ident << (char*) Key; +} diff --git a/comm/OLD/PortServerReq.h b/comm/OLD/PortServerReq.h new file mode 100644 index 0000000..febde80 --- /dev/null +++ b/comm/OLD/PortServerReq.h @@ -0,0 +1,45 @@ +/* + * The Unix Channel + * + * by Michel Beaudouin-Lafon + * + * Copyright 1990-1993 + * Laboratoire de Recherche en Informatique (LRI) + * + * Port server requests + * + * $Id$ + * $CurLog$ + */ + +#ifndef PortServerReq_H_ +#define PortServerReq_H_ + +#include "MsgBuffer.h" +#include "Message.h" + +enum PortServMessages { + PortServRegister, PortServRemove, PortServInquire, + PortServMatch, PortServEndMatch, PortServAnswer, + PortServFail, PortServDump, PortServQuit +}; + +class UchPortServerReq : public UchMessage { +public: + sword Type; + lword Host; + sword Port; + const char* Key; + lword Ident; + + UchPortServerReq (); + UchPortServerReq (sword); + UchPortServerReq (sword, lword, sword, lword, const char* = 0); + ~UchPortServerReq (); + +inline void SetKey (const char* k) { Key = k; } + void ReadFrom (UchMsgBuffer&, lword); + void WriteTo (UchMsgBuffer&); +}; + +#endif /* PortServerReq_H_ */ diff --git a/comm/OLD/ReqMgr.cc b/comm/OLD/ReqMgr.cc new file mode 100644 index 0000000..3850723 --- /dev/null +++ b/comm/OLD/ReqMgr.cc @@ -0,0 +1,184 @@ +/* + * The Unix Channel + * + * by Michel Beaudouin-Lafon + * + * Copyright 1993 + * Centre d'Etudes de la Navigation Aerienne (CENA) + * + * Request management, by Stephane Chatty + * + * $Id$ + * $CurLog$ + */ + +#include "ReqMgr.h" +#include +#include +#include + +UchReqMgr :: UchReqMgr () +: Name () +{ +} + +UchReqMgr :: ~UchReqMgr () +{ +} + +void +UchReqMgr :: SetName (const char* n) +{ + if (Name) + fprintf (stderr, "client type already set to %s\n", (const char*) Name); + else + Name = n; +} + +void +UchReqMgr :: DumpHeader (const char* file) +{ + ofstream f (file, ios::out); + if (!f) { + extern int errno; + fprintf (stderr, "can't write to %s: %s\n", file, sys_errlist [errno]); + return; + } + f << "/*\n *\tRequests for clients " << Name << "\n"; + f << " *\n *\tThis file was generated by reqgen - do not edit\n*/\n\n"; + f << "#ifndef " << Name << "Req_H_\n"; + f << "#define " << Name << "Req_H_\n\n"; + f << "#include \n\n"; + + CcuListIterOf req (Requests); + while (++req) { + (*req)->DumpHeader (f); + } + f << "#endif\t/* " << Name << "Req_H_ */\n"; +} + + +void +UchReqMgr :: DumpSource (const char* file) +{ + ofstream f (file, ios::out); + if (!f) { + extern int errno; + fprintf (stderr, "can't write to %s: %s\n", file, sys_errlist [errno]); + return; + } + + f << "/*\n *\tRequests for clients " << Name << "\n"; + f << " *\n *\tThis file was generated by reqgen - do not edit\n*/\n\n"; + f << "#include \"" << file << ".h\"\n\n"; + + CcuListIterOf req (Requests); + while (++req) { + (*req)->DumpSource (f); + } +} + + +void +RequestType :: DumpHeader (ofstream& f) +{ + f << "class " << Name << " : public UchMessage {\nprotected:\n"; + + CcuListIterOf fields (Fields); + while (++fields) { + RequestField* field = *fields; + f << "\t" << field->GetImpl () << "\t" << field->GetName () << ";\n"; + } + + f << "public:\n\t\t" << Name << " ();\n"; + + CcuListIterOf constr (Constructors); + while (++constr) { + f << "\t\t" << Name << " ("; + CcuListIterOf fields ((*constr)->GetParameters ()); + int first = 1; + while (++fields) { + if (first) + first = 0; + else + f << ", "; + f << (*fields)->GetType (); + } + f << ");\n"; + } + f << "\t\t~" << Name << " ();\n"; + f << "\tvoid\tWriteTo (UchMsgBuffer&);\n"; + f << "\tvoid\tReadFrom (UchMsgBuffer&, lword);\n"; + f << "\tvoid\tActivate ();\n"; + + CcuListIterOf getter (Getters); + while (++getter) { + RequestField* field = *getter; + f << "inline\t" << field->GetType () << "\tGet" << field->GetName () + << " () const { return (" << field->GetType () << ") " << field->GetName () << "; }\n"; + } + + CcuListIterOf setter (Setters); + while (++setter) { + RequestField* field = *setter; + f << "inline\tvoid\tSet" << field->GetName () << " (" << field->GetType () + << " f) { " << field->GetName () << " = (" << field->GetImpl () << ") f; }\n"; + } + + f << "};\n\n"; +} + +void +RequestType :: DumpSource (ofstream& f) +{ + f << Name << " :: " << Name << " ()\n: UchMessage ()\n{\n}\n\n"; + + CcuListIterOf constr (Constructors); + while (++constr) { + f << Name << " :: " << Name << " ("; + CcuListIterOf fields ((*constr)->GetParameters ()); + int i = 0; + while (++fields) { + if (i > 0) + f << ", "; + f << (*fields)->GetType () << " i" << i; + ++i; + } + f << ")\n: UchMessage ()"; + fields.Reset (); + i = 0; + while (++fields) { + f << ",\n " << (*fields)->GetName () << " (i" << i << ")"; + ++i; + } + f << "\n{\n}\n\n"; + } + + f << Name << " :: ~" << Name << " ()\n{\n}\n\n"; + + f << "#ifdef SERVER\n\n"; + + f << "void\n" << Name << " :: ReadFrom (UchMsgBuffer& b, lword l)\n{\n"; + f << "\tb"; + CcuListIterOf field (Fields); + while (++field) { + f << " >> " << (*field)->GetName (); + } + f << ";\n"; + f << "}\n\n"; + + f << "#endif\t/* SERVER */\n\n"; + f << "#ifdef CLIENT\n\n"; + + f << "void\n" << Name << " :: " << "WriteTo (UchMsgBuffer& b)\n{\n"; + f << "\tb"; + field.Reset (); + while (++field) { + f << " << " << (*field)->GetName (); + } + f << ";\n"; + f << "}\n\n"; + + f << "#endif\t/* CLIENT */\n"; +} + diff --git a/comm/OLD/ReqMgr.h b/comm/OLD/ReqMgr.h new file mode 100644 index 0000000..1b02196 --- /dev/null +++ b/comm/OLD/ReqMgr.h @@ -0,0 +1,81 @@ +/* + * The Unix Channel + * + * by Michel Beaudouin-Lafon + * + * Copyright 1993 + * Centre d'Etudes de la Navigation Aerienne (CENA) + * + * Request management, by Stephane Chatty + * + * $Id$ + * $CurLog$ + */ + +#ifndef ReqMgr_H_ +#define ReqMgr_H_ + +#include "ccu/String.h" +#include "ccu/List.h" +class ofstream; + +class RequestField { +protected: + CcuString Name; + CcuString Type; + CcuString Impl; +public: +inline RequestField (const char* n, const char* t, const char* i) : Name (n), Type (t), Impl (i) {} +inline const char* GetName () const { return Name; } +inline const char* GetType () const { return Type; } +inline const char* GetImpl () const { return Impl; } +}; + +class RequestConstructor { +friend int yyparse (); +protected: + CcuListOf Params; +public: +inline void AddParameter (RequestField& f) { Params.Append (&f); } +inline const CcuListOf & GetParameters () const { return Params; } +}; + +class RequestType { +friend int yyparse (); +protected: + CcuString Name; + CcuListOf Fields; + CcuListOf Getters; + CcuListOf Setters; + CcuListOf Constructors; + +public: +inline RequestType (const char* n) : Name (n) { } +inline void AddField (RequestField* f) { Fields.Append (f); } +inline void AddConstructor (RequestConstructor* f) { Constructors.Append (f); } +inline const char* GetName () const { return Name; } + void Dump (ofstream&); + void DumpHeader (ofstream&); + void DumpSource (ofstream&); +}; + + +class UchReqMgr { +friend int yyparse (); + +protected: + CcuString Name; + CcuListOf Requests; + void SetName (const char*); +inline void Add (RequestType* t) { Requests.Append (t); } + +public: + UchReqMgr (); + ~UchReqMgr (); + void Read (const char*); + void Dump (const char*); + void DumpHeader (const char*); + void DumpSource (const char*); +}; + +#endif /* ReqMgr_H_ */ diff --git a/comm/OLD/ReqMgr.l b/comm/OLD/ReqMgr.l new file mode 100644 index 0000000..bad288c --- /dev/null +++ b/comm/OLD/ReqMgr.l @@ -0,0 +1,95 @@ +%{ +/* + * The Unix Channel + * + * by Michel Beaudouin-Lafon + * + * Copyright 1993 + * Centre d'Etudes de la Navigation Aerienne (CENA) + * + * Request management, by Stephane Chatty + * + * $Id$ + * $CurLog$ + */ + +#include +#include +#include "ccu/HashTable.h" +#include "ccu/String.h" +#include "ReqMgr.yacc.h" // produced from the parser file with '-d' option of yacc + +extern int tee (int); +#define RETURN(x) return (tee(x)) + +extern "C" { + int atoi (const char *); + void exit (int); + int read (int, char*, int); +} + + +CcuDictionnary* ResWords; +int LineNo = 1; +static char IdBuf [1024]; + +void +LexInit () +{ + if (!ResWords) { + ResWords = new CcuDictionnary (10); + (*ResWords)["client"] = (void*) (Y_CLIENT); + (*ResWords)["request"] = (void*) (Y_REQUEST); + (*ResWords)["getters"] = (void*) (Y_GETTERS); + (*ResWords)["setters"] = (void*) (Y_SETTERS); + (*ResWords)["const"] = (void*) (Y_CONST); +// (*ResWords)[""] = (void*) (Y_); + } + LineNo = 1; + +#ifdef FLEX_SCANNER +static void yyrestart (FILE*); + yyrestart (0); +#endif +} + +%} + +Int [-]?[0-9]+ +Ident [a-zA-Z_][a-zA-Z_0-9]* +nl [\n] +sp0 [ \t]* +sp1 [ \t]+ +LocHeader \"{Ident}.h\" +GlobHeader \ + +%% + +[,();{}*] RETURN (yytext[0]); + +\%.*\n { /* comments */ + ++LineNo; + } + +{sp1} {} + +{nl} { + ++LineNo; + } + +-> { RETURN (Y_YIELDS); } + +#include +{Ident} { + CcuHashCell* c; + if (c = ResWords->Get (yytext)) { + RETURN(int (c->GetInfo ())); + } else { + yylval.string = NewString (yytext); + RETURN (Y_ID); + } + } + +. { + RETURN (-1); + } diff --git a/comm/OLD/ReqMgr.y b/comm/OLD/ReqMgr.y new file mode 100644 index 0000000..4c29f00 --- /dev/null +++ b/comm/OLD/ReqMgr.y @@ -0,0 +1,285 @@ +%{ + +/* + * The Unix Channel + * + * by Michel Beaudouin-Lafon + * + * Copyright 1993 + * Centre d'Etudes de la Navigation Aerienne (CENA) + * + * Request management, by Stephane Chatty + * + * $Id$ + * $CurLog$ + */ + +#include "ccu/HashTable.h" +extern "C" { + int yyerror (const char*); + int yyparse(); + int yywrap (); + int yylex(); +} + +/* This comes from the lexical analyzer */ +extern CcuDictionnary* ResWords; + +static void error (const char*, ...); + +#include +#include +#include +#include + +#include "ReqMgr.h" + +static UchReqMgr* CurMgr; +static RequestType* CurRequest; +static CcuListOf * CurFieldList; +static CcuDictionnaryOf Fields (16); + +static void AddFieldToReq (const char*, const char*, const char*, const char*, const char*); +static void AddFieldToList (const char*); + +%} + +%union { + int integer; + const char *string; +} + +%token Y_CLIENT Y_REQUEST Y_YIELDS Y_GETTERS Y_SETTERS Y_CONST +%token Y_ID +%type opt_star + +%% + +file: + /* empty */ + | file decl + ; + +decl: + client + | request + ; + +client: + Y_CLIENT Y_ID ';' + { + CurMgr->SetName ($2); + } + ; + +request: + Y_REQUEST Y_ID + { + CurRequest = new RequestType ($2); + CurMgr->Add (CurRequest); + Fields.Clear (); + } + '{' request_entries '}' ';' + ; + +request_entries: + /* empty */ + | request_entries request_entry + ; + +request_entry: + field + | getters + | setters + | constructor + ; + +field: + Y_CONST Y_ID opt_star Y_ID Y_YIELDS Y_ID ';' + { + AddFieldToReq ("const", $2, $3, $4, $6); + } + | Y_ID opt_star Y_ID Y_YIELDS Y_ID ';' + { + AddFieldToReq (0, $1, $2, $3, $5); + } + ; + +opt_star: + /* empty */ { $$ = 0; } + | '*' { $$ = "*"; } + ; + +getters: + Y_GETTERS + { + CurFieldList = &CurRequest->Getters; + } + '(' id_list ')' ';' + ; + +setters: + Y_SETTERS + { + CurFieldList = &CurRequest->Setters; + } + '(' id_list ')' ';' + ; + +constructor: + Y_ID + { + if (strcmp ($1, CurRequest->GetName ()) != 0) + fprintf (stderr, "unknown name %s in request type %s. Considering as constructor.\n", + $1, CurRequest->GetName ()); + RequestConstructor* c = new RequestConstructor; + CurRequest->AddConstructor (c); + CurFieldList = &c->Params; + } + '(' id_list ')' ';' + ; + +id_list: + /* empty */ + | Y_ID { AddFieldToList ($1); } + | id_list ',' Y_ID { AddFieldToList ($3); } + ; + +%% +#include + +static void +AddFieldToReq (const char* c, const char* t, const char* s, const char* id, const char* impl) +{ + int found; + CcuHashCellOf * hc = Fields.Add (id, &found); + if (found) { + fprintf (stderr, "warning: duplicate field %s in request %s. Ignoring. \n", + id, CurRequest->GetName ()); + } else { + char buf [128]; + if (c || s) { + sprintf (buf, "%s %s %s", (c ? c : ""), t, (s ? s : "")); + t = buf; + } + RequestField* f = new RequestField (id, t, impl); + hc->SetInfo (f); + CurRequest->AddField (f); + } +} + +static void +AddFieldToList (const char* name) +{ + RequestField* f = Fields [name]; + if (!f) { + fprintf (stderr, "warning: unknown field %s in request %s\n", + name, CurRequest->GetName ()); + } else { + CurFieldList->Append (f); + } +} + +int _DoTee = 0; + +int +tee (int x) +{ + if (_DoTee) { + char *s; + switch (x) { + case '(': + s = "("; + break; + case ')': + s = ")"; + break; + case '*': + s = "*"; + break; + case ';': + s = ";"; + break; + case ',': + s = ","; + break; + case '{': + s = "}"; + break; + case '\n': + s = "\n"; + break; + case Y_ID: + s = "ID"; + break; + case Y_CLIENT: + s = "CLIENT"; + break; + case Y_REQUEST: + s = "REQUEST"; + break; + case Y_YIELDS: + s = "->"; + break; + case Y_GETTERS: + s = "GETTERS"; + break; + case Y_SETTERS: + s = "SETTERS"; + break; +/* case Y_: + s = ""; + break; +*/ + default : + s = "???"; + break; + } + printf ("%s ", s); + } + return x; +} + +int +yyerror (const char *s) +{ + error ("%s\n",s); + return (0); +} + +int +yywrap () +{ + return 1; +} + +void +UchReqMgr :: Read (const char* file) +{ + extern void LexInit (); + extern FILE *yyin; + LexInit (); + if (!(yyin = fopen (file,"r"))) { + char err [1024]; + sprintf (err, "Cannot open %s", file); + yyerror (err); + return; + } + CurMgr = this; + if (yyparse ()) + fprintf (stderr, "\nParsing failed on %s\n", file); + fclose (yyin); +} + +extern int LineNo; + +static void +error (const char* s, ...) +{ + va_list ap; + va_start(ap, s); + + fprintf (stderr, "Error at line %d: ", LineNo); + vfprintf (stderr, s, ap); + exit (0); +} diff --git a/comm/OLD/Server.cc b/comm/OLD/Server.cc new file mode 100644 index 0000000..a2e908b --- /dev/null +++ b/comm/OLD/Server.cc @@ -0,0 +1,395 @@ +/* + * The Unix Channel + * + * by Michel Beaudouin-Lafon + * + * Copyright 1990-1993 + * Laboratoire de Recherche en Informatique (LRI) + * + * Servers + * + * $Id$ + * $CurLog$ + */ + +#include "Server.h" + +/*?class UchServer +The class \typ{UchServer} derives from \typ{UchStream}. +It is associated to a channel set to monitor the connected clients in parallel. +Whenever a new client connects to the server, the virtual function \fun{HandleNew} is called. +This function must return a pointer to an object of class \typ{UchClient} (or a derived class of \typ{UchClient}). + +Because the server and the clients may be on different machines with different architectures, +the byte swapping is always done by the server, by convention. (NOTE: byte swapping is not currently implemented) +?*/ + +/*?class UchClient +Class \typ{UchClient} derives from \typ{UchMsgStream}. +It is still an abstract class because the virtual functions \fun{NewMessage} and \fun{ConvertAnswer} +of \typ{UchMsgStream} are not defined in \typ{UchClient}. +Instances of \fun{UchClient} only know that they may belong to an instance of \typ{UchServer}. +Thus the virtual function \fun{Delete} is redefined to achieve a clean removal from the server's list. +?*/ + +// server ports +// constructors, +// destructor +// copy +// + +/*? +Construct an empty server port. +?*/ +UchServer :: UchServer () +: UchStream (), + Clients (), + ChanSet (0) +{ +} + +/*?nodoc?*/ +UchServer :: UchServer (const UchServer& sp) +: UchStream (sp), + Clients (), + ChanSet (0) +{ +} + +/*? +Construct a server port bound to address \var{a}. +When using Internet domain addresses, \var{a} should refer to +the wildcard address so that clients can connect from any machine. +This can be done with the instruction \com{new UchInetAddress(ANYADDR)}. +?*/ +UchServer :: UchServer (UchAddress* a) +: UchStream (a, 0), + Clients (), + ChanSet (0) +{ +} + +/*?nodoc?*/ +UchServer :: ~UchServer () +{ + UchClient* cl = 0; + + while (cl = Clients.First ()) + cl->Delete (); + ChanSet = 0; // will delete it if necessary +} + +/*?nodoc?*/ +UchChannel* +UchServer :: Copy () const +{ + return new UchServer (*this); +} + +/*? +Remove a client from the set of clients connected to the server. +This is normally done automatically whenever the client disconnects itself, +as a side effect of destroying the client. +?*/ +void +UchServer :: RemoveClient (UchClient* cl) +{ + if (Clients.Remove (cl)) { + cl->MyServer = 0; + HandleRemove (cl); + } else + Error (ErrWarn, "UchServer::RemoveClient", "not owned by this server"); +} + +/*? +Bind the socket and listen to it. +Initialize the channel set of the server to \var{cs}, or create a new one if \var{cs} is 0. +The channel set is used by the server to read and process messages from its clients: +whenever a client connects to the server, an instance of \typ{UchClient} is added to the channel set. +Whenever data is readable from a client, it is read in an input buffer and processed as soon +as a full message is received. +Whenever a client closes the connection, it is removed from the channel set of the server. +This function returns TRUE if all went well, else it calls \fun{SysError} and returns FALSE. +You may want to pass your own channel set if you are already multiplexing +i/o on that channel set. For instance, you may have several \typ{UchServer} objects +in your application. +A smart pointer to the channel set is actually kept in the server, so that it can be shared +securely. +?*/ +bool +UchServer :: Setup (UchMultiplexer* cs) +{ + if (Listen () < 0) { + SysError (ErrWarn, "UchServer::Setup"); + return FALSE; + } + SetMode (IORead); + if (! cs) + cs = new UchMultiplexer; + ChanSet = cs; + ChanSet->Add (this); + + return TRUE; +} + +/*? +This function removes the server from its channel set. +This has the effect of ignoring any incoming connections. +This function will also make \fun{Run} exit, if there is no other channel in the channel set of the server. +Unless you added your own channels to this channel set, +this will be the case if there is no client currently connected. +This is the only way to exit properly from \fun{Run}. +?*/ +void +UchServer :: Unlisten () +{ + if (ChanSet) + ChanSet->Remove (*this); +} + + +// handle connections on server port +// + +/*?nodoc?*/ +void +UchServer :: HandleRead () +{ +// cannot redefine Accept to return UchClient*, so this does not work : +// UchClient* cl = Accept (); +// need to delete the channel created by Accept, so this does not work either : +// UchClient* cl = new UchClient (Accept ()); +// so we do this (less elegant ...) : + UchChannel* ch = Accept (); + if (! ch) { + SysError (ErrWarn, "UchServer::HandleRead: Accept"); + return; + } + UchClient* cl = HandleNew (ch); + delete ch; + + cl->SetMode (IOReadSelect); + cl->SetOwner (this); + Clients.Append (cl); + if (ChanSet) + ChanSet->Add (cl); +} + +// Default virtual function for handling new connections +// If a class MY_CLIENT is derived from UchClient, +// a class MY_SERVER must be derived from UchServer, +// with MY_SERVER::HandleNew (UchChannel* ch) +// doing at least a return new MY_CLIENT (ch) +// +/*? +This virtual function is called whenever a new client connects to the server. +The default action is to return \com{new UchClient(ch)}. +This needs to be redefined only if you create a derived class of \typ{UchClient}, +say \typ{MY_CLIENT}, in which case it should be redefined in a derived class +of \typ{UchServer} to return \com{new MY_CLIENT(ch)}. +?*/ +UchClient* +UchServer :: HandleNew (UchChannel* ch) +{ + return new UchClient (ch); +} + +/*? +This virtual function is called whenever a client is removed (\fun{RemoveClient}). +It is a hook for the application to take whatever action; the default action is to do nothing. +When this function is called, the client is already removed from the client list of the server. +?*/ +void +UchServer :: HandleRemove (UchClient*) +{ + // nothing +} + +// default error functions +// +/*?nextdoc?*/ +bool +UchServer :: SysError (errtype how, const char* who, int excl1, int excl2) +{ + return ::SysError (how, who, excl1, excl2); +} + +/*? +Each server port class can have its own error handling routines. +Their default action is to call the global functions \fun{Error} and \fun{SysError}. +They can be called from the following functions: +\fun{Error} can be called from +\fun{RemoveClient} when the client does not belong to this server; +\fun{SysError} can be called from +\fun{Setup} when the socket could not be setup correctly, +and from \fun{HandleRead} +when the connection could not be accepted. +?*/ +void +UchServer :: Error (errtype how, const char* who, const char* what) +{ + ::Error (how, who, what); +} + +/*? +Setup the server if necessary, then scan and process its channel set +by calling \fun{LoopScan} for it. +To have a server active, you need at least to initialize it (and thus know its address), +and then run it. +If you have added your own channels to the channel set of the server, +their \fun{HandleRead} and \fun{HandleWrite} functions will be called normally by \fun{Run}. +?*/ +void +UchServer :: Run () +{ + if (! ChanSet) + if (! Setup ()) + Error (ErrFatal, "UchServer::Run", "could not setup"); + if (ChanSet) + ChanSet->LoopScan (); +} + +/*? +Send a message to all clients currently connected to this server. +If \var{flush} is TRUE, the output buffer of each client is flushed. +?*/ +void +UchServer :: Broadcast (UchMessage& msg, bool flush) +{ + CcuListIterOf li (Clients); + while (++li) + (*li)->Send (msg, flush); +} + +/*? +Send a message to all clients currently connected to this server, except \var{exclude}. +If \var{flush} is TRUE, the output buffer of each client is flushed. +?*/ +void +UchServer :: Broadcast (UchMessage& msg, UchClient* excl, bool flush) +{ + CcuListIterOf li (Clients); + while (++li) + if (*li != excl) + (*li)->Send (msg, flush); +} + +/*?nextdoc?*/ +void +UchServer :: Broadcast (UchMsgBuffer& buf, bool flush) +{ + CcuListIterOf li (Clients); + while (++li) + (*li)->Send (buf, flush); +} + +/*? +These functions are similar to the previous ones, except that they take +a buffer instead of a message. The buffer {\em must} contain a converted message. +It is more efficient to broadcast a buffer than a message because there is less +message conversion overhead. +?*/ +void +UchServer :: Broadcast (UchMsgBuffer& buf, UchClient* excl, bool flush) +{ + CcuListIterOf li (Clients); + while (++li) + if (*li != excl) + (*li)->Send (buf, flush); +} + +// clients: +// + +/*?nodoc?*/ +UchClient :: UchClient () +: UchMsgStream (), + MyServer (0) +{ +} + +/*?nodoc?*/ +UchClient :: UchClient (const UchClient& cl) +: UchMsgStream (cl), + MyServer (0) +{ +} + +/*? +Construct a new client connected to the server on channel \var{ch}. +?*/ +UchClient :: UchClient (UchChannel* ch) +: UchMsgStream (), + MyServer (0) +{ + UchChannel::Open (ch->FilDes ()); +} + +/*?nodoc?*/ +UchClient :: ~UchClient () +{ + if (MyServer) { + UchServer* s = MyServer; + s->Error (ErrWarn, "~UchClient", "client still in a server; deleting anyway ...\n"); + s->RemoveClient (this); + if (s->ChanSet) + s->ChanSet->Remove (*this); // calls the destructor if no more refs + } +} + +/*?nodoc?*/ +UchChannel* +UchClient :: Copy () const +{ + return new UchClient (*this); +} + +/*? +This function must be used to delete a client explicitly. +It is not safe to use the operator delete. +This function is called when an end of file is read from the client; +this means that you usually do not need to call it. +?*/ +void +UchClient :: Delete () +{ + if (MyServer) { + UchServer* s = MyServer; + s->RemoveClient (this); + if (s->ChanSet) + s->ChanSet->Remove (*this); // calls the destructor if no more refs + } +} + +/*?nodoc?*/ +void +UchClient :: SetOwner (UchServer* serv) +{ + if (MyServer) + serv->Error (ErrFatal, "UchClient::SetOwner", "already owned"); + else + MyServer = serv; +} + +#ifdef DOC +//fake entries for inline functions + +/*? +Return the channel set used by the server. +This is useful if you want to add your own channels to the channel set. +?*/ +UchMultiplexer* +UchServer :: GetChanSet () +{ +} + +/*? +Return the server corresponding to a given client. +?*/ +UchServer* +UchClient :: GetServer () +{ +} + +#endif /* DOC */ + diff --git a/comm/OLD/Server.h b/comm/OLD/Server.h new file mode 100644 index 0000000..7f8c597 --- /dev/null +++ b/comm/OLD/Server.h @@ -0,0 +1,77 @@ +/* + * The Unix Channel + * + * by Michel Beaudouin-Lafon + * + * Copyright 1990-1993 + * Laboratoire de Recherche en Informatique (LRI) + * + * Servers + * + * $Id$ + * $CurLog$ + */ + +#ifndef Server_H_ +#define Server_H_ + +#include "ccu/List.h" +#include "MsgStream.h" +#include "Multiplexer.h" +#include "error.h" + +class UchServer; + +class UchClient : public UchMsgStream { +friend class UchServer; + +protected: + UchServer *MyServer; + +public: + UchClient (); + UchClient (const UchClient& cl); + UchClient (UchChannel* ch); + ~UchClient (); + + UchChannel* Copy () const; + void Delete (); + +inline UchServer* GetServer () { return MyServer; } + void SetOwner (UchServer*); +}; + +class UchServer : public UchStream { +friend class UchClient; + +protected: + CcuListOf Clients; + pUchMultiplexer ChanSet; + +public: + UchServer (); + UchServer (const UchServer&); + UchServer (UchAddress*); + ~UchServer (); + + UchChannel* Copy () const; + void HandleRead (); + + bool Setup (UchMultiplexer* cs = 0); +inline UchMultiplexer* GetMultiplexer () { return ChanSet; } + void RemoveClient (UchClient*); + void Run (); + void Unlisten (); + void Broadcast (UchMessage&, bool = FALSE); + void Broadcast (UchMessage&, UchClient*, bool = FALSE); + void Broadcast (UchMsgBuffer&, bool = FALSE); + void Broadcast (UchMsgBuffer&, UchClient*, bool = FALSE); +// called by HandleRead when a new client connects: must build up a client from a channel +virtual UchClient* HandleNew (UchChannel*); +virtual void HandleRemove (UchClient*); +virtual bool SysError (errtype, const char*, int = 0, int = 0); +virtual void Error (errtype, const char*, const char*); +}; + + +#endif /* Server_H_ */ diff --git a/comm/OLD/Service.cc b/comm/OLD/Service.cc new file mode 100644 index 0000000..618214b --- /dev/null +++ b/comm/OLD/Service.cc @@ -0,0 +1,328 @@ +/* + * The Unix Channel + * + * by Michel Beaudouin-Lafon + * + * Copyright 1990-1993 + * Laboratoire de Recherche en Informatique (LRI) + * + * Client side: services + * + * $Id$ + * $CurLog$ + */ + +#include "Service.h" + +/*?class UchService +An object of class \typ{UchService} (derived from \typ{UchMsgStream}) exists in a client process +to represent the server it is connected to. + +The class \typ{UchService} is virtual: you must create subclasses that redefine +at least \fun{NewMessage} from class \fun{UchMsgStream}, and if necessary \fun{ConvertAnswer}. +\fun{NewMessage} should decipher the incoming message, transform it into an event, +and put in into the event queue with \fun{PutEvent}. +It can also handle non event messages (for instance errors). +?*/ + +/*?class UchEvtMsgQueue +An event queue is a linked list of events. +Events are normally appended to the end of the queue and extracted from the beginning. +?*/ + +/*?nodoc?*/ +UchEvtMsgQueue :: UchEvtMsgQueue () +: CcuSmartData (), + Queue () +{ +} + +/*?nodoc?*/ +UchEvtMsgQueue :: ~UchEvtMsgQueue () +{ +} + +#ifdef DOC +/*? +Append an event to the queue. +?*/ +void +UchEvtMsgQueue :: Put (UchEventMsg* msg) +{ +} + +/*? +Put an event back in the queue (therefore it becomes the first event of the queue). +?*/ +void +UchEvtMsgQueue :: PutBack (UchEventMsg* msg) +{ +} + +/*? +Return the first event from the queue and remove it. +?*/ +UchEventMsg* +UchEvtMsgQueue :: Get () +{ +} + +/*? +Return the first event from the queue without removing it. +?*/ +UchEventMsg* +UchEvtMsgQueue :: Peek () +{ +} + +#endif /* DOC */ + + +/*? +Construct an empty service. +?*/ +UchService :: UchService () +: UchMsgStream (), + EvQueue (0) +{ +} + +/*? +Construct a service connected to address \var{a}. +?*/ +UchService :: UchService (UchAddress* a) +: UchMsgStream (0, a), + EvQueue (0) +{ +} + +/*?nodoc?*/ +UchService :: UchService (const UchService& s) +: UchMsgStream (s), + EvQueue (s.EvQueue) +{ +} + +/*?nodoc?*/ +UchService :: ~UchService () +{ + EvQueue = 0; // deletes it +} + +/*?nodoc?*/ +UchChannel* +UchService :: Copy () const +{ + return new UchService (*this); +} + +/*? +Set the event queue to be used to store the incoming events. +If not set, a default event queue is created. +This function is intended to share a queue between several servers. +?*/ +void +UchService :: SetEvQueue (UchEvtMsgQueue* evq) +{ + EvQueue = evq; +} + +/*?nextdoc?*/ +UchEventMsg* +UchService :: PeekEvent (bool wait) +{ + Flush (); + if (!EvQueue) + EvQueue = new UchEvtMsgQueue; + UchEventMsg* ev = EvQueue->Peek (); + if (ev || ! wait) + return ev; + while (! ev) { + HandleRead (); + ev = EvQueue->Peek (); + } + return ev; +} + +/*? +These functions flush the output buffer, and check the events already in the queue. +If there is at least one, it is returned; +\fun{GetEvent} also removes it from the event queue. +If the event queue is empty and \var{wait} is TRUE, the function blocks until an event arrives, +else it returns 0 without blocking. +These functions also create the event queue if it was not set with \fun{SetEvQueue}. +?*/ +UchEventMsg* +UchService :: GetEvent (bool wait) +{ + Flush (); + if (!EvQueue) + EvQueue = new UchEvtMsgQueue; + UchEventMsg* ev = EvQueue->Get (); + if (ev || ! wait) + return ev; + while (! ev) { + HandleRead (); + ev = EvQueue->Get (); + } + return ev; +} + +/*?nextdoc?*/ +void +UchService :: PutEvent (UchEventMsg* ev) +{ + if (! EvQueue) + EvQueue = new UchEvtMsgQueue; + ev->From = this; + EvQueue->Put (ev); +} + +/*? +These functions are similar to the functions \fun{Put} and \fun{PutBack} +on the event queue of the server. +They create the event queue if it was not set with \fun{SetEvQueue}, +and set the event's server. +?*/ +void +UchService :: PutBackEvent (UchEventMsg* ev) +{ + if (! EvQueue) + EvQueue = new UchEvtMsgQueue; + ev->From = this; + EvQueue->PutBack (ev); +} + + +/*?class UchEventMsg +This class derives from \typ{UchMessage}, so it inherits the usual virtual functions +\fun{ReadFrom} and \fun{WriteTo} that must be redefined in each derived class. +An event is created when a client receives an asynchronous message from its server. +Events are linked together in event queues. +Events must derive from this class. +?*/ + +#ifdef DOC +// fake entries for inline functions + +/*? +Construct an event. +?*/ +UchEventMsg :: UchEventMsg () +{ +} + +#endif /* DOC */ + +/*?nodoc?*/ +UchEventMsg :: ~UchEventMsg () +{ +} + +#ifdef DOC + +/*? +Return the server that sent this event. +The service is known only if the event was appended to the event queue with \fun{UchService::PutEvent}, +else it is 0. +?*/ +UchService* +UchEventMsg :: GetService () +{ +} + +#endif /* DOC */ + +/*?class UchGenEvtMsg +This is a sample derived class of \typ{UchEventMsg}. +It defines events that contain a pointer to a \typ{UchMessage}. +This message must be allocated dynamically because it is deleted by the destructor. + +The virtual functions \fun{ReadFrom} and \fun{WriteTo} are defined to act upon the message stored in the event. + +The following example fetches a word from the input buffer, +creates a message depending on its value +(\typ{FOO_MSG} and \typ{BAR_MSG} have been derived from \typ{UchMessage}), +and transfers the data from the buffer to the event with \fun{Get}. + +This piece of code typically appears in the body +of \fun{NewMessage}: +\begin{ccode} +UchGenEvtMsg* ev = new UchGenEvtMsg; +sword type; +if (! buf.Peek (&type)) + return; +switch (type) { + case Foo : + ev->SetMsg (new FOO_MSG); + break; + case Bar : + ev->SetMsg (new BAR_MSG); + break; + ... +} + +if (!buf.Get (ev)) + // protocol error + +PutEvent (ev); +\end{ccode} +?*/ + +#ifdef DOC + +/*?nextdoc?*/ +UchGenEvtMsg :: UchGenEvtMsg () +{ } + +/*? +Construct a generic event. The second constructor sets its message. +The message is deleted when the event is destroyed. +Thus the message must have been allocated dynamically. +?*/ +UchGenEvtMsg :: UchGenEvtMsg (UchMessage* m) +{ } + +#endif /* DOC */ + +/*?nodoc?*/ +UchGenEvtMsg :: ~UchGenEvtMsg () +{ + if (Msg) + delete Msg; +} + + +#ifdef DOC + +/*?nextdoc?*/ +void +UchGenEvtMsg :: SetMsg (UchMessage* m) +{ } + +/*? +Set and get the message associated to the event. +When setting the value, the previous message of the event (if any) is deleted. +?*/ +UchMessage* +UchGenEvtMsg :: GetMsg () +{ } + +#endif /* DOC */ + +/*?nodoc?*/ +void +UchGenEvtMsg :: ReadFrom (UchMsgBuffer& buf, lword l) +{ + if (Msg) + Msg->ReadFrom (buf, l); +} + +/*?nodoc?*/ +void +UchGenEvtMsg :: WriteTo (UchMsgBuffer& buf) +{ + if (Msg) + Msg->WriteTo (buf); +} + diff --git a/comm/OLD/Service.h b/comm/OLD/Service.h new file mode 100644 index 0000000..b97a6cf --- /dev/null +++ b/comm/OLD/Service.h @@ -0,0 +1,100 @@ +/* + * The Unix Channel + * + * by Michel Beaudouin-Lafon + * + * Copyright 1990-1993 + * Laboratoire de Recherche en Informatique (LRI) + * + * Client side: services + * + * $Id$ + * $CurLog$ + */ + +#ifndef Service_H_ +#define Service_H_ + +#include "Message.h" +#include "MsgStream.h" +#include "error.h" +#include "cplus_bugs.h" +#include "ccu/List.h" + +class UchService; + +// events are messages sent by a server that are linked into an event list +// +class UchEventMsg : public UchMessage { +friend class UchService; + +protected: + UchMsgStream* From; + +public: + +inline UchEventMsg () : UchMessage (), From (0) { } + ~UchEventMsg (); + +inline UchMsgStream* GetSource () { return From; } +}; + + +class UchGenEvtMsg : public UchEventMsg { +protected: + UchMessage* Msg; + +public: +inline UchGenEvtMsg () : UchEventMsg () { Msg = 0; } +inline UchGenEvtMsg (UchMessage* m) : UchEventMsg () { Msg = m; } + ~UchGenEvtMsg (); + + void WriteTo (UchMsgBuffer&); + void ReadFrom (UchMsgBuffer&, lword); +inline void SetMsg (UchMessage* m) { if (Msg) delete Msg; Msg = m; } +inline UchMessage* GetMsg () { return Msg; } +}; + +// class for storing and retrieving events +// +class UchEvtMsgQueue : public CcuSmartData { + +protected: + CcuListOf Queue; + +public: + UchEvtMsgQueue (); + ~UchEvtMsgQueue (); + +inline void Put (UchEventMsg* m) { Queue.Append (m); } +inline UchEventMsg* Peek () { return Queue.First (); } +inline UchEventMsg* Get () { return Queue.RemoveFirst (); } +inline void PutBack (UchEventMsg* m) { Queue.Prepend (m); } +inline operator const CcuListOf & () const { return Queue; } +}; + +PointerClass (pUchEvtMsgQueue, UchEvtMsgQueue) + +// UchService is the client's view of a server +// we use smart pointers so that the event queue can be shared +// +class UchService : public UchMsgStream { +protected: + pUchEvtMsgQueue EvQueue; + +public: + UchService (); + UchService (UchAddress*); + UchService (const UchService&); + ~UchService (); + + UchChannel* Copy () const; + + void SetEvQueue (UchEvtMsgQueue*); + UchEventMsg* PeekEvent (bool = TRUE); + UchEventMsg* GetEvent (bool = TRUE); + void PutEvent (UchEventMsg*); + void PutBackEvent (UchEventMsg*); +}; + +#endif /* Service_H_ */ diff --git a/comm/OLD/SimpleMessage.cc b/comm/OLD/SimpleMessage.cc new file mode 100644 index 0000000..1346d22 --- /dev/null +++ b/comm/OLD/SimpleMessage.cc @@ -0,0 +1,47 @@ +#ifdef DOC +// fake entries for inline functions + +/*?class UchSimpleMessage +A simple UchMessage.has a single public field \var{Data} of type \typ{lword}. +?*/ + +/*? +Construct a simple message with data 0. +?*/ +UchSimpleMessage :: UchSimpleMessage () +{} + +/*? +Construct a simple message with data \var{d}. +?*/ +UchSimpleMessage :: UchSimpleMessage (lword d) +{} + +/*?class UchStringMessage +A string UchMessage.has a public field \var{Data} of type \typ{char*}. +It is designed to carry a null-terminated string. +?*/ + +/*? +Construct a simple message with data 0. +?*/ +UchStringMessage :: UchStringMessage () +{} + +/*? +Construct a string message with type data \var{d}. +The string is not copied. +?*/ +UchStringMessage :: UchStringMessage (char* d = 0) +{} + +/*? +Change the string of the message and set it to \var{d}. +The string is not copied. +?*/ +void +UchStringMessage :: SetData (char* d) +{} + +#endif /* DOC */ + diff --git a/comm/OLD/SimpleMessage.h b/comm/OLD/SimpleMessage.h new file mode 100644 index 0000000..b4a00ce --- /dev/null +++ b/comm/OLD/SimpleMessage.h @@ -0,0 +1,23 @@ + +class UchSimpleMessage : public UchMessage { +public: + lword Data; + + UchSimpleMessage () { Data = 0; } + UchSimpleMessage (lword d) { Data = d; } + + void WriteTo (UchMsgBuffer& b) { b << Data; } + void ReadFrom (UchMsgBuffer& b, lword) { b >> Data; } +}; + +class UchStringMessage : public UchMessage { +public: + char* Data; + + UchStringMessage () { Data = 0; } + UchStringMessage (char* d) { Data = d; } + + void WriteTo (UchMsgBuffer& b) { b << Data; } + void ReadFrom (UchMsgBuffer& b, lword l) { Data = (char*) b.Buffer (); b.Flush ((int) l); } + void SetData (char* d) { Data = d; } +}; diff --git a/comm/OLD/TextStream.cc b/comm/OLD/TextStream.cc new file mode 100644 index 0000000..a6d12d3 --- /dev/null +++ b/comm/OLD/TextStream.cc @@ -0,0 +1,1030 @@ +/* + * The Unix Channel + * + * by Michel Beaudouin-Lafon + * + * Copyright 1990-1993 + * Laboratoire de Recherche en Informatique (LRI) + * + * Text streams + * + * $Id$ + * $CurLog$ + */ + +#include "TextStream.h" +#include "TimeOut.h" +#include "PortServer.h" +#include "Multiplexer.h" +#include +#include +#include +#include + +// list is a set of words +// the separator must be the first and last char of the string: +// e.g. :yes:no: /foo/bar/toto/ +// word is a word to be looked up in the list. +// we use the same kind of comparison as in cmpname, provided that the words +// in the list are capitalized. +// windex returns 0 if the word was not found, otherwise its index in the list. +int +windex (const char* list, const char* word) +{ + if (! list || ! word) + return 0; + char sep = *list; + if (! sep) + return 0; + register const char* p; + register const char* w = word; + int ind = 1; + for (p = list+1; *p; p++) { + if (w) { // so far the word matches + if (!*w && *p == sep) // found ! + return ind; + if (*w == *p) // still ok + w++; + else if (isupper (*p)) { + if (*w == '-' || *w == '_') + w++; + if (*p == *w || tolower (*p) == *w) + w++; + else + w = 0; + } else // not this item + w = 0; + } else { + if (*p == sep) { // start new item + ind++; + w = word; + } + } + } + return 0; +} + +// compare two names +// an uppercase letter can match the same uppercase letter, +// or an optional hyphen or underscore followed by the lowercase letter. +// if the name starts with an uppercase, it matches the lowercase +// FooBar == foo-bar, foo_bar, foobar +// returns like strcmp +int +cmpname (register const char* s1, register const char* s2) +{ + for (; *s1 && *s2; s1++, s2++) { + if (*s1 == *s2) + continue; + if (isupper (*s1)) { + if (*s2 == '-' || *s2 == '_') + s2++; + if (*s1 == *s2 || tolower (*s1) == *s2) + continue; + else + break; + } else if (isupper (*s2)) { + if (*s1 == '-' || *s1 == '_') + s1++; + if (*s2 == *s1 || tolower (*s2) == *s1) + continue; + else + break; + } else + break; + } + + if (*s1 == '\0' && *s2 == '\0') + return 0; + if (*s1 == '\0') + return -1; + if (*s2 == '\0') + return 1; + if (tolower (*s1) < tolower (*s2)) + return -1; + return 1; +} + +// sprintf a message (up to 5 args) and return the formatted string +// the address of a static string is returned +const char* +stringf (const char* fmt, const void* a1, const void* a2, const void* a3, const void* a4, const void* a5) +{ + static char msg [1024]; + sprintf (msg, fmt, a1, a2, a3, a4, a5); + return msg; +} + + +void +UchTextWord :: SetVal (const char* val) +{ + // if Sval is null, then it's an integer, whose value is in Ival + // if Sval is non null, then it's a string, whose value is in Sval + Ival = atoi (val); + if (Ival == 0 && strcmp (val, "0") != 0) + Sval = val; // word is a string: atoi returned 0 and val is not "0" + else + Sval = 0; // word is an integer +} + +const char* +UchTextWord :: GetQuotes () const +{ + if (! Sval) + return ""; /* no quotes */ + if (! *Sval) + return "\'\'"; /* empty string */ + if (index (Sval, ' ') == 0) + return ""; /* no quotes */ + if (index (Sval, '\'') == 0) + return "\'\'"; /* 'hello' */ + if (index (Sval, '\"') == 0) + return "\"\""; /* "hello" */ + return 0; /* cannot quote */ +} + + +void +UchTextLine :: NewWord (const char* s, int i) +{ + if (Num >= Max) { + if (Max == 0) { + Max = 16; + Words = new UchTextWord [Max]; + } else { + UchTextWord* old = Words; + Words = new UchTextWord [Max *= 2]; + UchTextWord* po = old; + UchTextWord* pn = Words; + for (int i = 0; i < Num; i++) + *pn++ = *po++; + delete [] old; + } + } + if (s) + Words [Num++].SetVal (s); + else + Words [Num++].SetVal (i); +} + +UchTextLine :: UchTextLine () +: Num (0), + Max (0), + Words (0) +{ +} + +UchTextLine :: ~UchTextLine () +{ + if (Words) + delete [] Words; +} + +void +UchTextLine :: AddTrailer (const UchTextLine& line, int first) +{ + for (; first < line.Num; first++) { + UchTextWord& word = line [first]; + NewWord (word, word); + } +} + +bool +UchTextLine :: Parse (char* line) +{ + enum WHERE { IN_SPACE, IN_WORD, IN_QUOTE }; + char quote = '\0'; + register char* s = line; + register char* word = line; + register WHERE where = IN_SPACE; + + // split a line into words + // a word is a sequence of non blank characters, + // or a quoted string (' or "). + // '(', ')' and ',' are also ignored to allow for function like notation + for (; *s; s++) { + switch (where) { + case IN_WORD: + if (index (" \t(),", *s)) { + *s = '\0'; + where = IN_SPACE; + AddWord (word); + } + break; + case IN_QUOTE: + if (*s == quote) { + *s = '\0'; + where = IN_SPACE; + AddWord (word); + } + break; + case IN_SPACE: + if (*s == '\'' || *s == '\"') { + quote = *s; + *s = '\0'; + where = IN_QUOTE; + word = s+1; + } else + if (index (" \t(),", *s)) { + *s = '\0'; + } else { + where = IN_WORD; + word = s; + } + break; + } + } + if (where != IN_SPACE) + AddWord (word); + if (where == IN_QUOTE) + return FALSE; + return TRUE; +} + +// decompile line into buffer +// if the line does not fit in the buffer, it ends with '...' +// the return value is dest, or a pointer to a static string "..." +// if dest or len is 0. +char* +UchTextLine :: Unparse (char* dest, int len) const +{ + UchTextWord* pa = Words; + char* p = dest; + char* lim = dest + len -1; + + if (! dest || ! len) + return "..."; + + for (int i = 0; i < Num; i++, pa++) { + // convert arg to string + const char* sa; + const char* quotes = ""; + if (pa->IsInt ()) { + char num [32]; + sprintf (num, "%d", int (*pa)); + sa = num; + } else { + sa = *pa; + quotes = pa->GetQuotes (); + if (! quotes) { + fprintf (stderr, "UchTextLine::Unparse, cannot quote word\n"); + quotes = ""; + sa = "(unquotable)"; + } + } + // try to append + if (p + strlen (sa) + strlen (quotes) < lim) { + if (*quotes) + *p++ = quotes [0]; + strcpy (p, sa); + p += strlen (p); + if (*quotes) + *p++ = quotes [1]; + } else { + if (*quotes) + *p++ = quotes [0]; + strncpy (p, sa, (lim - p) - 3); + strcpy (lim -3, "..."); + p = lim; + break; + } + // separator with next one + if (i < Num -1 && p < lim) + *p++ = ' '; + } + *p = '\0'; + return dest; +} + +// unparse the line in a buffer. +// the unparsed string is _not_ terminated by a null +// the return value is a pointer to the beginning of the string in the buffer +char* +UchTextLine :: Unparse (UchMsgBuffer* buf) const +{ + UchTextWord* pa = Words; + char* start = (char*) buf->Buffer (); + + for (int i = 0; i < Num; i++, pa++) { + // convert arg to string and append it + const char* sa; + const char* quotes = ""; + if (pa->IsInt ()) { + char num [32]; + sprintf (num, "%d", int (*pa)); + sa = num; + } else { + sa = *pa; + quotes = pa->GetQuotes (); + if (! quotes) { + fprintf (stderr, "UchTextLine::Unparse, cannot quote word <%s>\n", sa); + quotes = ""; + sa = "(unquotable)"; + } + } + if (*quotes) + buf->Append (quotes [0]); + buf->Append (sa, FALSE /*no newline*/); + if (*quotes) + buf->Append (quotes [1]); + // separator with next one + if (i < Num -1) + buf->Append (' '); + } + return start; +} + +bool +UchTextLine :: Match (const char* cmdname, const char* profile) const +{ + if (Num <= 0) + return FALSE; // no word on line + const char* myname = Words [0]; + if (! myname || cmpname (myname, cmdname) != 0) + return FALSE; // wrong command name + + // check the profile, if it is non null. + // each letter in the profile corresponds to one argument: + // 'i' for integer, 's' for string, 'b' for bool. + // the value for a boolean can be an integer (0=false, otherwise true) + // or a string: yes, y, true, t, on, no, n, false, f, off + // if the last character of the profile is '+', then the extra arguments + // are not checked. + // a null profile is identical to the profile "+". + // an empty profile ("") describes a command with no argument. + // *** we'll probably add features for repeated arguments, + // *** enums (mapping strings to ints), etc. + if (! profile) + return TRUE; + const char* p = profile; + for (int i = 1; i < Num; i++) { + switch (*p) { + case 'i': + if (! Words [i].IsInt ()) + return FALSE; + break; + case 's': + if (! Words [i].IsString ()) + return FALSE; + break; + case 'b': + { + if (Words [i].IsInt ()) + break; + const char* sval = Words [i]; + if (windex (":Yes:Y:True:T:On:", sval)) { + Words[i].SetVal (1); + break; + } else if (windex (":No:N:False:F:Off:", sval)) { + Words[i].SetVal (0); + break; + } + return FALSE; + } + case '+': + if (*(p+1)) // not at end of profile + fprintf (stderr, "UchTextLine::Match, invalid command profile: <%s>\n", profile); + return TRUE; + case '\0': + // profile shorter than arg list + return FALSE; + default: + fprintf (stderr, "UchTextLine::Match, invalid command profile: <%s>\n", profile); + + return FALSE; + } + p++; + } + if (*p) + return FALSE; // arg list shorter than profile + return TRUE; +} + +// return -1 if word is not in the line, otherwise return its index +int +UchTextLine :: Index (const char* word) const +{ + UchTextWord* w = Words; + int i = 0; + + for (; i < Num; i++, w++) + if (w->IsString () && strcmp (*w, word) == 0) + return i; + return -1; +} + +// return -1 if value is not in the line, otherwise return its index +int +UchTextLine :: Index (int value) const +{ + UchTextWord* w = Words; + int i = 0; + + for (; i < Num; i++, w++) + if (w->IsInt () && *w == value) + return i; + return -1; +} + +//---------------- constants + +// we start with a few quick retries +// AbandonRetries doubles the time and decrement the retries until 1 +// 2 secs * 5 = 10 secs +// 4 secs * 4 = 16 secs +// 8 secs * 3 = 24 secs +// 16 secs * 2 = 32 secs +// 32 secs * 1 = 32 secs +// total = 15 retries in 114 secs ~ 2 minutes +// we want this to be significantly longer than the REG_TIME of the port server + +#define RETRY_TIME 2 +#define MAX_RETRIES 5 + +/*? +Construct a \typ{UchTextStream}. +?*/ +UchTextStream :: UchTextStream () +: UchStream (), + InBuffer (256), + OutBuffer (256), + MyMpx (0) +{ +} + +/*? +Protected destructor. +\fun{Close} must be used instead to properly close down a \typ{UchTextStream} +?*/ +UchTextStream :: ~UchTextStream () +{ + // nothing +} + +/*? +This virtual function is called by \fun{HandleRead} when an end of file +is read (the argument is then \var{TRUE}), or when an error occured while +reading (the argument is then \var{FALSE}). +The default action is to call \fun{Close}. +?*/ +void +UchTextStream :: Closing (bool) +{ + Close (); +} + +void +UchTextStream :: AddNotify (UchMultiplexer& m) +{ + MyMpx = &m; +} + +void +UchTextStream :: RemoveNotify (UchMultiplexer& m) +{ + if (MyMpx == &m) + MyMpx = 0; +} + +/*? +Read and decode input. When a complete line (terminated either by +a newline or cr-newline) is in the input buffer, it is parsed. +If parsing is successful, \fun{Execute} is called with the line as argument. +If \fun{Execute} returns \var{isCmdUnknown}, \fun{TryPredefined} is called +to decode the predefined commands. +Finally, \fun{ProcessCmdResult} is called and the line is flushed from +the input buffer. +Note that since the parsed line passed to \fun{Execute} contains pointers +to the input buffer, \fun{Execute} {\em must not} keep copies of these +pointers, but copy the contents instead. +?*/ +void +UchTextStream :: HandleRead () +{ + InBuffer.NeedSize (128); + int n = Read (InBuffer); + if (n <= 0) { + Closing (n < 0 ? FALSE : TRUE); + return; + } + + // scan buffer for complete commands + char* start = (char*) InBuffer.Buffer (); + char* stop = start + InBuffer.BufLength (); + for (char* p = start; p < stop; p++) { + if (*p == '\n') { + *p = '\0'; + // check cr-nl sequence + if (p > start && *(p-1) == '\r') + *(p-1) = '\0'; + // parse line, lookup command + UchTextLine line; + cmd_res res = isCmdUnknown; + if (line.Parse (start)) { + if (line.NumWords () == 0) + res = isCmdOk; + else + res = Execute (line); + if (res == isCmdUnknown) + res = TryPredefined (line); + } else + res = isCmdSyntax; + ProcessCmdResult (res, line); + // reset for scanning rest of buffer + start = p+1; + InBuffer.Flush ((byte*) start); + } + } +} + +/*? +Process the result \var{res} of the execution of the line \var{line}. +If \var{res} is one of \var{isCmdSyntax}, \var{isCmdUnknown}, \var{isCmdError}, +a warning message is issued. +If \var{res} is \var{isCmdClose} (resp. \var{isCmdQuit}), the virtual function +\fun{Close} (resp. \fun{Quit}) is called. +If \var{res} is \var{isCmdTerminate} (resp. \var{isCmdAbort}), +the global function \fun{MpxTerminate} (resp. \fun{MpxAbort}) is called. +If \var{res} is \var{isCmdExit}, \fun{exit(1)} is called. +Finally, if \var{res} is \var{isCmdOk}, nothing happens. +?*/ +void +UchTextStream :: ProcessCmdResult (cmd_res res, const UchTextLine& line) +{ + const char* msg = 0; + switch (res) { + case isCmdSyntax: + msg = "syntax error in <%s>"; + break; + case isCmdUnknown: + msg = "unknown command: <%s>"; + break; + case isCmdOk: + return; + case isCmdError: + msg = "error while executing command <%s>"; + break; + case isCmdClose: + // like eof (but smarter for the session) + Close (); + return; + case isCmdQuit: + //quit application + Quit (); + return; + case isCmdTerminate: + // terminate the multiplexer + if (MyMpx) + MyMpx->LoopEnd (); + return; + case isCmdAbort: + // abort the multiplexer +// MpxAbort (); + return; + case isCmdExit: + exit (1); + } + if (msg) { + char buf [64]; + line.Unparse (buf, sizeof (buf)); + fprintf (stderr, "UchTextStream::ProcessCmdResult\n", msg, buf); + } +} + +/*? +If \var{line} is one of the words +\com{Close}, \com{Quit}, \com{Terminate}, \com{Abort}, \com{Exit}, +the corresponding code is returned (\var{isCmdClose}, etc.), +otherwise, \var{isCmdUnknown} is returned. +?*/ +UchTextStream::cmd_res +UchTextStream :: TryPredefined (const UchTextLine& line) +{ + const char* cname = line [0]; + + if (line.NumWords () != 1) + return isCmdUnknown; + switch (windex (":Close:Quit:Terminate:Abort:Exit:", cname)) { + case 1: return isCmdClose; + case 2: return isCmdQuit; + case 3: return isCmdTerminate; + case 4: return isCmdAbort; + case 5: return isCmdExit; + } + return isCmdUnknown; +} + +/*? +This virtual function is called by the members that send commands on the stream. +By defaults, it writes the output buffer on the stream. +Derived classes can redefine this function. +?*/ +void +UchTextStream :: DoSend () +{ + Write (OutBuffer); +} + +/*? +This virtual function is called by \fun{ProcessCmdResult} when a \com{close} +command has been received. +It can be called directly by the application to close down the connection +(remember that the destructor is private and cannot be called directly). +By default it calls \fun{MpxRemoveChannel} to unregister this stream from the +multiplexer. +This will trigger deletion of the stream is the multiplexer holds the last +smart pointer to it. +Derived classes can redefine this function, but the redefinition +should call the default implementation. +?*/ +void +UchTextStream :: Close () +{ + MyMpx->Remove (*this); +} + +/*? +This virtual function is called by \fun{ProcessCmdResult} when a \com{quit} +command has been received. +By default is calls \fun{MpxTerminate}. +Derived classes can redefine this function if they want to terminate more +gracefully. +?*/ +void +UchTextStream :: Quit () +{ + MyMpx->LoopEnd (); +} + +//---------------- ServiceStarter + +class UchServiceStarter : public UchBaseTimeOut { +friend class UchTextService; + +protected: + UchTextService* Service; + int Retries; + int Max; + +public: + UchServiceStarter (UchTextService*, int, int); + void Handle (Millisecond); +}; + +UchServiceStarter :: UchServiceStarter (UchTextService* s, int retry, int maxr) +: UchBaseTimeOut (*s->GetMultiplexer (), retry * 1000), + Service (s), + Retries (0), + Max (maxr) +{ +} + +void +UchServiceStarter :: Handle (Millisecond) +{ + /* UchServiceStarters are not timers triggered on signals. + For that reason, Handle is allowed to be complex. */ + + if (! Service) + return; + + fprintf (stderr, "ServiceStarter::TimeOut, attempting restart\n"); + if (Service->GetStatus () == UchTextService::isRunning + || Service->Restart () == UchTextService::isRunning) { + Stop (); + Service->Starter = 0; + delete this; + return; + } + + if (++Retries >= Max) { + Stop (); + if (Max <= 1) { + fprintf (stderr, "ServiceStarter::TimeOut, abandoning restart\n"); + Service->Starter = 0; + Service->AbandonRestart (); + delete this; + } else { + Max--; + Retries = 0; + ChangePeriod (GetPeriod () * 2); + Restart (); + } + } +} + + +/*? +Construct an empty \var{UchTextService}. +When using this constructor, the application must call \fun{Init} at a later +time in order to define the server to connect to. +The initial state of the service is \var{isLost}, since the actual +connection to the server is not established yet. +?*/ +UchTextService :: UchTextService () +: UchTextStream (), + StatusFlag (isLost), + Starter (0), + Closed (FALSE), + User (), + Service (), + Host () +{ +} + +/*? +Construct a \var{UchTextService} connected to server \var{s} on host \var{h}. +This is equivalent to the empty constructor followed by a call to \fun{Init}. +?*/ +UchTextService :: UchTextService (const char* s, const char* h) +: UchTextStream (), + StatusFlag (isLost), + Starter (0), + Closed (FALSE), + User (), + Service (), + Host () +{ + Init (s, h); +} + +/*? +The (protected) destructor. +Call \fun{Close} or \fun{CloseNow} to close down the connection and +delete the service. +?*/ +UchTextService :: ~UchTextService () +{ + if (Starter) + delete Starter; +} + +/*? +This protected virtual function is called by \fun{UchTextStream::HandleRead} +when an end-of-file is read or when a read failed. +It calls \var{LostServer} so that an attempt to reconnect to the server +can be made. +?*/ +void +UchTextService :: Closing (bool) +{ + fprintf (stderr, "UchTextService::Closing, lost server\n"); + StatusFlag = isLost; + LostServer (); +} + +/*? +Attempt to connect to the server. +If the address of the server cannot be retrieved from the port server, +this function sets the state of the service to \var{isUnavailable} +and returns. +If an address is obtained from the port server, this function tries to connect +to that address. +If this fails, it sets the state to \var{isError} and returns. +Otherwise, it sets the state to \var{isRunning} and calls the virtual +function \fun{GotServer}. +The service is automatically registered with the multiplexer +(by calling \fun{MpxAddChannel} with mode \var{IONone}) +the first time the connection is established. +?*/ +UchTextService::status +UchTextService :: Restart () +{ + UchInetAddress* addr = PortServerInquire ("portserv", Service, Host); + if (! addr) { + fprintf (stderr, "UchTextService::Restart, no address in port server\n"); + return StatusFlag = isUnavailable; + } + ConnectTo (addr); + + bool wasopened = Fd.Opened (); + bool setup = TRUE; + if (wasopened) { + // we are in trouble because a stream socket can be connected + // only once and a channel is immutable... + // so we have to fake a reopen + int ofd = FilDes (); + if (ofd >= 0) { + close (ofd); + int nfd = socket (ConnectedTo () ->Family (), SockType (), 0); + fprintf (stderr, "UchTextService::Restart, reopening socket: %d -> %d\n", (void*) ofd, (void*) nfd); + if (nfd < 0) { + setup = FALSE; + } else if (nfd != ofd) { + dup2 (nfd, ofd); + close (nfd); + } + } + } + if (setup) + setup = Setup (); + if (! wasopened) { + SetMode (IONone); + MyMpx->Add (this); + } + if (! setup) { + fprintf (stderr, "UchTextService::Restart, could not setup connection\n"); + return StatusFlag = isError; + } + + StatusFlag = isRunning; + if (Starter) // do not log if got server at first attempt + fprintf (stderr, "UchTextService::Restart, got server\n"); + GotServer (); + return StatusFlag; +} + +/*? +This protected function arranges for a connection to be attempted in +\var{retry_time} seconds. +If the connection cannot be established after \var{maxr} retries, +\var{retry_time} is doubled and \var{maxr} is decremented and new attempts +are made with these new values. +When the number of retries is null, then the virtual function +\fun{AbandonRestart} is called and no further attempts are made. +If \var{retry_time} is 0, any pending attempts are cancelled. +If \var{retry_time} or var{maxr} is negative, suitable default values are used. +?*/ +void +UchTextService :: AutoStart (int retry_time, int maxr) +{ + if (retry_time < 0) + retry_time = RETRY_TIME; + if (maxr < 0) + maxr = MAX_RETRIES; + + if (retry_time == 0) { // cancel auto-start + if (Starter) { + delete Starter; + Starter = 0; + } + return; + } + if (Starter) { + Starter->ChangePeriod (retry_time); + Starter->Retries = 0; + Starter->Max = maxr; + } else if (MyMpx) { + Starter = new UchServiceStarter (this, retry_time, maxr); + } else + fprintf (stderr, "UchTextService::AutoStart, no multiplexer for time-out\n"); +} + +/*? +This protected virtual function is called when the connection to the server +has been broken. +By default, it resets the mode of the channel to \var{IONone} so that the +multiplexer does not attempt to read from the server. +Derived classes can redefine this function, but the redefinition should +call the default implementation. +?*/ +void +UchTextService :: LostServer () +{ + MyMpx->SetMode (*this, IONone); + AutoStart (); +} + +/*? +This protected virtual function is called when the connection to the server +has been established. +By default, it sets the mode of the channel to \var{IORead} so that the +multiplexer can monitor the channel. +If the output buffer is not empty, it sends it to the server by calling +\fun{DoSend}. +Derived classes can redefine this function, but the redefinition should +call the default implementation. +?*/ +void +UchTextService :: GotServer () +{ + MyMpx->SetMode (*this, IORead); + if (OutBuffer.BufLength ()) + DoSend (); +} + +/*? +This protected virtual function is called when the connection cannot +be restarted (see \fun{AutoStart}). +By default it calls \fun{CloseNow} which should ultimately destroy the object. +A derived class can redefine this function to take whatever clean-up action +is required. +?*/ +void +UchTextService :: AbandonRestart () +{ + CloseNow (); +} + +/*? +This implementation of the protected virtual function \fun{UchTextStream::Execute} +always return \var{isCmdOk}. +This is suitable for the global function \var{TellServer}. +Derived classes generally do redefine this function. +?*/ +UchTextStream::cmd_res +UchTextService :: Execute (const UchTextLine&) +{ + // just discard received request by default + // (suitable for TellServer below) + return isCmdOk; +} + +/*? +This implementation of the protected virtual function \fun{UchTextStream::DoSend} +writes the output buffer only if the state of the connection is +\var{isRunning}. +Otherwise, the outgoing requests accumulate in the ouput buffer until the +connection is established. +This function also closes down the connection by calling \var{MpxRemoveChannel} +if \fun{Close} has been called before the connection ws actually established. +?*/ +void +UchTextService :: DoSend () +{ + if (StatusFlag == isRunning) { + Write (OutBuffer); + if (Closed) + MyMpx->Remove (*this); + } +} + +/*? +Define the server and host to connect to. This function must be used +if the object was constructed with the default constructor. +If the connection to the server cannot be established, \fun{Init} calls +\var{AutoStart} to attempt the connection later. +?*/ +void +UchTextService :: Init (const char* s, const char* h) +{ + if (StatusFlag == isRunning) + return; + + Service = s; + Host = h; + + StatusFlag = Restart (); + if (StatusFlag != isRunning) + AutoStart (); +} + +/*? +Return the current retry time for the auto-restart of the connection. +If the connection is not in auto-restart mode, return -1; +?*/ +int +UchTextService :: GetRetryTime () +{ + return Starter ? Starter->GetPeriod () / 1000 : -1; +} + +/*? +Return the current maximum number of retries for the auto-restart of the connection. +If the connection is not in auto-restart mode, return -1; +?*/ +int +UchTextService :: GetMaxRetries () +{ + return Starter ? Starter->Max : -1; +} + +/*? +Remove this service from the multiplexer if the connection is established +or if the output buffer is empty. +Otherwise, mark the connection as being closed; +in this case, the output buffer will be sent when the connection is etablished, +and then the connection will be closed. +?*/ +void +UchTextService :: Close () +{ + Closed = TRUE; + if (StatusFlag == isRunning || OutBuffer.BufLength () == 0) + MyMpx->Remove (*this); +} + +/*? +Remove this service from the multiplexer, whatever +the state of the connection is. +?*/ +void +UchTextService :: CloseNow () +{ + MyMpx->Remove (*this); +} + +/*? +This global function provides the simplest interface to send a request \var{req} +to a server \var{sname} on host \var{host}. +?*/ +bool +TellServer (const char* sname, const char* host, const char* req) +{ + UchTextService* serv = new UchTextService; + serv->Init (sname, host); + serv->Send (req); + serv->Close (); + return TRUE; +} diff --git a/comm/OLD/TextStream.h b/comm/OLD/TextStream.h new file mode 100644 index 0000000..cb43fe3 --- /dev/null +++ b/comm/OLD/TextStream.h @@ -0,0 +1,157 @@ +/* + * The Unix Channel + * + * by Michel Beaudouin-Lafon + * + * Copyright 1990-1993 + * Laboratoire de Recherche en Informatique (LRI) + * + * Text streams + * + * $Id$ + * $CurLog$ + */ + +#ifndef TextStream_H_ +#define TextStream_H_ + +#include "cplus_bugs.h" +#include "Stream.h" +#include "MsgBuffer.h" +#include "ccu/String.h" + +class UchTextWord { +protected: + const char* Sval; + int Ival; +public: +inline UchTextWord () : Sval (0), Ival (0) {} +inline UchTextWord (const char* s) { SetVal (s); } +inline UchTextWord (int i) { SetVal (i); } + void SetVal (const char*); +inline void SetVal (int i) { Sval = 0; Ival = i; } +inline bool IsInt () const { return (Sval == 0) ? TRUE : FALSE; } +inline bool IsString () const { return (Sval != 0) ? TRUE : FALSE; } + const char* GetQuotes () const; +inline operator int () const { return Ival; } +inline operator const char* () const { return Sval; } +}; + + +class UchTextLine { +protected: + int Num; + int Max; + UchTextWord* Words; +void NewWord (const char* s, int i); + +public: + UchTextLine (); + ~UchTextLine (); +inline UchTextWord& operator [] (int i) const { return Words [i]; } +inline int NumWords () const { return Num; } +inline void AddWord (const char* s) { NewWord (s, 0); } +inline void AddWord (int i) { NewWord (0, i); } + void AddTrailer (const UchTextLine&, int); +inline UchTextLine& operator << (int i) { AddWord (i); return *this; } +inline UchTextLine& operator << (const char* s) { AddWord (s); return *this; } +inline UchTextLine& operator << (const UchTextLine& l) { AddTrailer (l, 0); return *this; } + bool Parse (char*); + char* Unparse (char* dest, int len) const; + char* Unparse (UchMsgBuffer*) const; + bool Match (const char*, const char* = 0) const; + int Index (const char*) const; + int Index (int) const; +inline bool Contains (const char* w) const { return (Index (w) == -1) ? FALSE : TRUE; } +inline bool Contains (int i) const { return (Index (i) == -1) ? FALSE : TRUE; } +inline void Reset () { Num = 0; } +}; + +class UchTextStream : public UchStream { +public: +enum cmd_res { + isCmdSyntax, // syntax error when parsing command + isCmdUnknown, // unknown command + isCmdOk, // request executed + isCmdError, // problem while executing request + isCmdClose, // close connection + isCmdQuit, // quit server + isCmdTerminate, // terminate multiplexer + isCmdAbort, // abort multiplexer + isCmdExit // call exit + }; +protected: + UchMsgBuffer InBuffer; + UchMsgBuffer OutBuffer; + UchMultiplexer* MyMpx; + +virtual void Closing (bool); + void AddNotify (UchMultiplexer&); + void RemoveNotify (UchMultiplexer&); + void HandleRead (); + void ProcessCmdResult (cmd_res, const UchTextLine&); + cmd_res TryPredefined (const UchTextLine&); +virtual cmd_res Execute (const UchTextLine&) = 0; +virtual void DoSend (); + +public: + UchTextStream (); + ~UchTextStream (); + UchTextStream (const UchTextStream&); + UchChannel* Copy () const; +inline UchMultiplexer* GetMultiplexer () const { return MyMpx; } +virtual void Close (); // 'close' request +virtual void Quit (); // 'quit' request +inline void Append (const char* l) { OutBuffer.Append (l, FALSE); } +inline void Append (const UchTextLine& l) { l.Unparse (&OutBuffer); OutBuffer.Append ('\n');} +inline UchTextStream& operator << (const char* l) { Append (l); return *this; } +inline UchTextStream& operator << (const UchTextLine& l) { Append (l); return *this; } +inline void Send () { DoSend (); } +inline void Send (const char* l) { Append (l); DoSend (); } +inline void Send (const UchTextLine& l) { Append (l); DoSend (); } +}; + +class UchTextService : public UchTextStream { +friend class UchServiceStarter; +public: +enum status { + isUnavailable, // address not found in port server + isError, // could not init connection + isRunning, // connection established + isLost, // no connection (auto-starting) + }; + +protected: + status StatusFlag; + UchServiceStarter* Starter; + bool Closed; + CcuString User; + CcuString Service; + CcuString Host; + + void Closing (bool); + status Restart (); + void AutoStart (int = -1, int = -1); +virtual void LostServer (); +virtual void GotServer (); +virtual void AbandonRestart (); + cmd_res Execute (const UchTextLine&); + void DoSend (); + +public: + UchTextService (const char*, const char* = 0); + UchTextService (); + ~UchTextService (); + void Init (const char*, const char* = 0); + status GetStatus () { return StatusFlag; } + int GetRetryTime (); + int GetMaxRetries (); + void Close (); // close after output buffer emptied + void CloseNow (); // close now + +}; + +// the simplest interface to a server. +extern bool TellServer (const char*, const char*, const char*); + +#endif /* TextStream_H_ */ diff --git a/comm/OLD/dgram.cc b/comm/OLD/dgram.cc new file mode 100644 index 0000000..d6e5c50 --- /dev/null +++ b/comm/OLD/dgram.cc @@ -0,0 +1,717 @@ +/* + * The Unix Channel + * + * by Michel Beaudouin-Lafon + * + * Copyright 1990-1993 + * Laboratoire de Recherche en Informatique (LRI) + * + * Reliable datagrams - to be updated + * + * $Id$ + * $CurLog$ + */ + +#include "MsgBuffer.h" +#include "dgram.h" +#include "error.h" + +#include +#include +#include +#include + +// needed only if DEBUG is active +#include +#define DEBUG //**// +bool DGTrace = FALSE; + +/*?class UchDGRAM +The class \typ{UchDGRAM} is derived from \typ{UchDatagram}. +It adds to a datagram a simple protocol to ensure that all messages sent are received. +It does not prevent message duplication, neither does he ensure that the messages are +received in the order they are sent. + +Each message is sent along with a tag number. The sender keeps each message it sends +until it receives an acknowledge for it. +When a message is received, an acknowledge is sent back. +Unacknowledged messages are resent at regular intervals. +If a message is not acknowledged after a given number of retries, it is simply discarded. +?*/ + +struct PENDING { + UchMsgBuffer* outBuf; + pUchAddress toAddr; + short retries; // for pending + lword id; // for input + + PENDING (UchMsgBuffer* b, UchAddress* a, int r = 0) { outBuf = b; toAddr = a; retries = r; id = 0;} + ~PENDING () { delete outBuf; toAddr = 0; } +}; + +static void +DelPending (void* p) +{ + delete ((PENDING*) p); +} + +/*?hidden?*/ +void +UchDGRAM_TIMER :: Handle (Millisecond) +{ +if (DGTrace) printf ("*** TimeOut ***\n"); + dgram.Expired (); +} + +/*?nodoc?*/ +UchDGRAM_TIMER :: UchDGRAM_TIMER (UchDGRAM& dg) +: CcuBaseTimer (0), + dgram (dg) +{ +} + +UchDGRAM_TIMER :: ~UchDGRAM_TIMER () +{ +} + +/*?hidden?*/ +void +UchDGRAM :: Init () +{ + npending = 0; + ninput = 0; + fromAddr = 0; + retry = 5; + resend = FALSE; + locked = 0; + timeout = 1000; + sync = FALSE; +} + +/*?nextdoc?*/ +UchDGRAM :: UchDGRAM () +: pending (2), input (), timer (*this) +{ + Init (); +} + +/*? +Same constructors as for the class \typ{UchDatagram}; +?*/ +UchDGRAM :: UchDGRAM (UchAddress* b, UchAddress* c) +: UchDatagram (b, c), pending (2), input (), timer (*this) +{ + Init (); +} + +/*? +The destructor of \typ{UchDGRAM} discards all pending and input messages. +A derived class could call \fun{Drain} in its destructor to send all pending messages, +and it could call \fun{Receive} while \fun{NumInput} is non null. +?*/ +UchDGRAM :: ~UchDGRAM () +{ + timer.Stop (); + if (! npending) + return; + for (CcuIdIter iter (pending); iter (); ++iter) { + RemovePending (iter.CurId ()); + if (! npending) + break; + } + if (ninput) { + CcuListIter li (input); + while (++li) + DelPending (*li); + input.Clear (); + } +} + +static const byte DGRAM_ERR = 0; +static const byte DGRAM_SEND = 1; +static const byte DGRAM_ACK = 2; + +// GetInput reads an incoming message +// if its an ack, it is processed +// if it is a normal message, it is stored in the input list +// CheckInput calls GetInput wile there is something to read +// WaitInput returns when a message is in the input list +// Wait waits for the acknowledge of a particular id + +/*?hidden?*/ +int +UchDGRAM :: GetInput (int len) +{ + UchMsgBuffer* buf = new UchMsgBuffer (len); + int n; + while ((n = UchDatagram :: Receive (*buf)) == -1 && errno == EINTR) +DEBUG SysError (ErrWarn, "UchDGRAM::GetInput") + ; + if (n < 0) { + SysError (ErrWarn, "UchDGRAM::GetInput"); + delete buf; + return DGRAM_ERR; + } +DEBUG if (DGTrace) printf ("UchDGRAM :: GetInput : received %d bytes\n", buf->BufLength ()); + + lword id; + buf->Get (&id); + byte s; + buf->Get (&s); +DEBUG if (DGTrace) printf ("UchDGRAM :: GetInput : id %x byte %d\n", id, s); + + if (s == DGRAM_ACK) { +DEBUG if (DGTrace) printf ("UchDGRAM :: GetInput : acknowledged %x\n", id); + RemovePending (id); + delete buf; + return DGRAM_ACK; + } + + PENDING* p = new PENDING (buf, FAddr); + p->id = id; + input.Append (p); + ninput++; + return DGRAM_SEND; +} + +/*?hidden?*/ +bool +UchDGRAM :: CheckInput () +{ + // get any pending messages + for (;;) { + lword np; + if (ioctl (FilDes (), FIONREAD, (char*) &np) < 0) + return FALSE; +DEBUG if (DGTrace) if (np) printf ("UchDGRAM :: CheckInput : FIONREAD says %d\n", np); + if (np == 0) + break; + if (GetInput (int (np)) == DGRAM_ERR) + return FALSE; + } + return TRUE; +} + +/*?hidden?*/ +bool +UchDGRAM :: WaitInput () +{ + // check the input queue + if (ninput) + return TRUE; + + // check pending input + if (! CheckInput ()) + return FALSE; + + // block until something + while (! ninput) + if (GetInput (2048) == DGRAM_ERR) // *** arbitrary max size + return FALSE; + return TRUE; +} + +/*?hidden?*/ +bool +UchDGRAM :: Wait (lword id) +{ +DEBUG if (DGTrace) printf ("UchDGRAM::Wait %x\n", id); + bool ret = FALSE; + bool loop = TRUE; + Lock (); + + PENDING* pend; + while (loop) { + if (! CheckInput ()) + break; + + // did we receive the ack ? + pend = (PENDING*) pending.Get (id); + if (! pend) { + ret = TRUE; + break; + } + + if (resend) { +DEBUG if (DGTrace) printf ("UchDGRAM::Wait : resending\n"); + if (pend->retries <= 0) { + RemovePending (id); + break; + } + + int n = SendBuffer (* pend->outBuf, *pend->toAddr); + if (n < 0) + break; + --pend->retries; + resend = FALSE; + } else +DEBUG if (DGTrace) printf ("UchDGRAM::Wait : waiting\n"), + timer.Wait (); // sets resend to TRUE + } + Unlock (); +DEBUG if (DGTrace) printf ("UchDGRAM::Wait : %s\n", ret ? "done" : "failed"); + return ret; +} + +// SendAck sends an acknowledge +// Expired is called when the resend timer has expired +// PrepareToSend returns a buffer with the header set, +// and adds it to the pending set +// SendBuffer sends a buffer, handling interrupted sys calls +// RemovePending removes a pending message from the table +/*?hidden?*/ +void +UchDGRAM :: SendAck (lword id, UchAddress& addr) +{ + UchMsgBuffer buf; + buf.Append (id); + buf.Append (DGRAM_ACK); + SendBuffer (buf, addr); + fromAddr = & addr; +DEBUG if (DGTrace) printf ("UchDGRAM :: SendAck : acknowledging %x\n", id); +} + +/*?hidden?*/ +void +UchDGRAM :: Expired () +{ + if (locked) + resend = TRUE; + else + Resend (); +} + +/*?hidden?*/ +UchMsgBuffer* +UchDGRAM :: PrepareToSend (UchAddress& to, int retries) +{ + UchMsgBuffer* obuf = new UchMsgBuffer; +/* +UchInetAddress* ia = (UchInetAddress*) &to; +UchAddress* toCopy = new UchInetAddress (ia->Host (), ia->Port ()); +*/ + if (retries == 0) + retries = retry; + PENDING* out = new PENDING (obuf, &to /*toCopy*/, retries); + outId = pending.Store (out); + +DEBUG if (DGTrace) printf ("UchDGRAM :: PrepareToSend : id %x\n", outId); + obuf->Append (outId); + obuf->Append (DGRAM_SEND); + + if (! npending) { + timer.ChangePeriod (timeout); + timer.Restart (); + } + npending++; + + return obuf; +} + +/*?hidden?*/ +int +UchDGRAM :: SendBuffer (UchMsgBuffer& buf, UchAddress& addr) +{ + int n; + while ((n = UchDatagram :: Send (buf, addr, TRUE)) == -1 && errno == EINTR) +DEBUG SysError (ErrWarn, "UchDGRAM::SendBuffer") + ; + return n; +} + +/*?hidden?*/ +void +UchDGRAM :: RemovePending (lword id) +{ + PENDING* pend = (PENDING*) pending.Get (id); + if (! pend) { + Error (ErrWarn, "Receive", "unrecognized ACK"); + return; + } + pending.Remove (id); + delete pend; + npending--; + if (npending == 0) + timer.Stop (); +} + +//---------------- the public functions + +/*?nextdoc?*/ +int +UchDGRAM :: Send (byte* buf, int len, UchAddress& to, bool ack, int retries) +{ +DEBUG if (DGTrace) printf ("UchDGRAM :: Send\n"); + Lock (); + UchMsgBuffer* obuf = PrepareToSend (to, retries); + obuf->Append (buf, len); + int n = SendBuffer (*obuf, to); + CheckInput (); + int ret = (ack || sync) ? (Wait (outId) ? n : -1) : n; + Unlock (); + return ret; +} + +/*?nextdoc?*/ +int +UchDGRAM :: Receive (byte* buf, int len) +{ +DEBUG if (DGTrace) printf ("UchDGRAM :: Receive\n"); + if (! WaitInput ()) + return -1; + PENDING* p = (PENDING*) input.RemoveFirst (); + ninput--; + int n = p->outBuf->BufLength (); + if (len < n) + n = len; + memcpy (buf, p->outBuf->Buffer (), n); + SendAck (p->id, *p->toAddr); + delete p; +DEBUG if (DGTrace) printf ("UchDGRAM :: Receive : received %d bytes\n", n); + return n; +} + +/*?nextdoc?*/ +int +UchDGRAM :: Reply (byte* buf, int len, bool ack, int retries) +{ + if (! fromAddr) + return -1; + return Send (buf, len, *fromAddr, ack, retries); +} + +/*?nextdoc?*/ +int +UchDGRAM :: Send (UchMsgBuffer& buf, UchAddress& to, bool peek, bool ack, int retries) +{ +DEBUG if (DGTrace) printf ("UchDGRAM :: Send\n"); + Lock (); + UchMsgBuffer* obuf = PrepareToSend (to, retries); + obuf->Append (buf.Buffer (), buf.BufLength ()); + if (! peek) + buf.Flush (); + int n = SendBuffer (*obuf, to); + CheckInput (); + int ret = (ack || sync) ? (Wait (outId) ? n : -1) : n; + Unlock (); + return ret; +} + +/*?nextdoc?*/ +int +UchDGRAM :: Receive (UchMsgBuffer& buf) +{ +DEBUG if (DGTrace) printf ("UchDGRAM :: Receive\n"); + if (! WaitInput ()) + return -1; + PENDING* p = (PENDING*) input.RemoveFirst (); + ninput--; + int n = p->outBuf->BufLength (); + buf.Append (p->outBuf->Buffer (), n); + SendAck (p->id, *p->toAddr); + delete p; +DEBUG if (DGTrace) printf ("UchDGRAM :: Receive : received buffer %d bytes\n", n); + return n; +} + +/*? +These functions are similar to the same functions in the class \typ{UchDatagram}, +except that they manage messages acknowledgement. +\fun{Send} saves the message in a buffer so that it can be resent if no acknowledge +is received. +\fun{Receive} acknowledges the received message. +\fun{Reply} sends the message to the sender of the last received message. +If \var{ack} is TRUE, the acknowledge of the message is waited for. +In that case, \fun{Send} returns -1 if the acknowledge is not received. +If \var{retries} is non zero, it specifies a number of retries for this message +different from the default retry number of this dgram. +?*/ +int +UchDGRAM :: Reply (UchMsgBuffer& buf, bool peek, bool ack, int retries) +{ + if (! fromAddr) + return -1; + return Send (buf, *fromAddr, peek, ack, retries); +} + +/*?nextdoc?*/ +bool +UchDGRAM :: Send (UchMessage& msg, UchAddress& to, bool ack, int retries) +{ +DEBUG if (DGTrace) printf ("UchDGRAM :: Send message\n"); + Lock (); + UchMsgBuffer* obuf = PrepareToSend (to, retries); + obuf->Append (msg); +// int l; +// printf ("outBuffer is %d bytes long\n", l = obuf->BufLength ()); +// for (int i = 0; i < l; i++) printf ("%02x ", obuf->Buffer () [i]); +// printf ("\n"); + int n = SendBuffer (*obuf, to); + CheckInput (); + bool ret; + if (ack || sync) + ret = Wait (outId) ? TRUE : FALSE; + else + ret = bool (n != obuf->BufLength ()); + Unlock (); + return ret; +} + +/*?nextdoc?*/ +bool +UchDGRAM :: Receive (UchMessage* msg) +{ +DEBUG if (DGTrace) printf ("UchDGRAM :: Receive message\n"); + if (! WaitInput ()) + return FALSE; + PENDING* p = (PENDING*) input.RemoveFirst (); + ninput--; +// int n; +// printf ("inBuffer is %d bytes long\n", n = p->outBuf->BufLength ()); +// for (int i = 0; i < n; i++) printf ("%02x ", p->outBuf->Buffer () [i]); +// printf ("\n"); + if (! p->outBuf->Get (msg)) + return FALSE; + SendAck (p->id, *p->toAddr); + delete p; +DEBUG if (DGTrace) printf ("UchDGRAM :: Receive : received message\n"); + return TRUE; +} + +/*? +These functions are similar to the previous functions except that they take +a \typ{UchMessage} as argument. +\fun{Send} converts the message in the output buffer. +If \var{ack} is TRUE, the acknowledge of the message is waited for. +In that case, \fun{Send} returns FALSE if the acknowledge is not received. +If \var{retries} is non zero, it specifies a number of retries for this message +different from the default retry number of this dgram. +\fun{Receive} converts the incoming data into the message passed as argument. +\fun{Reply} sends the message to the sender of the last received message. +See also the function \fun{NewMessage} to handle incoming messages. +?*/ +bool +UchDGRAM :: Reply (UchMessage& msg, bool ack, int retries) +{ + if (! fromAddr) + return FALSE; + return Send (msg, *fromAddr, ack, retries); +} + +//----------------------- +#if 0 +bool +UchDGRAM :: Ask (UchMessage& msg, UchAddress& to) +{ +DEBUG if (DGTrace) printf ("UchDGRAM :: Ask\n"); + Lock (); + UchMsgBuffer* obuf = PrepareToSend (to, DGRAM_ASK *******); + obuf->Append (msg); + int n = SendBuffer (*obuf, to); + CheckInput (); + bool ret = Wait (outId) ? TRUE : FALSE; + if (ret) { + // wait answer + for (;;) { + if (! WaitInput ()) + continue; + PENDING* pend = (PENDING*) input.Last (); + if (pend->toAddr == to............. + } + } + Unlock (); + return ret; +} +#endif +//----------------------- + +/*? +Resend all messages for which no acknowledge has been received. +If a UchMessage.has been resent more than the retry number, it is discarded. +When successive messages are to be sent to the same address, only the first +is resent, to avoid connection overflow. +?*/ +void +UchDGRAM :: Resend () +{ +if (DGTrace) printf ("UchDGRAM::Resend\n"); + Lock (); + if (npending) + CheckInput (); + if (! npending) { + resend = FALSE; + Unlock (); + return; + } + + UchAddress* addr = 0; + + PENDING* pend; + for (CcuIdIter iter (pending); pend = (PENDING*) iter (); ++iter) { + if (pend->retries <= 0) { + // the output buffer contains the leading id and type + UchMsgBuffer fake (*pend->outBuf); + lword id; + byte typ; + fake.Get (&id); + fake.Get (&typ); + if (! DiscardNotify (fake, *pend->toAddr)) { + pend->retries = retry; // *** should be controllable ? + continue; + } +if (DGTrace) printf ("UchDGRAM::Resend : abandonning %x\n", iter.CurId ()); + RemovePending (iter.CurId ()); + if (! npending) + break; + } else + if (addr != (UchAddress*) pend->toAddr) { +DEBUG printf ("UchDGRAM::Resend : resending %x\n", iter.CurId ()); + SendBuffer (* pend->outBuf, *pend->toAddr); + --pend->retries; + addr = pend->toAddr; + CheckInput (); + } +DEBUG else printf ("UchDGRAM::Resend : skipping %x\n", iter.CurId ()); + } + + resend = FALSE; + Unlock (); +} + +/*? +This virtual function is called whenever a pending message is about +to be discarded because no acknowledge has been received after +the the default number of retries. +If it returns FALSE, the message is not discarded and its retry count is reset to zero. +Returning FALSE is not very social. +The default action is to return TRUE, thus discarding the pending message. +?*/ +bool +UchDGRAM :: DiscardNotify (UchMsgBuffer&, UchAddress&) +{ + return TRUE; +} + +/*? +Set the interval between retries. +The default value is 1000 (1 second). +?*/ +void +UchDGRAM :: SetRetryTime (Millisecond m) +{ + timeout = m; + if (npending) { + timer.ChangePeriod (m); + timer.Restart (); + } +} + +/*? +Wait until all acknowledges have been received, +or until all messages have been discarded. +?*/ +void +UchDGRAM :: Drain () +{ + if (npending) + CheckInput (); + while (npending) + timer.Wait (); +} + +/*? +This virtual function is called by \fun{HandleRead}. +It must be redefined in a derived class if \fun{HandleRead} is to be used +(for instance if this \typ{UchDGRAM} is put in a channel set). +This function should convert the contents of the buffer into a message, and handle it. +It should return TRUE if the message was correctly handled, else FALSE. +Note that if it returns FALSE, the message will not be acknowledged, and thus it will +be resent later. +The default behaviour of this virtual function is to issue an error message and +to return TRUE. +?*/ +bool +UchDGRAM :: NewMessage (UchMsgBuffer&) +{ + Error (ErrWarn, "UchDGRAM :: NewMessage", "should be defined in derived class"); + return TRUE; +} + +/*? +This is an instance of the virtual function \fun{HandleRead} of class \typ{UchChannel}. +It calls the virtual function \fun{NewMessage} when a message is received. +If \fun{NewMessage} returns TRUE, an acknowledge is sent back. +This functions also handles incoming acknowledges. +?*/ +void +UchDGRAM :: HandleRead () +{ + if (! CheckInput ()) + return; + if (! ninput) + return; + +DEBUG if (DGTrace) printf (">>UchDGRAM :: HandleRead\n"); + PENDING* p = (PENDING*) input.RemoveFirst (); + ninput--; + if (NewMessage (* p->outBuf)) + SendAck (p->id, *p->toAddr); +DEBUG if (DGTrace) printf ("<>UchDGRAM :: HandleSelect\n"); + PENDING* p = (PENDING*) input.RemoveFirst (); + ninput--; + if (NewMessage (* p->outBuf)) + SendAck (p->id, *p->toAddr); +DEBUG if (DGTrace) printf ("< +#include +#include +#include +#include +#include +#include + +//---------------- globals +// if SaveTime > 0, the state is saved to SaveFile +// SaveTime seconds after a modification +// if SaveTime < 0, saving is immediate +// if SaveTime is 0, no save +// if SaveFileIsFd >= 0, it is taken as output file descr instead of SaveFile +const char* SaveFile = 0; +int SaveFileIsFd = -1; +int SaveTime = 0; +int Mod = 0; +int NbEntries = 0; +bool Trace = FALSE; +const char* LoadFile; + +//---------------- constants +// if an auto-launch server has not registered for REG_TIME seconds +// and somebody inquires its address, it is killed and restarted. +#define REG_TIME 60000 +#define REG_STR "minute" + +// if an auto-launch server has been run within the last ALR_TIME seconds +// and has not yet registered and somebody inquires its address, +// is is _not_ run. +#define ALR_TIME 30000 +#define ALR_STR "30 seconds" + +struct Serv { + CcuString Key; + lword Host; + sword Port; + int Ident; + // auto-launch stuff + bool AutoLaunch; + bool Running; + Millisecond LastRun; + Millisecond LastRegistered; + char* CmdLine; // the line + char** RunCmd; // array to pointers to CmdLine (for execvp) +}; + +#ifndef CPLUS_BUG19 +CcuListOf ServList; +#else +CcuList ServList; +#endif + +//---------------- auto-launch stuff +char** +Parse (char* line) +{ + enum WHERE { IN_SPACE, IN_WORD, IN_QUOTE }; + char quote = '\0'; + register char* s = line; + register char* word = line; + register WHERE where = IN_SPACE; + char* words [256]; + int nw = 0; + + // split a line into words + // a word is a sequence of non blank characters, + // or a quoted string (' or "). + // '(', ')' and ',' are also ignored to allow for function like notation + for (; *s; s++) { + switch (where) { + case IN_WORD: + if (index (" \t(),", *s)) { + *s = '\0'; + where = IN_SPACE; + if (nw < 255) + words [nw++] = word; + else + return 0; + } + break; + case IN_QUOTE: + if (*s == quote) { + *s = '\0'; + where = IN_SPACE; + if (nw < 255) + words [nw++] = word; + else + return 0; + } + break; + case IN_SPACE: + if (*s == '\'' || *s == '\"') { + quote = *s; + *s = '\0'; + where = IN_QUOTE; + word = s+1; + } else + if (index (" \t(),", *s)) { + *s = '\0'; + } else { + where = IN_WORD; + word = s; + } + break; + } + } + if (where != IN_SPACE) + if (nw < 255) + words [nw++] = word; + else + return 0; + if (where == IN_QUOTE) + return 0; + + // if no argv specified, argv[0] = executable + if (nw == 3) + words [nw++] = words [2]; + // expected format: key dir exec argv + if (nw < 4) + return 0; + // null terminated array for execvp + words [nw++] = 0; + + // return a freshly allocated array + char** nwords = new char* [nw]; + for (int i = 0; i < nw; i++) + nwords [i] = words [i]; + return nwords; +} + +void +AutoLaunch (Serv* s) +{ + // s->RunCmd [0] service name + // s->RunCmd [1] directory + // s->RunCmd [2] executable + // s->RunCmd [3 ... ] argv [0 ...] + + CcuTimeStamp now; + if (s->LastRun + ALR_TIME > now) { +// Log ("AutoLaunch", "%s already launched in the past %s", s->RunCmd [0], ALR_STR); + return; + } +// Log ("AutoLaunch", "running %s dir=%s, exec=%s", s->RunCmd[0], s->RunCmd[1], s->RunCmd[2]); + if (! fork ()) { + // close everything but stdio + for (int i = 3; i < NFILE; i++) + close (i); + // detach from controlling terminal + setsid (); + // change directory + if (chdir (s->RunCmd [1]) < 0) + SysError (ErrFatal, s->RunCmd [1]); + // exec server + if (execvp (s->RunCmd [2], s->RunCmd + 3) < 0) + SysError (ErrFatal, s->RunCmd [2]); + } + s->LastRun = now; +} + +void +LoadConfig () +{ + char* l = getlogin (); + if (! l) + l = cuserid (0); + if (! l) + Error (ErrFatal, "LoadConfig", "getlogin failed"); + + FILE* f = fopen (LoadFile, "r"); + if (!f) + SysError (ErrFatal, LoadFile); + char line [2048]; + while (fgets (line, sizeof (line), f)) { + // empty line or comment + if (! line [0] || line [0] == '#') + continue; + + // expected format: servername dir exec argv0 ... + + // remove trailing newline and parse + line [strlen (line) -1] = 0; + char* nline = new char [strlen (line) + 1]; + strcpy (nline, line); + char** words = Parse (nline); + if (! words) { + Error (ErrWarn, "LoadConfig", "incorrect line ignored in file"); + delete nline; + continue; + } + Serv* s = new Serv; + char key [256]; // portserv:mbl:audioserver + sprintf (key, "portserv:%s:%s", l, words [0]); + s->Key = key; + s->Host = 0; + s->Port = 0; + s->Ident = 0; + s->AutoLaunch = TRUE; + s->Running = FALSE; + s->LastRun= 0; + s->LastRegistered = 0; + s->CmdLine = nline; + s->RunCmd = words; + ServList.Append (s); +// Log ("LoadConfig", "defined auto-launch server %s dir=%s exec=%s", +// s->RunCmd [0], s->RunCmd [1], s->RunCmd [2]); + } + fclose (f); +} + +void +KillProcesses () +{ + // kill all processes that have been autolaunched, + // assuming that their id is their pid. + Serv* s; +#ifndef CPLUS_BUG19 + CcuListIterOf iter (ServList); + while (s = *++iter) { +#else + CcuListIter iter (ServList); + while (s = (Serv*) *++iter) { +#endif + if (s->AutoLaunch && s->Ident) { +// Log ("KillProcesses", "killing %s pid=%d", s->RunCmd [2], (void*) s->Ident); + kill (s->Ident, SIGTERM); + } + } +} + +Serv* +Register (const char* key, lword host, sword port, int id) +{ + Serv* s = 0; + +#ifndef CPLUS_BUG19 + CcuListIterOf iter (ServList); + while (s = *++iter) { +#else + CcuListIter iter (ServList); + while (s = (Serv*) *++iter) { +#endif + if (strcmp (key, s->Key) == 0) + break; + } + if (! s) { + s = new Serv; + s->Key = key; + s->AutoLaunch = FALSE; + s->LastRun = 0; + s->CmdLine = 0; + s->RunCmd = 0; + ServList.Append (s); + } + s->Host = host; + s->Port = port; + s->Ident = id; + CcuTimeStamp now; + s->LastRegistered = now; + s->Running = TRUE; + return s; +} + +bool +Remove (const char* key, lword host, sword port, int id) +{ + Serv*s; +#ifndef CPLUS_BUG19 + CcuListIterOf iter (ServList); + CcuListIterOf trailing_iter (ServList); +#else + CcuListIter iter (ServList); + CcuListIter trailing_iter (ServList); +#endif + while (++iter) { +#ifndef CPLUS_BUG19 + s = *iter; +#else + s = (Serv*) *iter; +#endif + if (host == s->Host && port == s->Port && id == s->Ident && strcmp (key, s->Key) == 0) { + if (s->AutoLaunch) { + s->Running = FALSE; + s->LastRun = 0; + s->LastRegistered = 0; + s->Host = 0; + s->Port = 0; + s->Ident = 0; + } else { + s->Key = 0; + delete s; + ServList.RemoveAfter (trailing_iter); + } + return TRUE; + } + ++trailing_iter; + } + Error (ErrWarn, key, "not found for remove"); + return FALSE; +} + +Serv* +Inquire (const char* key) +{ + Serv*s; +#ifndef CPLUS_BUG19 + CcuListIterOf iter (ServList); +#else + CcuListIter iter (ServList); +#endif + while (++iter) { +#ifndef CPLUS_BUG19 + s = *iter; +#else + s = (Serv*) *iter; +#endif + + if (strcmp (key, s->Key) != 0) + continue; + + if (!s->AutoLaunch) + return s; + + if (s->Running) { + CcuTimeStamp now; + if (s->LastRegistered + REG_TIME < now) { +// Log ("Inquire", "%s not registered for the last %s: autolaunching it", s->RunCmd [0], REG_STR); + if (s->Ident) + kill (s->Ident, SIGTERM); + s->Host = 0; + s->Port = 0; + s->Ident = 0; + AutoLaunch (s); + } + } else + AutoLaunch (s); + return s; + } + Error (ErrWarn, key, "not found"); + return 0; +} + + +void +Match (const char* key, UchDatagram& portserv) +{ + UchMsgBuffer buf (256); + UchPortServerReq ans (PortServMatch); + int nb = 0; + + CcuRegExp re (key); + if (re.Compile ()) { + Serv*s; +#ifndef CPLUS_BUG19 + CcuListIterOf iter (ServList); + while (++iter) { + s = *iter; +#else + CcuListIter iter (ServList); + while (++iter) { + s = (Serv*) *iter; +#endif + if (re.Match (s->Key)) { +if (Trace) printf ("Match: %s\n", (const char*) s->Key); + ans.Host = s->Host; + ans.Port = s->Port; + ans.SetKey (s->Key); + buf.Append (ans); + portserv.Reply (buf); + nb++; + } + } + } else + nb = -1; + ans.Type = PortServEndMatch; + ans.Ident = nb; + ans.SetKey (0); + buf.Append (ans); + portserv.Reply (buf); +} + +int +Dump () +{ + Serv*s; +#ifndef CPLUS_BUG19 + CcuListIterOf iter (ServList); +#else + CcuListIter iter (ServList); +#endif + while (++iter) { +#ifndef CPLUS_BUG19 + s = *iter; +#else + s = (Serv*) *iter; +#endif + printf ("<%s> 0x%lx:%d (%d)\n", (const char*) s->Key, s->Host, s->Port, s->Ident); + } + return 0; +} + +int +DumpFile () +{ +if (Trace) printf ("dumping\n"); + int fd = SaveFileIsFd; + if (fd < 0 && (fd = creat (SaveFile, 0600)) < 0) { + SysError (ErrWarn, SaveFile); + return 0; + } + UchFilDes Fd (fd); + char buf [256]; + Serv*s; +#ifndef CPLUS_BUG19 + CcuListIterOf iter (ServList); +#else + CcuListIter iter (ServList); +#endif + while (++iter) { +#ifndef CPLUS_BUG19 + s = *iter; +#else + s = (Serv*) *iter; +#endif + sprintf (buf, "0x%lx:%d %s\n", s->Host, s->Port, (const char*) s->Key); + Fd.Write ((byte*) buf, strlen (buf)); + } + Mod = 0; + return 1; +} + +// for handlers +// +void +SigDump (int) +{ + Dump (); +} + +void +SigDumpFile (int) +{ + DumpFile (); +} + +void +Modified () +{ + if (! Mod && SaveTime > 0) { + Mod++; + alarm (SaveTime); + return; + } + if (SaveTime < 0) { + Mod++; + DumpFile (); + } +} + +void +Usage () +{ + Error (ErrUsage, "portserv", "[-t] [-s service] [-quit] [-save file [time]] [-load file]"); +} + +main (int argc, const char** argv) +{ + const char* servname = "portserv"; + int running = 1; + bool qflag = FALSE; + + ProgramName (argv [0]); + while (--argc) { + ++argv; + if (strcmp (*argv, "-quit") == 0) { + qflag = TRUE; + } else + if (strcmp (*argv, "-t") == 0) { + Trace = TRUE; + } else + if (strcmp (*argv, "-s") == 0) { + if (--argc < 1) + Usage (); + servname = *++argv; + } else + if (strcmp (*argv, "-save") == 0) { + if (--argc < 1) + Usage (); + SaveFile = *++argv; + if (strcmp (SaveFile, "-") == 0) + SaveFileIsFd = 1; + else if (strcmp (SaveFile, "--") == 0) + SaveFileIsFd = 2; + + if (argc > 1 && argv [1][0] != '-') { + SaveTime = atoi (*++argv); + --argc; + } else + SaveTime = -1; + } else if (strcmp (*argv, "-load") == 0 || strcmp (*argv, "-l") == 0) { + if (--argc < 1) + Usage (); + LoadFile = *++argv; + } else + Usage (); + } + + // special case: portserv -quit + // send a PortServQuit message to + // the portserver that is running + if (qflag) { + if (SaveFile || LoadFile) + Error (ErrFatal, "-quit", "incompatible with -save or -load"); + UchPortServer ps (servname, 0); + ps.Quit (); + exit (0); + } + + sword servport; + + // get service name + // + struct servent* service = getservbyname (servname, 0); + if (service) + servport = ntohs (service->s_port); + else { + const char* portno = getenv (servname); + if (portno) { + servport = atoi (portno); + } else + Error (ErrFatal, servname, "service not available"); + } + + // load file + // + if (LoadFile) + LoadConfig (); + + // open dgram socket + // +if (Trace) printf ("opening\n"); + UchDatagram portserv (new UchInetAddress (ANYADDR, servport), 0); + if (! portserv.Setup ()) +// Error (ErrFatal, "main", "Setup failed"); + SysError (ErrFatal, "main"); + + signal (SIGHUP, SigDump); + if (SaveTime > 0) + signal (SIGALRM, SigDumpFile); + + // handle requests + // + UchMsgBuffer buf (256); + UchPortServerReq req; + +if (Trace) printf ("looping\n"); + while (running) { + buf.Flush (); + int n = portserv.Receive (buf); + if (n <= 0) { + SysError (ErrFatal, "Receive", EINTR); + continue; + } +if (Trace) printf ("received\n"); + if (! buf.Get (&req)) { + Error (ErrWarn, "Receive", "could not read message"); + continue; + } + + // respond + switch (req.Type) { + case PortServRegister : +if (Trace) printf ("register\n"); + Register (req.Key, req.Host, req.Port, int (req.Ident)); + Modified (); + break; + + case PortServRemove : +if (Trace) printf ("remove\n"); + Remove (req.Key, req.Host, req.Port, int (req.Ident)); + Modified (); + break; + + case PortServInquire : + { +if (Trace) printf ("inquire\n"); + UchPortServerReq ans; + Serv* inq = Inquire (req.Key); + if (inq) { + ans.Type = PortServAnswer; + ans.Host = inq->Host; + ans.Port = inq->Port; + } else { + ans.Type = PortServFail; + ans.Host = 0; + ans.Port = 0; + } + buf.Flush (); + buf.Append (ans); + portserv.Reply (buf); + } + break; + + case PortServMatch : +if (Trace) printf ("match\n"); + Match (req.Key, portserv); + break; + + case PortServDump : +if (Trace) printf ("dump\n"); + Dump (); + break; + + case PortServQuit : +if (Trace) printf ("quit\n"); + if (SaveTime) + DumpFile (); + KillProcesses (); + running = 0; ////////dangerous + break; + + default: +if (Trace) printf ("unknown\n"); + Error (ErrWarn, "Receive", "unknown request"); + } + } +} + diff --git a/comm/OLD/porttest.cc b/comm/OLD/porttest.cc new file mode 100644 index 0000000..c73268d --- /dev/null +++ b/comm/OLD/porttest.cc @@ -0,0 +1,72 @@ +/* + * The Unix Channel + * + * by Michel Beaudouin-Lafon + * + * Copyright 1990-1993 + * Laboratoire de Recherche en Informatique (LRI) + * + * Port server: test program + * + * $Id$ + * $CurLog$ + */ + +#include "PortServer.h" + +#include +#include +#include + +int +fMatch (const char* match, lword host, sword port, const char*) +{ + printf ("0x%lx:%d -> %s\n", host, port, match); + return 1; +} + +main (int argc, char **argv) +{ + char *host = 0; + char *service = "portserv"; + + if (argc > 1 && argv [1] [0] == ':') { + host = argv [1] + 1; + argc--, argv++; + } + if (argc > 1 && argv [1] [0] == '=') { + service = argv [1] + 1; + argc--, argv++; + } + UchPortServer ps (service, host); + + switch (argc) { + case 1: + ps.Dump (); + break; + case 2: + if (strcmp (argv [1], "quit") == 0) + ps.Quit (); + else + if (argv [1] [0] == '?') + ps.Match (argv [1] + 1, fMatch); + else { + UchInetAddress* addr = ps.Inquire (argv [1]); + if (addr) { + printf ("0x%lx:%d\n", addr->Host (), addr->Port ()); + delete addr; + } else + printf ("not found\n"); + } + break; + case 3: { + UchInetAddress addr ("", atoi (argv [2])); + if (argv [1] [0] == '-') + ps.Remove (argv [1] + 1, addr, 987); + else + ps.Register (argv [1], addr, 987); + break; + } + } +} + diff --git a/comm/OLD/reqgen.cc b/comm/OLD/reqgen.cc new file mode 100644 index 0000000..648cc83 --- /dev/null +++ b/comm/OLD/reqgen.cc @@ -0,0 +1,34 @@ +/* + * The Unix Channel + * + * by Michel Beaudouin-Lafon + * + * Copyright 1993 + * Centre d'Etudes de la Navigation Aerienne (CENA) + * + * Request management, by Stephane Chatty + * + * $Id$ + * $CurLog$ + */ + +#include "ReqMgr.h" +#include + +static void +usage () +{ + fprintf (stderr, "usage: reqgen file\n"); +} + +main (int argc, const char** argv) +{ + if (argc !=2) { + usage (); + return 0; + } + UchReqMgr r; + r.Read (argv [1]); + r.DumpHeader ("reqgen.out.h"); + r.DumpSource ("reqgen.out"); +} \ No newline at end of file diff --git a/comm/Socket.cc b/comm/Socket.cc new file mode 100644 index 0000000..1d5a542 --- /dev/null +++ b/comm/Socket.cc @@ -0,0 +1,274 @@ +/* + * The Unix Channel + * + * by Michel Beaudouin-Lafon + * + * Copyright 1990-1993 + * Laboratoire de Recherche en Informatique (LRI) + * + * Sockets + * + * $Id$ + * $CurLog$ + */ + +#include "Socket.h" + +#include +#include +#include + +extern int errno; + +/*?class UchSocket +The class \typ{UchSocket} derives from \typ{UchChannel}. +It is a virtual base class: no objects of class \typ{UchSocket} are ever created. +It implements a Unix socket, that is a file descriptor and two addresses: +the address the socket is bound to, and the address it is connected to. +An address needs to be bound to a socket only if another process wants to connect to this socket; +a socket needs to be connected to an address only for streams or connected datagrams. +Thus, none of the addresses is mandatory. + +Addresses are always referenced by pointers. +Smart pointers to addresses (\typ{^{pUchAddress}}) can be used anywhere a pointer is used. +?*/ + +/*? +Construct a closed socket. +?*/ +UchSocket :: UchSocket () +: UchChannel (), + BAddr (0), + CAddr (0), + AddrFamily (AF_UNSPEC) +{ +} + +/*? +Construct a closed socket bound to address \var{bound} and connected to address \var{connected}. +Each or both arguments may be 0. +?*/ +UchSocket :: UchSocket (UchAddress* bound, UchAddress* connected) +: UchChannel (), + BAddr (bound), + CAddr (connected), + AddrFamily (AF_UNSPEC) +{ +} + +/*?nodoc?*/ +UchSocket :: UchSocket (const UchSocket& s) +: UchChannel (s), + BAddr (s.BAddr), + CAddr (s.CAddr) +{ +} + +/*?nodoc?*/ +UchSocket :: ~UchSocket () +{ + // will unreference addresses and destroy them if necessary + BAddr = 0; + CAddr = 0; +} + +/*?nodoc?*/ +UchChannel* +UchSocket :: Copy () const +{ + return new UchSocket (*this); +} + +/*? +This virtual function returns the type of the socket: +one of \var{SOCK_UNSPEC}, \var{SOCK_STREAM}, \var{SOCK_DGRAM}, +depending on the class. +?*/ +int +UchSocket :: SockType () +{ + return SOCK_UNSPEC; +} + +/*?nextdoc?*/ +void +UchSocket :: BindTo (UchAddress* a) +{ + BAddr = a; +} + +/*? +Set the address a socket is to be bound to or connected to. +?*/ +void +UchSocket :: ConnectTo (UchAddress* a) +{ + CAddr = a; +} + +/*? +Open the socket (with the \fun{socket} system call) if it is not already open. +The socket must have an address bound or connected to get the protocol family, +or its family must have been defined with \fun{SetFamily}. +Return FALSE if the family is undefined or if a system error occurred. +?*/ +bool +UchSocket :: Open () +{ + errno = 0; + if (FilDes () >= 0) + return TRUE; + if (AddrFamily == AF_UNSPEC) { + if (BAddr) + AddrFamily = BAddr->Family (); + else if (CAddr) + AddrFamily = CAddr->Family (); + else + return FALSE; + } + int fd = socket (AddrFamily, SockType (), 0); + if (fd < 0) + return FALSE; + UchChannel::Open (fd); + return TRUE; +} + +/*?nextdoc?*/ +int +UchSocket :: Bind () +{ + if (! BAddr || ! BAddr->IsValid ()) + return -1; + if (! Open ()) + return -1; + int ret = bind (FilDes (), BAddr->GetSockAddr (), BAddr->Length ()); + if (ret < 0) + return ret; + + GEN_ADDR addr; + int alen = sizeof (addr); + if (getsockname (FilDes (), &addr.sa, &alen) < 0) + return -1; + BAddr = UchAddress::Decode (&addr, alen); + return ret; +} + +/*?nextdoc?*/ +int +UchSocket :: Bind (UchAddress* addr) +{ + if (addr) + BAddr = addr; + return Bind (); +} + +/*?nextdoc?*/ +int +UchSocket :: Connect () +{ + if (! CAddr || ! CAddr->IsValid ()) + return -1; + if (! Open ()) + return -1; + int ret = connect (FilDes (), CAddr->GetSockAddr (), CAddr->Length ()); + if (ret < 0) + return ret; + + GEN_ADDR addr; + int alen = sizeof (addr); + if (getpeername (FilDes (), &addr.sa, &alen) < 0) + return -1; + CAddr = UchAddress::Decode (&addr, alen); + return ret; +} + +/*? +These two functions implement the Unix system calls \fun{bind} and \fun{connect}. +If \var{addr} is given, it is first associated to the socket. +Then the socket is opened, and finally the system call is performed. +The returned value is that of the system call, unless opening failed in which case +-1 is returned. +?*/ +int +UchSocket :: Connect (UchAddress* addr) +{ + if (addr) + CAddr = addr; + return Connect (); +} + +/*? +Open the socket if it is not already open. +Bind and connect it depending on the addresses that are defined. +You should use this function instead of calling \fun{Open}, \fun{Bind} and +\fun{Connect}, for it is simpler. +This function returns FALSE if a system error occurred. In this case, the caller can +call \fun{SysError} to report the error. +?*/ +bool +UchSocket :: Setup () +{ + if (! Open ()) + return FALSE; + if (BAddr) + if (BAddr->IsValid ()) + if (Bind () < 0) + return FALSE; + if (CAddr) + if (CAddr->IsValid ()) + if (Connect () < 0) + return FALSE; + return TRUE; +} + +/*?nodoc?*/ +char* +UchSocket :: StrRepr (char* buf) +{ + UchChannel :: StrRepr (buf); + strcat (buf, " / "); + if (BAddr) + BAddr -> StrRepr (buf + strlen (buf)); + else + strcat (buf, "null"); + strcat (buf, " / "); + if (CAddr) + CAddr -> StrRepr (buf + strlen (buf)); + else + strcat (buf, "null"); + return buf; +} + +#ifdef DOC + +/*?nextdoc?*/ +int +UchSocket :: Family () +{ } + +/*? +These function get and set the family. +The possible values currently are \var{AF_UNSPEC}, \var{AF_UNIX} and \var{AF_INET}. +Other values may be defined if supported by the system. +The family must be defined for \fun{Open} (and thus \fun{Setup}) to succeed. +If an address is bound or connected to the socket, its family is used. +Thus, an application seldom needs to call \fun{SetFamily}. +?*/ +void +UchSocket :: SetFamily (int f) +{ } + +/*?nextdoc?*/ +UchAddress* +UchSocket :: BoundTo () +{ } + +/*? +Return the address currently bound or connected to the socket. +?*/ +UchAddress* +UchSocket :: ConnectedTo () +{ } + +#endif /* DOC */ + diff --git a/comm/Socket.h b/comm/Socket.h new file mode 100644 index 0000000..db84209 --- /dev/null +++ b/comm/Socket.h @@ -0,0 +1,60 @@ +/* + * The Unix Channel + * + * by Michel Beaudouin-Lafon + * + * Copyright 1990-1993 + * Laboratoire de Recherche en Informatique (LRI) + * + * Sockets + * + * $Id$ + * $CurLog$ + */ + +#ifndef Socket_H_ +#define Socket_H_ + +#include "cplus_bugs.h" +#include "Channel.h" +#include "Address.h" + +extern char* StrReprBuf; + +#define SOCK_UNSPEC 0 + +// Virtual base class for sockets. +// A socket is a channel and an address. +// As a channel, it is immutable, and must be initialized once and for all at creation. +// +class UchSocket : public UchChannel { +protected: + pUchAddress BAddr; + pUchAddress CAddr; + int AddrFamily; +public: + UchSocket (); + UchSocket (UchAddress*, UchAddress*); + ~UchSocket (); + UchSocket (const UchSocket& s); + + UchChannel* Copy () const; +virtual int SockType (); // SOCK_UNSPEC, SOCK_STREAM, SOCK_DGRAM +inline int Family () { return AddrFamily; } +inline void SetFamily (int f) { AddrFamily = f; } + + void BindTo (UchAddress*); + void ConnectTo (UchAddress*); +inline UchAddress* BoundTo () { return BAddr; } +inline UchAddress* ConnectedTo () { return CAddr; } + bool Open (); + int Bind (); + int Bind (UchAddress*); + int Connect (); + int Connect (UchAddress*); + bool Setup (); + char* StrRepr (char* = StrReprBuf); +}; + +#endif /* Socket_H_ */ + diff --git a/comm/Stream.cc b/comm/Stream.cc new file mode 100644 index 0000000..f03e298 --- /dev/null +++ b/comm/Stream.cc @@ -0,0 +1,124 @@ +/* + * The Unix Channel + * + * by Michel Beaudouin-Lafon + * + * Copyright 1990-1993 + * Laboratoire de Recherche en Informatique (LRI) + * + * Streams + * + * $Id$ + * $CurLog$ + */ + +#include "Stream.h" +#include +#include + +/*?class UchStream +Streams implement reliable point to point connections. +UchStream connections do not keep the message boundaries: +several messages can be read at a time, and a message can be read in several parts. + +Establishing a stream connection is not a symmetric operation: +one process (the server) creates a bound socket and listens to it (with function \fun{Listen}); +other processes (the clients) create sockets and connect them to the address of the server. +Each such connection is accepted (\fun{SockAccept}) by the server and results in the creation of a new socket. +Communication with each client will then proceed on this channel, +while the server still can accept connections. + +Because a server has to listen to pending connections and communicate +with its connected clients, using a channel set is highly recommended: +when a connection is pending, the channel is ready for reading, +and you can accept the connection with \fun{SockAccept} (instead of reading data). +Note that because \typ{UchStream} derives from \typ{UchSocket} +and thus from \typ{UchChannel}, the virtual functions +\fun{HandleRead} and \fun{HandeWrite} can be used. +?*/ + +/*?nextdoc?*/ +UchStream :: UchStream () +: UchSocket () +{ +} + +/*? +These constructors are similar to those of the class \typ{UchSocket}. +?*/ +UchStream :: UchStream (UchAddress* bound, UchAddress* connected) +: UchSocket (bound, connected) +{ +} + +/*?nodoc?*/ +UchStream :: UchStream (const UchStream& s) +: UchSocket (s) +{ +} + +/*?nodoc?*/ +UchStream :: ~UchStream () +{ +} + +/*?nodoc?*/ +UchChannel* +UchStream :: Copy () const +{ + return new UchStream (*this); +} + +/*?nodoc?*/ +int +UchStream :: SockType () +{ + return SOCK_STREAM; +} + +/*? +This function implements the Unix system call \fun{listen}. +\var{n} is the maximum number of pending connections: +if \var{n} clients are waiting for the server to accept their connection, +the next client will have its connection refused. +You can safely use the default value. +The stream socket is first set up (with \fun{Setup}), and then the system call is performed. +The returned value is that of the system call, unless the set up failed in which case +-1 is returned. +Note that it is useless to listen on a socket if it is not bound to an address. +?*/ +int +UchStream :: Listen (int n) +{ + if (! Setup ()) + return -1; + return listen (FilDes (), n); +} + +/*? +This function implements the Unix system call \fun{accept}. +The stream socket is first set up (with \fun{Setup}), and then the system call is performed. +A new dynamically created socket is returned, connected to the address returned by \fun{accept}. +If the socket could not be set up or a system error occurred, 0 is returned. +Note that in order to accept connections, the socket must be listen to, +and thus an address must be bound to it. +?*/ +UchSocket* +UchStream :: SockAccept () +{ + GEN_ADDR addr; + int alen = sizeof (addr); + int fd; + + if (! Setup ()) + return (UchSocket*) 0; + + if ((fd = accept (FilDes (), &addr.sa, &alen)) < 0) + return (UchSocket*) 0; + + UchSocket* s = new UchSocket (0, UchAddress::Decode (&addr, alen)); + s -> UchChannel :: Open (fd); + return s; +} + + diff --git a/comm/Stream.h b/comm/Stream.h new file mode 100644 index 0000000..3a4282d --- /dev/null +++ b/comm/Stream.h @@ -0,0 +1,33 @@ +/* + * The Unix Channel + * + * by Michel Beaudouin-Lafon + * + * Copyright 1990-1993 + * Laboratoire de Recherche en Informatique (LRI) + * + * Streams + * + * $Id$ + * $CurLog$ + */ + +#ifndef Stream_H_ +#define Stream_H_ + +#include "cplus_bugs.h" +#include "Socket.h" + +class UchStream : public UchSocket { +public: + UchStream (); + UchStream (UchAddress*, UchAddress*); + ~UchStream (); + UchStream (const UchStream&); + UchChannel* Copy () const; + int SockType (); + int Listen (int = 5); + UchSocket* SockAccept (); +}; + +#endif /* Stream_H_ */ diff --git a/comm/TimeOut.cc b/comm/TimeOut.cc new file mode 100644 index 0000000..18fb3ec --- /dev/null +++ b/comm/TimeOut.cc @@ -0,0 +1,69 @@ +#include "TimeOut.h" +#include "ccu/Signal.h" +#include "Multiplexer.h" + +UchBaseTimeOut :: UchBaseTimeOut (UchMultiplexer& m, Millisecond period, int pulses) +: CcuCoreTimer (period, pulses, m.GetTimerSet ()), + MyMpx (m) +{ + CcuSignalBlocker b (SigAlrm); + + if (PulsesLeft != 0) + Activate (); +} + +/*?hidden?*/ +UchBaseTimeOut :: ~UchBaseTimeOut () +{ + /* stop it */ + if (StatusFlag == Active) + Stop (); +} + +/*?hidden?*/ +void +UchBaseTimeOut :: StopAlarm () +{ + MyMpx.SuppressTimeOut (); +} + +/*?hidden?*/ +void +UchBaseTimeOut :: SetAlarm (Millisecond when) +{ + MyMpx.SetTimeOut (when); +} + + + + +/*?class UchTimeOut +The class \typ{UchTimeOut} is a derived class of \typ{UchBaseTimeOut} that +can be used without deriving a new class. +Each \typ{UchTimeOut} holds a pointer to a function which is called when the timer +expires. This function, which is passed to the constructor, must +take a \typ{Millisecond} argument and return \typ{void}. +?*/ + +/*? +Create a timer associated to the multiplexer \var{m}, that will expire every \var{period} milliseconds and call +the function \var{handler}. +?*/ +UchTimeOut :: UchTimeOut (UchMultiplexer& m, Millisecond period, void (*handler) (Millisecond), int pulses) +: UchBaseTimeOut (m, period, pulses), + Handler (handler) +{ +} + +/*?nodoc?*/ +UchTimeOut :: ~UchTimeOut () +{ +} + +/*?nodoc?*/ +void +UchTimeOut :: Handle (Millisecond ref) +{ + (*Handler) (ref); +} + diff --git a/comm/TimeOut.h b/comm/TimeOut.h new file mode 100644 index 0000000..9a05cb2 --- /dev/null +++ b/comm/TimeOut.h @@ -0,0 +1,32 @@ +#ifndef UchTimeOut_H_ +#define UchTimeOut_H_ + +#include "ccu/Timer.h" + +class UchBaseTimeOut : public CcuCoreTimer { +friend class UchMultiplexer; + +private: +static void ClassInit (); + +protected: + UchMultiplexer& MyMpx; + void SetAlarm (Millisecond); + void StopAlarm (); + +public: + UchBaseTimeOut (UchMultiplexer&, Millisecond, int = -1); + ~UchBaseTimeOut (); +}; + +class UchTimeOut : public UchBaseTimeOut { +protected: + void (*Handler) (Millisecond); + void Handle (Millisecond); + +public: + UchTimeOut (UchMultiplexer&, Millisecond, void (*) (Millisecond), int = -1); + ~UchTimeOut (); +}; + +#endif /* UchBaseOut_H_ */ diff --git a/comm/doc.main b/comm/doc.main new file mode 100644 index 0000000..44abcd4 --- /dev/null +++ b/comm/doc.main @@ -0,0 +1,396 @@ +% +% The Unix Channel +% +% by Michel Beaudouin-Lafon +% +% Copyright 1990-1993 +% Laboratoire de Recherche en Informatique (LRI) +% +% User's Manual main file - to be updated +% +% $Id$ +% $CurLog$ +% + +\namedoc{Unix Channel} +\def\uch{{\sc Uch}} + +\chapter{Introduction} +%-------------------------------- + +The Unix Channel library provides a set of classes to build distributed applications +under Unix using sockets. + +The first level of the library, described in chapter \ref{General io}, defines basic +classes: messages to be exchanged, buffers to hold data (usually messages), +file descriptors that represent communication channels, and channel sets for +multiplexing input/output. + +The second level, described in chapters \ref{Addresses} and \ref{Basics}, defines +classes to build networks of processes exchanging messages. +The different classes implement different transport protocols: datagrams (fast but +unreliable), byte streams (reliable but without message boundaries), and streams +(reliable delivery of messages). + +The third level is a specialized set of classes for implementing distributed +applications using the client/server architecture. Chapter \ref{Servers} describes +the server side, while chapter \ref{Clients} describes the server side. +An application can be both a server and the client of other applications. + +Finally chapter \ref{Portserv} describes a set of classes to communicate +addresses between the components of a distributed application. +Most of the time th eoperating system allocates the channel addresses; +a cumbersome part of the protocol often is for a client to know the physical address +of a server from a logical name. +The port server described in chapter \ref{Portserv} implements a service that maps +logical names to physical addresses with a context. + +The present documentation assumes some knowledge of the network facilities +available under Unix, as described in the document +``{\em A Socket-Based Interprocess Communication Tutorial}''. +This document is part of the Unix documentation. + +Some knowledge of the {\em CCU} library is also needed. +This library is described in the document +``{\em CCU, the CENA C++ Utilities library}''. +\uch\ uses that library mainly for {\em smart pointer} classes. +Given a class \typ{A}, a smart pointer class to \typ{A}, usually named \typ{pA}, +implements a class that behaves like pointers to objects of class \typ{A} (\typ{A*}), +with the following additional feature: +the pointed to object maintains a count of the number of smart pointers +pointing to it; when this reference count reaches zero and the object was +allocated dynamically (with \fun{operator new}), it is automatically destroyed. + + +\section{Using The Unix Channel} +\uch\ is provided as 4 files~: + +\begin{enumerate} + + \item + the header files \com{chan.h} and \com{uch.h}, usually installed in \com{/usr/include/CC}, + contains the definitions for classes, functions and constants provided by \uch. + \com{chan.h} contains the classes described in chapter \ref{General io} and can be used + in simple applications. + \com{uch.h} contains all the classes and is to be used by advanced applications. + One of these files should be included in any file using the library. + As most of \uch\ functions are member functions, this file can + be used as a quick reference. + + \item + \samepage + {the archive files \com{libUch.a} and \com{libChan.a}, which are usually installed in \com{/usr/lib} + or in \com{/usr/loc\-al/lib}, contain the library procedures. One of these libraries must be loaded + with the object files which use \uch. This is usually performed + by adding the flag \com{-lUch} or \com{-lChan} in the command line for your C++ compiler. + \uch\ uses the utilities library, so \com{libutils++.a} must be included when loading an application. + \com{libUch.a} contain the object files for the whole library, while \com{libChan.a} + contains only the object files for the classes described in chapter \ref{General io}, + corresponding to the header file \com{chan.h}. + For instance, you can type~: + \begin{center} + \com{CC -o demo main.C -lUch -lCcu} + \end{center}} + +\end{enumerate} + + +\chapter{Error management} + +#iclass UchError + +\chapter{General io} +\label{General io} + +This chapter describes the very basic classes of \uch. + +Unix input/output is byte oriented: byte buffers can be read and written in file descriptors +with the system calls \fun{read} and \fun{write}. +\uch\ implements message oriented input/output, +although byte io can be used if needed. +The class \typ{Message} is the abstract base class for the messages +to be exchanged between applications. A \typ{Message} only needs to +know how to convert itself to and from a set of bytes in a \typ{MsgBuffer}. +The class \typ{MsgBuffer} is used throughout \uch\ to buffer input and output. +Buffering output can dramatically improve performances (especially if the +messages are short), while buffering input is necessary to implement message +delivery (a message can be received in several pieces that need to be gathered +before actual delivery to the application). + +The following types are defined for machine independent handling of data: +\begin{itemize} +\item The type \typ{^{byte}} is defined as an 8-bit quantity. Buffers contain bytes. +\item The type \typ{^{sword}} refers to a 16-bit quantity. +\item The type \typ {^{lword}} refers to a 32-bit quantity. +\end{itemize}. + +The class \typ{UchFilDes} encapsulates the Unix notion of file descriptor. +The class \typ{UchChannel} derives from \typ{UchFilDes}, adding virtual functions +for implementing multiplexing and communication protocols. +The class \typ{UchMultiplexer} implements a set of channels for multiplexing +input and output. + +These classes are not sufficient to write distributed applications because +there is no way to establish a communication between two processes. +However they can be used for traditional input/output to terminals and files. +This is illustrated in section \ref{General io example}. + +#class UchMessage +#class UchMsgBuffer +#class UchFilDes +#class UchChannel +#class UchMultiplexer + +\section{Example} +\label{General io example}. + +[to be done] + +\chapter{Addresses} +\label{Addresses} + +Processes can communicate once a channel has been established +between them. One of the process has to create the channel, and thus has to +know the other process. More precisely, a process connects to an {\em address} +on which the other process is listening. +This chapter describes classes that represent such addresses. + +Addresses can be established in different {\em domains}. +Think of e-mail addresses and surface mail addresses: they are +completely different although both can be used to reach the same person. +Two domains are implemented: Unix domain addresses, and internet domain addresses. + +A Unix domain address is represented by a file in the file system. +A process only needs to open the file to connect to the process that created the address. +An internet domain address is made of a host number and a port number. +It makes it possible for processes on different machines to communicate. +Unfortunately the port numbers of internet addresses are usually assigned by +the system, so that a distant process cannot know this port number to +establish the communication. +The port server described in chapter \ref{Portserv} overcomes this problem. +It is often used to create internet addresses from logical names, +instead of the class \typ{UchInetAddress} described in this chapter. + +#class UchAddress +#class UchUnixAddress +#class UchInetAddress + + +\chapter{Basic communications} +%-------------------------------- +\label{Basics} + +This chapter describes the main classes for developing distributed applications. +The communication between the processes is done by using Unix sockets. +The class \typ{UchSocket} encapsulates such sockets, but it is an abstract base class. +The classes \typ{UchDatagram} and \typ{UchStream} implement the two main +protocols available under Unix, i.e. datagrams and streams. +These protocols differ in the way the connection can be established and the way +the data is transmitted between the processes. +In particular, streams transfer bytes and do not know about messages. +The class \typ{UchMsgStream} extends the stream protocol to transfer messages +and not bytes. +This is the class that will be used most of the time to build distributed applications, +as illustrated in section \ref{Basics example} + +\fig{socketclasses}{Hierarchy of classes} + +Figure \ref{fig:socketclasses} shows the derivation tree for the classes described +in this chapter. All derivations are public. This means that the member functions +of a base class are available in its derived classes, although they appear in the +documentation only for the base class. + +#class UchSocket +#class UchDatagram +#class UchStream +#class UchMsgStream +#class UchDGRAM + +\section{Example} +\label{Basics example} + +[to be done] + +\chapter{Servers} +%-------------------------------- +\label{Servers} + +This chapter describes the classes \typ{UchServer} and \typ{UchClient} to be used +to implement a server. +A server is a process that accepts connections from several clients, +and processes their requests. + +When implementing a server, there is one object of class \typ{UchServer}, +and one object of class \typ{UchClient} for each connected client. +Each such \typ{UchClient} object is connected to another process where +a corresponding \typ{Service} object exists (see figure \ref{fig:clientserver}). +We call these processes the clients of the server: +a \typ{UchClient} represents a connected client in the server, +while a \typ{Service} represents the server in a client. + +\fig{clientserver}{Objects in a client-server system} + +The class \typ{Service} is useful only if your application follows an +event/request protocol. +If this is not the case, you can create your own class, derived from +class \typ{MsgStream}. +The class \typ{Service} and the associated classes for managing +events are described in chapter \ref{Clients}. + +#class UchServer +#class UchClient + +\chapter{Clients} +\label{Clients} + +An object of a class derived from \typ{UchMsgStream} must exist in a client process to communicate +with its server (implemented by an object of class \typ{UchClient} in the server process). + +The class \typ{UchService} described here can be used when the protocol between the +server and its clients is of the event/request type. + +\fig{evreq}{Event / request protocol model.} + +In the event/request model (see figure \ref{fig:evreq}), +a client communicates with a server by sending requests. +These requests are generally asynchronous, thus allowing buffered i/o. +Some requests may need an answer from the server: those requests are synchronous by nature. +The server sends events to its clients; +events are asynchronous by nature: a server never waits for an answer from a client, +for this would potentially block it. + +Incoming requests are stored in an event queue for later processing by the client: +usually the client reads an event in its top-level loop, and responds to it; +this may involve sending requests to the server, that in turn will generate events. + +A single client may want to be connected to several servers. +The class \typ{UchService} makes this possible by allowing to share an event queue +between several services. +Thus incoming events are multiplexed, and the top level loop of the client is of the +same form as above. +Each event contains the server that sent it so that it can be easily dispatched by the client. + +Events are messages. They are implemented by the virtual base class \typ{EventMsg}. +The event queue of a service is implemented by the class \typ{UchEvtMsgQueue}. + +#class UchService +#class UchEventMsg +#class UchGenEvtMsg +#class UchEvtMsgQueue + +\chapter{Port registration server} +\label{Portserv} + +A client and a server need to agree on an address to establish a communication. +Internet addresses are generally allocated by the system by passing a null port number; +thus, there is no simple way for the clients to know on which address the server is listening. + +The standard solution to this problem is to use the services database of Unix, +to associate a given port number to a service name. +This database is not designed to be updated easily (it is the job of the system administrator), +so it is not possible for a server to register its address in this database each time the server is launched. +Instead, for a given service, the database holds the service name and port number of a daemon, +and this daemon maintains the address of the server currently implementing this service. +Several servers may use the same daemon, for instance if a given service +needs one server per different user. + +The port server is such a daemon. +The port server is a separate process that stores associations between keys and addresses. +Each such association represents a server that is available. +The key can encode the service name, user, etc... +Servers register themselves by entering their address with a key and an identification +(usually their process number); +they can (they should) remove their entry from the port server whenever they die. +In order to connect to a server, a client inquires the address to the port server by sending it the key. +It is also possible to ask the port server a set of keys based on a regular expression. + +The port server itself is run by the following command: +\begin{ccode} +portserv [ service [ file [ savetime ] ] ] +\end{ccode} +\var{service} is the name of the service as stored in the services database (default is ``\com{portserv}''). +\var{file} is the name of a file to store the state of the port server. +Reloading such a file is not currently implemented. +If \var{file} is ``$-$'', standard output is used, if it is ``$--$'', standard error is used. +If \var{file} is not specified, the saving feature is disabled. +If \var{savetime} is specified, it determines how many seconds after a modification +the new state is saved. If it is not specified, the state is saved immediately after each modification. +The save time is intended for port servers that are heavily used in order to reduce file accesses. + +#class UchPortServer + +\section{Testing the port server} + +\com{porttest} is a simple program that can be used to interact with a port server. +Each call to \com{porttest} connects to a port server and executes an operation on it, +depending on the arguments of the command. +The port server that \com{porttest} connects to has name ``\com{portserv}'', +and is on the local host by default. +If the first argument is of the form \com{=}\var{service}, +it connects to a service named \var{service} instead of \com{portserv}. +If the first or second argument is of the form \com{:}\var{host}, +it connects to a portserv on the machine \var{host} instead of the local host. + +The different commands are the following: +\begin{itemize} +\item + \com{porttest [=service] [:host]}\\ + Ask the port server to dump its state on its standard error. +\item + \com{porttest [=service] [:host] quit}\\ + Make the port server exit. +\item + \com{porttest [=service] [:host] key}\\ + Inquire \var{key}. If found, it is printed with its associated address, + else an error message is printed. +\item + \com{porttest [=service] [:host] ?rekey}\\ + Inquire all keys matching \var{rekey}. +\item + \com{porttest [=service] [:host] key portnumber}\\ + Register \var{key} with an address made of port \var{portnumber} on the local host. + \com{porttest} provides its own identification for registration. +\item + \com{porttest [=service] [:host] key -portnumber}\\ + Remove \var{key}, if bound to the address made of port \var{portnumber} on the local host. +\end{itemize} + +\section{Sample usage} + +[to be done] + +\newpage +\appendix + +\chapter{Class List} + +This chapter contains the list of the classes defined in \uch. +The first section contains the inheritance tree of \uch\ classes. +The second section contains for each class the ordered list of its base classes. +The section number indicated after each class refers to the +documentation of that class. The class names that appear in italic +are defined in other libraries. Classes defined in \uch\ but +which are not documented do not appear in the lists. + +\section{Inheritance Tree} +This section contains the set of classes defined in \uch. +Each base class is followed by the indented list of its subclasses. + +\input{inhtree.tex} + +\newpage +\section{Inheritance List} +This section contains the set of classes defined in \uch. +Each class is followed by its base class, recursively. +Thus, from a given class, one can follow the inheritance link +and thus refer to the documentation for the inherited methods. + +\begin{inhlist}{XXXXXXXXXXXXXXXX} +\input{inhlist.tex} +\end{inhlist} + +\begin{theindex} +\indexinc +\end{theindex} + +\end{document} diff --git a/comm/error.cc b/comm/error.cc new file mode 100644 index 0000000..fc02eb9 --- /dev/null +++ b/comm/error.cc @@ -0,0 +1,261 @@ +/* + * The Unix Channel + * + * by Michel Beaudouin-Lafon + * + * Copyright 1990-1993 + * Laboratoire de Recherche en Informatique (LRI) + * + * Error management + * + * $Id$ + * $CurLog$ + */ + +#include "error.h" + +#include +#include +#include +#include + +/*?class UchERROR +The set of global functions described here are designed to handle errors. +An error has a type, an origin, and a message. +The type defines how the error is handled. +The origin and the message are used to generate the error message. +The origin is usually the function name where the error occurred. +A log file may be defined for storing all error messages, in addition to the normal +notification mechanism. + +Handling the error is a three steps process: +first the error string is generated, then the error is emitted by a user-defined error handler, +and finally the way the program continues is determined from the error type. +These types are described by the following values (enumerated type \typ{^{errtype}}): +\index{errtype :: ErrNone}\index{errtype :: ErrLog}\index{errtype :: ErrWarn} +\index{errtype :: ErrAbort}\index{errtype :: ErrExit}\index{errtype :: ErrFatal} +\index{errtype :: ErrUsage} +\begin{itemize} +\item \var{ErrNone} is not an error: the handler is not called and the program continues; +\item \var{ErrLog} is an error that is only logged to the log file: the handler is not called and the program continues; +\item \var{ErrWarn} is a warning: the handler is called and the program then continues; +\item \var{ErrAbort} handles the error and calls \fun{abort}; +\item \var{ErrExit} handles the error and calls \fun{exit(0)}; +\item \var{ErrFatal} handles the error and calls \fun{exit(1)}; +\item \var{ErrUsage} handles the error and calls \fun{exit(1)}; +\var{ErrUsage} is different from \var{ErrFatal} in that the generated error message +is not the same (see \fun{MakeErrorString}). +\end{itemize} + +The type of an error handler is the following: +\index{ErrorHandler} +\begin{ccode} +typedef errtype (*ErrorHandler) (errtype, const char* who, const char* what, const char* msg); +\end{ccode} +?*/ + +// static data for error handling: message table and default handler +// +char* ErrorTable [] = { + "Subclass should implement virtual member", // ErrShouldImplement +}; + +static char ProgName [128], LogFile [128]; +static bool LogOn =FALSE; + +static errtype +DefaultHandler (errtype how, const char* /*who*/, const char* /*what*/, const char* msg) +{ + write (2, msg, strlen (msg)); + return how; +} + +static ErrorHandler Handler = DefaultHandler; + +// main function for handling errors +// +static bool +HandleError (errtype how, const char* who, const char* what) +{ + if (how == ErrNone) + return TRUE; + + char *msg = MakeErrorString (how, who, what); + + if (how >= ErrLog && LogOn) { + LogMessage (msg); + if (how == ErrLog) + return TRUE; + } + + how = (*Handler) (how, who, what, msg); + + switch (how) { + case ErrNone: + return TRUE; + case ErrLog: + case ErrWarn: + break; + case ErrAbort: + write (2, "aborting\n", 9); + abort (); + break; + case ErrExit: + exit (0); + break; + case ErrUsage: + case ErrFatal: + exit (1); + break; + } + return FALSE; +} + +// public functions: +// register program name, cleanup function, log file +// change error handler +// error functions +// + +/*? +Set the program name, that is used to label each output message. +This usually called from \fun{main} with \com{argv[0]} as argument. +?*/ +void +ProgramName (const char* name) +{ + if (name) + strncpy (ProgName, name, sizeof (ProgName) -1); + else + ProgName [0] = 0; +} + +/*? +Set the log file name. +All messages are appended to the logfile. +If \var{reset} is TRUE, the file is cleared. +NOTE : logging is not currently implemented. +?*/ +void +LogfileName (const char* file, bool reset) +{ + if (file) { + strncpy (LogFile, file, sizeof (LogFile) -1); + LogOn = TRUE; + if (reset) { + // to do: clear logfile + } + } else + LogOn = FALSE; +} + +/*?nodoc?*/ +void +CleanUp (CleanUpProc) +{ + // to do: register function +} + +/*? +Append a message to the logfile, if one has been specified. +NOTE : logging is not currently implemented. +?*/ +void +LogMessage (const char*) +{ + // to be done +} + +/*? +Build an error message from the type of error, the place where the error occurred, +and the error message itself, with the following format:\\ +``\com{ [program\_name :] [fatal error in]}\var{who}\com{: }\var{what}''\\ +When \var{how} is \var{ErrUsage}, the format is\\ +``\com{ usage: program\_name}\var{what}''.\\ +The string returned is the address of a static buffer that is overwritten +each time this function is called. +?*/ +char* +MakeErrorString (errtype how, const char* who, const char* what) +{ + static char errmsg [1024]; + + if (how == ErrUsage) + sprintf (errmsg, "usage: %s %s\n", ProgName, what); + else + sprintf (errmsg, "%s%s%s%s: %s\n", + ProgName, + ProgName [0] ? ": " : "", + how >= ErrAbort ? "fatal error in " : "", + who, + what); + return errmsg; +} + +/*? +Change the error handler. +The error handler is called each time an error is emitted. +It is called with four arguments: +the error type, the error source, the error message, +and the error string as returned by \fun{MakeErrorString}. +It should return an error type (usually the first argument) that determines +what happens next: nothing, logging to a file, aborting, or exiting. +The default error handler can be reset by passing the argument 0. +The default error handler writes the error string to the standard error, +and returns its first argument. +?*/ +ErrorHandler +SetErrorHandler (ErrorHandler h) +{ + ErrorHandler old = Handler; + if (! h) + h = DefaultHandler; + Handler = h; + return old; +} + +/*? +Emit an error of type \var{how}, from function \var{who}, with message \var{what}. +?*/ +void +Error (errtype how, const char* who, const char* what) +{ + HandleError (how, who, what); +} + +/*? +Emit a system error of type \var{how}, from function \var{who}. +The message is retrieved from the value of the global variable \var{errno}. +Thus, this function should be used after system calls that set \var{errno}. +\var{exc1} and \var{exc2}, if non negative, are error codes (values of \var{errno}) to be ignored: +if the current error code is one of these, the function returns FALSE. +This is useful for instance to ignore interrupted system calls: +\begin{ccode} + #include + n = read (...); + if (n < 0) + SysError (ErrWarn, "read", EINTR); +\end{ccode} +?*/ +bool +SysError (errtype how, const char* who, int exc1, int exc2) +{ + extern int errno; + extern int sys_nerr; + extern char* sys_errlist []; + char* msg; + char defmsg [80]; + + if (! errno || errno == exc1 || errno == exc2) + return FALSE; + + if (errno >= sys_nerr) + sprintf (msg = defmsg, "system error code %d", errno); + else + if (errno == 0) + msg = "internal error"; + else + msg = sys_errlist [errno]; + return HandleError (how, who, msg); +} + diff --git a/comm/error.h b/comm/error.h new file mode 100644 index 0000000..a0d11ef --- /dev/null +++ b/comm/error.h @@ -0,0 +1,58 @@ +/* + * The Unix Channel + * + * by Michel Beaudouin-Lafon + * + * Copyright 1990-1993 + * Laboratoire de Recherche en Informatique (LRI) + * + * Error management + * + * $Id$ + * $CurLog$ + */ + +#ifndef UchError_H_ +#define UchError_H_ + +#include "global.h" + +#ifdef DOC +// fake class for global functions +class UchError { +}; +#endif + +enum errtype { + ErrNone = -4, // do nothing + ErrLog = -3, // only log to file + ErrWarn = -2, // warn and continue + ErrAbort = -1, // call abort + ErrExit = 0, // exit with code 0 + ErrUsage = 1, // bad usage + ErrFatal = 2 // exit with code != 0 +}; + +enum errcode { + ErrShouldImplement +}; + +extern char *ErrorTable []; + +typedef errtype (*ErrorHandler) (errtype, const char* who, const char* what, const char* msg); +typedef void (*CleanUpProc) (); + +extern void ProgramName (const char*); +extern void LogfileName (const char*, bool = FALSE); +extern void CleanUp (CleanUpProc); +extern ErrorHandler SetErrorHandler (ErrorHandler); + +extern void Error (errtype, const char* who, const char* what); +extern bool SysError (errtype, const char* who, int exc1 = -1, int exc2 = -1); +extern char* MakeErrorString (errtype, const char* who, const char* what); +extern void LogMessage (const char*); + +extern int errno; + +#endif /* UchError_H_ */ + diff --git a/comm/global.h b/comm/global.h new file mode 100644 index 0000000..71244dc --- /dev/null +++ b/comm/global.h @@ -0,0 +1,28 @@ +/* + * The Unix Channel + * + * by Michel Beaudouin-Lafon + * + * Copyright 1990-1993 + * Laboratoire de Recherche en Informatique (LRI) + * + * General definitions + * + * $Id$ + * $CurLog$ + */ + +#ifndef UchGlobal_H_ +#define UchGlobal_H_ + +#ifndef NIL +#define NIL 0 +#define Nil(type) ((type) NIL) +#endif /* NIL */ + +typedef void *pointer; + +#include "ccu/bool.h" +#include "ccu/word.h" + +#endif /* UchGlobal_H_ */ diff --git a/comm/version.h b/comm/version.h new file mode 100644 index 0000000..8870ab1 --- /dev/null +++ b/comm/version.h @@ -0,0 +1,75 @@ +/* + * The Unix Channel + * + * by Michel Beaudouin-Lafon + * + * Copyright 1990-1993 + * Laboratoire de Recherche en Informatique (LRI) + * + * Version and copyright header + * + * $Id$ + * $CurLog$ + */ + +/** + * Copyright 1990, 1991, 1992 Laboratoire de Recherche en Informatique (LRI) + * Copyright 1992, 1993 Centre d'Etudes de la Navigation Aerienne (CENA) + * + * Permission to use, copy, and modify this software and its documentation + * for your own purposes is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and use acknowledge that the software was developed + * by Laboratoire de Recherche en Informatique, Universite de Paris-Sud, + * Orsay, France, and Centre d'Etudes de la Navigation Aerienne, Toulouse, + * France. LRI and CENA make no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + * + * This software or modified versions of this software cannot be + * distributed in source or binary form, nor included into products + * without prior written permission of the author. + * + * LRI AND CENA DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL LRI OR + * CENA BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY + * DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Maintainer: + * + * Stephane Chatty e-mail: chatty@dgac.fr + * CENA + * 7 avenue Edouard Belin phone: +33 62 25 95 42 + * 31055 TOULOUSE - FRANCE fax: +33 62 25 95 99 + * +**/ + +/** +title The Unix Channel +subtitle C++ Communication library +version 3.1 +date January 1993 +copyright (c) 1989-1992 LRI, 1992-1993 CENA +authors Michel Beaudouin-Lafon +| St\'ephane Chatty +address Laboratoire de Recherche en Informatique +| B\^at. 490, Facult\'e d'Orsay +| 91405 Orsay Cedex, France +| and +| Centre d'Etudes de la Navigation A\'erienne +| 7 avenue Edouard Belin +| 31055 TOULOUSE CEDEX, France +e-mail mbl@lri.fr and chatty@dgac.fr +phone +33 1 69 41 69 10 and +33 62 25 95 42 +**/ + +#ifndef UchVersion + +#define UchVersion 3 +#define UchRelease 2 +#define UchPatchLevel 0 + +#endif -- cgit v1.1