/* * 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 "Datagram.h" #include "ccu/List.h" #include "ccu/RegExp.h" #include "ccu/String.h" #include "ccu/Time.h" #include #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"); } } }