/* * The Unix Channel * * by Michel Beaudouin-Lafon * * Copyright 1990-1993 * Laboratoire de Recherche en Informatique (LRI) * * Port server * * $Id$ * $CurLog$ */ #include "PortServerReq.h" #include "PortServer.h" #include "error.h" #include "Multiplexer.h" #include "Datagram.h" #include "SignalHandler.h" #include "ccu/List.h" #include "ccu/RegExp.h" #include "ccu/String.h" #include "ccu/Time.h" #include #include #include #include #include #include #include #ifdef __osf__ extern "C" { #endif #include #ifdef __osf__ } #endif //---------------- 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; UchMultiplexer* Mpx = 0; const char* LoadFile; #ifndef DEFAGENTLIB #define DEFAGENTLIB "/usr/local/lib/agents" #endif char DefLoadFile [256]; #ifndef DEFAGENTBIN #define DEFAGENTBIN "/usr/local/lib/agents/bin" #endif const char* BinDir = 0; //---------------- 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 Service { 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** ParseConfigLine (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 (strchr (" \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 (strchr (" \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 (Service* 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. "-" is default const char* dir = s->RunCmd [1]; if (strcmp (dir, "-") == 0) dir = BinDir; // change directory if (chdir (dir) < 0) SysError (ErrFatal, dir); // exec server if (execvp (s->RunCmd [2], s->RunCmd + 3) < 0) SysError (ErrFatal, s->RunCmd [2]); } s->LastRun = now; } void LoadConfigFile () { 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 = ParseConfigLine (nline); if (! words) { ::Error (ErrWarn, "LoadConfigFile", "incorrect line ignored in file"); delete nline; continue; } Service* s = new Service; char key [256]; // agent:audio sprintf (key, "agent:%s", 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 ("LoadConfigFile", "defined auto-launch server %s dir=%s exec=%s", // s->RunCmd [0], s->RunCmd [1], s->RunCmd [2]); } fclose (f); } void KillProcesses (bool all) { // kill all processes that have been autolaunched, // assuming that their id is their pid. Service* s; #ifndef CPLUS_BUG19 CcuListIterOf iter (ServList); while (s = *++iter) { #else CcuListIter iter (ServList); while (s = (Service*) *++iter) { #endif if ((all || s->AutoLaunch) && s->Ident) { // Log ("KillProcesses", "killing %s pid=%d", s->RunCmd [2], (void*) s->Ident); kill (s->Ident, SIGTERM); } } } Service* RegisterService (const char* key, lword host, sword port, int id) { Service* s = 0; #ifndef CPLUS_BUG19 CcuListIterOf iter (ServList); while (s = *++iter) { #else CcuListIter iter (ServList); while (s = (Service*) *++iter) { #endif if (strcmp (key, s->Key) == 0) break; } if (! s) { s = new Service; 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 RemoveService (const char* key, lword host, sword port, int id) { Service* 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 = (Service*) *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; } Service* InquireService (const char* key) { Service* s; #ifndef CPLUS_BUG19 CcuListIterOf iter (ServList); #else CcuListIter iter (ServList); #endif while (++iter) { #ifndef CPLUS_BUG19 s = *iter; #else s = (Service*) *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; } int Dump () { Service* s; #ifndef CPLUS_BUG19 CcuListIterOf iter (ServList); #else CcuListIter iter (ServList); #endif while (++iter) { #ifndef CPLUS_BUG19 s = *iter; #else s = (Service*) *iter; #endif printf ("<%s> 0x%lx:%d (%d)\n", (const char*) s->Key, s->Host, s->Port, s->Ident); } return 0; } int DumpToFile () { 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]; Service* s; #ifndef CPLUS_BUG19 CcuListIterOf iter (ServList); #else CcuListIter iter (ServList); #endif while (++iter) { #ifndef CPLUS_BUG19 s = *iter; #else s = (Service*) *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 signal handlers // void SigDump (int, int) { Dump (); } void SigDumpFile (int, int) { DumpToFile (); } class RegistrationChannel : public UchDatagram { protected: void Match (const char*); public: RegistrationChannel (sword); void HandleRead (); }; RegistrationChannel :: RegistrationChannel (sword port) : UchDatagram (new UchInetAddress (ANYADDR, port), 0) { } void Modified () { if (! Mod && SaveTime > 0) { Mod++; alarm (SaveTime); return; } if (SaveTime < 0) { Mod++; DumpToFile (); } } void RegistrationChannel :: HandleRead () { UchMsgBuffer buf (256); UchPortServerReq req; if (Trace) printf ("HandleRead\n"); buf.Flush (); int n = Receive (buf); if (n <= 0) { SysError (ErrFatal, "Receive", EINTR); return; } if (! buf.ReadMsg (req)) { ::Error (ErrWarn, "Receive", "could not read message"); return; } // respond switch (req.Type) { case PortServRegister: if (Trace) printf ("register\n"); RegisterService (req.Key, req.Host, req.Port, int (req.Ident)); Modified (); break; case PortServRemove: if (Trace) printf ("remove\n"); RemoveService (req.Key, req.Host, req.Port, int (req.Ident)); Modified (); break; case PortServInquire: { if (Trace) printf ("inquire\n"); UchPortServerReq ans; Service* inq = InquireService (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.WriteMsg (ans); if (Reply (buf) < 0) SysError (ErrWarn, "reply"); } break; case PortServMatch: if (Trace) printf ("match\n"); Match (req.Key); break; case PortServDump: if (Trace) printf ("dump\n"); Dump (); break; case PortServQuit: if (Trace) printf ("quit\n"); if (SaveTime) DumpToFile (); KillProcesses (true); Mpx->Close (); break; default: if (Trace) printf ("unknown\n"); ::Error (ErrWarn, "Receive", "unknown request"); } } void RegistrationChannel :: Match (const char* key) { UchMsgBuffer buf (256); UchPortServerReq ans (PortServMatch); int nb = 0; CcuRegExp re (key); if (re.Compile ()) { Service* s; #ifndef CPLUS_BUG19 CcuListIterOf iter (ServList); while (++iter) { s = *iter; #else CcuListIter iter (ServList); while (++iter) { s = (Service*) *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.WriteMsg (ans); Reply (buf); nb++; } } } else nb = -1; ans.Type = PortServEndMatch; ans.Ident = nb; ans.SetKey (0); buf.WriteMsg (ans); Reply (buf); } //---------------- BroadcastChannel class BroadcastChannel : public UchDatagram { protected: sword p; pUchAddress bcast; UchMsgBuffer registerBuf; UchMsgBuffer removeBuf; UchMsgBuffer answerBuf; static lword BroadCastAddress (); void Answer (); public: BroadcastChannel (sword, UchInetAddress*); bool SetUp (); void HandleRead (); void RegisterMe () { printf ("RegisterMe: %d\n", Send (registerBuf, *bcast, true /*peek*/)); } void RemoveMe () { Send (removeBuf, *bcast, true /*peek*/); printf ("sending remove\n"); } }; lword BroadcastChannel :: BroadCastAddress () { // *** can't get broadcast to work on DECstation !!! char* bcast = getenv ("BroadcastChannel"); unsigned int b1, b2, b3, b4; sscanf (bcast, "%d.%d.%d.%d", &b1, &b2, &b3, &b4); printf ("bacst = %d.%d.%d.%d\n", b1, b2, b3, b4); // return (b1 << 24) | (b2 << 16) | (b3 << 8) | b4; return (((b1 * 256U) + b2) * 256U + b3) * 256U + b4; } BroadcastChannel :: BroadcastChannel (sword port, UchInetAddress* reg_addr) : UchDatagram (new UchInetAddress (ANYADDR, port), 0) { lword broadcast = BroadCastAddress (); bcast = new UchInetAddress (broadcast, port); const char* l = getlogin (); if (! l) l = cuserid (0); if (! l) ::Error (ErrFatal, "BroadcastChannel", "getlogin failed"); /* prepare answerBuf for future requests */ char key [32]; sprintf (key, "BroadcastChannel:%s:", l); lword reg_host = reg_addr->Host (); sword reg_port = reg_addr->Port (); int pid = getpid (); UchPortServerReq reg (PortServRegister, reg_host, reg_port, pid, key); registerBuf.WriteMsg (reg); UchPortServerReq rem (PortServRemove, reg_host, reg_port, pid, key); removeBuf.WriteMsg (rem); UchPortServerReq ans (PortServAnswer, reg_host, reg_port, pid, key); answerBuf.WriteMsg (ans); } bool BroadcastChannel :: SetUp () { int on = 1; if (! UchDatagram::Setup ()) return false; if (setsockopt (FilDes (), SOL_SOCKET, SO_BROADCAST, (char*) &on, sizeof (on)) < 0) return false; return true; } void BroadcastChannel :: Answer () { int res = Send (answerBuf, *bcast, true /*peek*/); printf ("AnswerMe: %d\n", res); } void BroadcastChannel :: HandleRead () { // handle requests // UchMsgBuffer buf (256); UchPortServerReq req; buf.Flush (); int n = Receive (buf); if (n <= 0) { SysError (ErrFatal, "BroadcastChannel", EINTR); return; } if (! buf.ReadMsg (req)) { ::Error (ErrWarn, "BroadcastChannel", "could not read message"); return; } // respond switch (req.Type) { case PortServRegister: RegisterService (req.Key, req.Host, req.Port, int (req.Ident)); Modified (); printf ("received registration %s 0x%x %d %d\n", (const char*) req.Key, req.Host, req.Port, req.Ident); // when somebody else registers, we send our address Answer (); break; case PortServInquire: printf ("received inquiry\n"); Answer (); break; case PortServAnswer: RegisterService (req.Key, req.Host, req.Port, int (req.Ident)); Modified (); printf ("received answer %s 0x%x %d %d\n", (const char*) req.Key, req.Host, req.Port, req.Ident); break; case PortServRemove: RemoveService (req.Key, req.Host, req.Port, int (req.Ident)); Modified (); break; default: ::Error (ErrWarn, "main", "unknown request"); } } // main // sword GetPortNumber (const char* servname, int dflt) { /* first try services database */ struct servent* service = getservbyname (servname, 0); if (service) return ntohs (service->s_port); /* then, environment variable */ char* portno = getenv (servname); if (portno) { int servport = atoi (portno); return servport; } /* then default value (may be zero)*/ return dflt; } 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]); LoadFile = getenv ("AGENTMGRCF"); 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: portService -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); } if (! LoadFile) { const char* agentlib = getenv ("AGENTLIB"); if (!agentlib) agentlib = DEFAGENTLIB; strcpy (DefLoadFile, agentlib); strcat (DefLoadFile, "/agentman.cf"); if (access (DefLoadFile, R_OK) == 0) LoadFile = DefLoadFile; } // load config file // if (! BinDir) { BinDir = getenv ("AGENTBIN"); if (! BinDir) BinDir = DEFAGENTBIN; } if (LoadFile) LoadConfigFile (); UchMultiplexer mpx; Mpx = &mpx; // get port // sword reg_port = GetPortNumber (servname, 3003); if (! reg_port) ::Error (ErrFatal, servname, "service not available"); // open dgram socket // if (Trace) printf ("opening\n"); RegistrationChannel reg_chan (reg_port); if (! reg_chan.Setup ()) SysError (ErrFatal, "main, creating portserv"); reg_chan.SetMode (IORead); mpx.Add (®_chan); // open dgram broadcast socket for communicating between port servers // sword bc_port = GetPortNumber ("superman", 0); BroadcastChannel* bc_chan = 0; if (bc_port) { bc_chan = new BroadcastChannel (bc_port, (UchInetAddress*) reg_chan.BoundTo ()); if (! bc_chan->Setup ()) { SysError (ErrWarn, "main, creating broadcast channel"); delete bc_chan; bc_chan = 0; } else { ::Error (ErrLog, "main", "created broadcast channel"); bc_chan->SetMode (IORead); bc_chan->RegisterMe (); mpx.Add (bc_chan); } } // auto-dump stuff // UchSignalHandler h (mpx, SigHup, SigDump); if (SaveTime > 0) new UchSignalHandler (mpx, SigAlrm, SigDumpFile); // run the multiplexer // mpx.LoopScan (); // when quitting, notify other agent mgrs // if (bc_chan) bc_chan->RemoveMe (); return 0; }