/* * 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 #if 1 #include #endif #ifdef __osf__ extern "C" { #endif #include #ifdef __osf__ } #endif #ifdef __osf__ extern "C" { unsigned short htons (unsigned short); unsigned short ntohs (unsigned short); int gethostname (char*, int); } #endif #if defined(sun) && defined(__svr4__) extern "C" { int gethostname (char*, int); } #endif #define DEFKEY "%s:" // *** horrible patch bool PortServerInquireSysError = false; /*?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 portno = 3003; // ::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.WriteMsg (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); } static bool expired = false; static void Expire (Millisecond) { expired = true; } /*? 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) { // *** horrible patch PortServerInquireSysError = false; UchPortServerReq req (PortServInquire, 0, 0, 0, MakeKey (key)); SendRequest (Serv, req); UchMsgBuffer buf (256); expired = false; CcuTimer timer (timeout * 1000, Expire); int n = Serv->Receive (buf); if (expired) { ::Error (ErrWarn, "UchPortServer::Inquire", "timeout"); return 0; } if (n <= 0) { SysError (ErrWarn, "Inquire"); // *** horrible patch PortServerInquireSysError = true; return 0; } if (! buf.ReadMsg (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 (); expired = false; CcuTimer timer (timeout * 1000, Expire); int n = Serv->Receive (buf); if (expired) { ::Error (ErrWarn, "UchPortServer::Match", "timeout"); return 0; } if (n <= 0) { SysError (ErrWarn, "Match"); break; } if (! buf.ReadMsg (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 0 if (gethostname (p, sizeof (buffer) - (p - buffer)) < 0) { SysError (ErrWarn, "UchPortServer::MakeKey"); strcpy (p, "???"); } #else struct utsname un; if (uname (&un) < 0) { SysError (ErrWarn, "UchPortServer::MakeKey"); strcpy (p, "???"); } else strncpy (p, un.nodename, sizeof (buffer) - (p - buffer)); #endif 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) { #if 0 UchPortServer ps (service); #else UchPortServer ps ("agentman"); #endif char key [256]; #if 0 strcpy (key, DEFKEY); #else strcpy (key, service); strcpy (key, name); #endif 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) { #if 0 UchPortServer ps (service); #else UchPortServer ps ("agentman"); #endif char key [256]; #if 0 strcpy (key, DEFKEY); #else strcpy (key, service); strcpy (key, name); #endif 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) { #if 1 UchPortServer ps (service, (host && *host) ? host : 0); #else UchPortServer ps ("agentman", (host && *host) ? host : 0); #endif char key [256]; #if 0 strcpy (key, DEFKEY); #else strcpy (key, service); strcpy (key, name); #endif strcat (key, name); return ps.Inquire (key); }