1 /*
2  * cli.cxx
3  *
4  * Command line interpreter
5  *
6  * Copyright (C) 2006-2008 Post Increment
7  *
8  * The contents of this file are subject to the Mozilla Public License
9  * Version 1.0 (the "License"); you may not use this file except in
10  * compliance with the License. You may obtain a copy of the License at
11  * http://www.mozilla.org/MPL/
12  *
13  * Software distributed under the License is distributed on an "AS IS"
14  * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
15  * the License for the specific language governing rights and limitations
16  * under the License.
17  *
18  * The Original Code is WOpenMCU
19  *
20  * The Initial Developer of the Original Code is Post Increment
21  *
22  * Contributor(s): Craig Southeren (craigs@postincrement.com)
23  *                 Robert Jongbloed (robertj@voxlucida.com.au)
24  *
25  * Portions of this code were written by Post Increment (http://www.postincrement.com)
26  * with the assistance of funding from US Joint Forces Command Joint Concept Development &
27  * Experimentation (J9) http://www.jfcom.mil/about/abt_j9.htm
28  *
29  * Further assistance for enhancements from Imagicle spa
30  *
31  * $Revision: 24931 $
32  * $Author: csoutheren $
33  * $Date: 2010-12-07 20:01:15 -0600 (Tue, 07 Dec 2010) $
34  */
35 
36 #include <ptlib.h>
37 #include <ptclib/cli.h>
38 #include <ptclib/telnet.h>
39 
40 
41 ///////////////////////////////////////////////////////////////////////////////
42 
Context(PCLI & cli)43 PCLI::Context::Context(PCLI & cli)
44   : m_cli(cli)
45   , m_ignoreNextEOL(false)
46   , m_thread(NULL)
47   , m_state(cli.GetUsername().IsEmpty() ? (cli.GetPassword().IsEmpty() ? e_CommandEntry : e_Password) : e_Username)
48 {
49 }
50 
51 
~Context()52 PCLI::Context::~Context()
53 {
54   Stop();
55   delete m_thread;
56 }
57 
58 
Write(const void * buf,PINDEX len)59 PBoolean PCLI::Context::Write(const void * buf, PINDEX len)
60 {
61   if (m_cli.GetNewLine().IsEmpty())
62     return PIndirectChannel::Write(buf, len);
63 
64   const char * newLinePtr = m_cli.GetNewLine();
65   PINDEX newLineLen = m_cli.GetNewLine().GetLength();
66 
67   PINDEX written = 0;
68 
69   const char * str = (const char *)buf;
70   const char * nextline;
71   while (len > 0 && (nextline = strchr(str, '\n')) != NULL) {
72     PINDEX lineLen = nextline - str;
73 
74     if (!PIndirectChannel::Write(str, lineLen))
75       return false;
76 
77     written += GetLastWriteCount();
78 
79     if (!PIndirectChannel::Write(newLinePtr, newLineLen))
80       return false;
81 
82     written += GetLastWriteCount();
83 
84     len -= lineLen+1;
85     str = nextline+1;
86   }
87 
88   if (!PIndirectChannel::Write(str, len))
89     return false;
90 
91   lastWriteCount = written + GetLastWriteCount();
92   return true;
93 }
94 
95 
Start()96 bool PCLI::Context::Start()
97 {
98   if (!IsOpen()) {
99     PTRACE(2, "PCLI\tCannot start context, not open.");
100     return false;
101   }
102 
103   if (m_thread == NULL)
104     m_thread = PThread::Create(PCREATE_NOTIFIER(ThreadMain), "CLI Context");
105 
106   return true;
107 }
108 
109 
Stop()110 void PCLI::Context::Stop()
111 {
112   Close();
113 
114   if (m_thread != NULL && PThread::Current() != m_thread) {
115     m_thread->WaitForTermination(10000);
116     delete m_thread;
117     m_thread = NULL;
118   }
119 }
120 
121 
OnStart()122 void PCLI::Context::OnStart()
123 {
124   WritePrompt();
125 }
126 
127 
OnStop()128 void PCLI::Context::OnStop()
129 {
130 }
131 
132 
WritePrompt()133 bool PCLI::Context::WritePrompt()
134 {
135   switch (m_state) {
136     case e_Username :
137       if (!m_cli.GetUsername().IsEmpty())
138         return WriteString(m_cli.GetUsernamePrompt());
139       // Do next case
140 
141     case e_Password :
142       SetLocalEcho(false);
143       if (!m_cli.GetPassword().IsEmpty())
144         return WriteString(m_cli.GetPasswordPrompt());
145       // Do next case
146 
147     default :
148       return WriteString(m_cli.GetPrompt());
149   }
150 }
151 
152 
ReadAndProcessInput()153 bool PCLI::Context::ReadAndProcessInput()
154 {
155   if (!IsOpen())
156     return false;
157 
158   int ch = ReadChar();
159   if (ch < 0) {
160     PTRACE(2, "PCLI\tRead error: " << GetErrorText(PChannel::LastReadError));
161     return false;
162   }
163 
164   return ProcessInput(ch);
165 }
166 
ProcessInput(const PString & str)167 bool PCLI::Context::ProcessInput(const PString & str)
168 {
169   PStringArray lines = str.Lines();
170 
171   PINDEX i;
172   for (i = 0; i < lines.GetSize(); ++i) {
173     PINDEX j;
174     PString & line = lines[i];
175     for (j = 0; j < line.GetLength(); ++j)
176       if (!ProcessInput(line[j]))
177         return false;
178     if (!ProcessInput('\n'))
179       return false;
180   }
181   return true;
182 }
183 
ProcessInput(int ch)184 bool PCLI::Context::ProcessInput(int ch)
185 {
186   if (ch != '\n' && ch != '\r') {
187     if (m_cli.GetEditCharacters().Find((char)ch) != P_MAX_INDEX) {
188       if (!m_commandLine.IsEmpty()) {
189         m_commandLine.Delete(m_commandLine.GetLength()-1, 1);
190         if (m_cli.GetRequireEcho() && m_state != e_Password) {
191           if (!WriteString("\b \b"))
192             return false;
193         }
194       }
195     }
196     else if (ch > 0 && ch < 256 && isprint(ch)) {
197       m_commandLine += (char)ch;
198 
199       if (m_cli.GetRequireEcho() && m_state != e_Password) {
200         if (!WriteChar(ch))
201           return false;
202       }
203     }
204 
205     m_ignoreNextEOL = false;
206     return true;
207   }
208 
209   if (m_ignoreNextEOL) {
210     m_ignoreNextEOL = false;
211     return true;
212   }
213 
214   m_ignoreNextEOL = true;
215 
216   switch (m_state) {
217     case e_Username :
218       if (m_cli.GetPassword().IsEmpty()) {
219         if (m_cli.OnLogIn(m_commandLine, PString::Empty()))
220           m_state = e_CommandEntry;
221       }
222       else {
223         m_enteredUsername = m_commandLine;
224         m_state = e_Password;
225       }
226       break;
227 
228     case e_Password :
229       if (!WriteString(m_cli.GetNewLine()))
230         return false;
231 
232       if (m_cli.OnLogIn(m_enteredUsername, m_commandLine))
233         m_state = e_CommandEntry;
234       else
235         m_state = m_cli.GetUsername().IsEmpty() ? (m_cli.GetPassword().IsEmpty() ? e_CommandEntry : e_Password) : e_Username;
236 
237       SetLocalEcho(m_state != e_Password);
238       m_enteredUsername.MakeEmpty();
239       break;
240 
241     default :
242       OnCompletedLine();
243   }
244 
245   m_commandLine.MakeEmpty();
246   return WritePrompt();
247 }
248 
249 
CheckInternalCommand(const PCaselessString & line,const PCaselessString & cmds)250 static bool CheckInternalCommand(const PCaselessString & line, const PCaselessString & cmds)
251 {
252   PINDEX pos = cmds.Find(line);
253   if (pos == P_MAX_INDEX)
254     return false;
255   char terminator = cmds[pos + line.GetLength()];
256   return terminator == '\n' || terminator == '\0';
257 }
258 
259 
OnCompletedLine()260 void PCLI::Context::OnCompletedLine()
261 {
262   PCaselessString line = m_commandLine.Trim();
263   if (line.IsEmpty())
264     return;
265 
266   PTRACE(4, "PCLI\tProcessing command line \"" << line << '"');
267 
268   if (CheckInternalCommand(line, m_cli.GetExitCommand())) {
269     Stop();
270     return;
271   }
272 
273   if (line.NumCompare(m_cli.GetRepeatCommand()) == EqualTo) {
274     if (m_commandHistory.IsEmpty()) {
275       *this << m_cli.GetNoHistoryError() << endl;
276       return;
277     }
278 
279     line = m_commandHistory.back();
280   }
281 
282   if (CheckInternalCommand(line, m_cli.GetHistoryCommand())) {
283     unsigned cmdNum = 1;
284     for (PStringList::iterator cmd = m_commandHistory.begin(); cmd != m_commandHistory.end(); ++cmd)
285       *this << cmdNum++ << ' ' << *cmd << '\n';
286     flush();
287     return;
288   }
289 
290   if (line.NumCompare(m_cli.GetHistoryCommand()) == EqualTo) {
291     PINDEX cmdNum = line.Mid(m_cli.GetHistoryCommand().GetLength()).AsUnsigned();
292     if (cmdNum <= 0 || cmdNum > m_commandHistory.GetSize()) {
293       *this << m_cli.GetNoHistoryError() << endl;
294       return;
295     }
296 
297     line = m_commandHistory[cmdNum-1];
298   }
299 
300   if (CheckInternalCommand(line, m_cli.GetHelpCommand()))
301     m_cli.ShowHelp(*this);
302   else {
303     Arguments args(*this, line);
304     m_state = e_ProcessingCommand;
305     m_cli.OnReceivedLine(args);
306     m_state = e_CommandEntry;
307   }
308 
309   m_commandHistory += line;
310 }
311 
312 
ThreadMain(PThread &,INT)313 void PCLI::Context::ThreadMain(PThread &, INT)
314 {
315   PTRACE(4, "PCLI\tContext thread started");
316 
317   if (IsOpen()) {
318     OnStart();
319     while (ReadAndProcessInput())
320       ;
321     OnStop();
322   }
323 
324   PTRACE(4, "PCLI\tContext thread ended");
325 }
326 
327 
328 ///////////////////////////////////////////////////////////////////////////////
329 
Arguments(Context & context,const PString & rawLine)330 PCLI::Arguments::Arguments(Context & context, const PString & rawLine)
331   : PArgList(rawLine)
332   , m_context(context)
333 {
334 }
335 
336 
WriteUsage()337 PCLI::Context & PCLI::Arguments::WriteUsage()
338 {
339   if (!m_usage.IsEmpty())
340     m_context << m_context.GetCLI().GetCommandUsagePrefix() << m_usage << endl;
341   return m_context;
342 }
343 
344 
WriteError(const PString & error)345 PCLI::Context & PCLI::Arguments::WriteError(const PString & error)
346 {
347   m_context << m_command << m_context.GetCLI().GetCommandErrorPrefix();
348   if (!error.IsEmpty())
349     m_context << error << endl;
350   return m_context;
351 }
352 
353 
354 ///////////////////////////////////////////////////////////////////////////////
355 
PCLI(const char * prompt)356 PCLI::PCLI(const char * prompt)
357   : m_newLine("\r\n")
358   , m_requireEcho(false)
359   , m_editCharacters("\b\x7f")
360   , m_prompt(prompt != NULL ? prompt : "CLI> ")
361   , m_usernamePrompt("Username: ")
362   , m_passwordPrompt("Password: ")
363   , m_exitCommand("exit\nquit")
364   , m_helpCommand("?\nhelp")
365   , m_helpOnHelp("Use ? or 'help' to display help\n"
366                  "Use ! to list history of commands\n"
367                  "Use !n to repeat the n'th command\n"
368                  "Use !! to repeat last command\n"
369                  "\n"
370                  "Command available are:")
371   , m_repeatCommand("!!")
372   , m_historyCommand("!")
373   , m_noHistoryError("No command history")
374   , m_commandUsagePrefix("Usage: ")
375   , m_commandErrorPrefix(": error: ")
376   , m_unknownCommandError("Unknown command")
377 {
378 }
379 
380 
~PCLI()381 PCLI::~PCLI()
382 {
383   Stop();
384 }
385 
386 
Start(bool runInBackground)387 bool PCLI::Start(bool runInBackground)
388 {
389   if (runInBackground) {
390     PTRACE(4, "PCLI\tStarting background contexts");
391     m_contextMutex.Wait();
392     for (ContextList_t::iterator iter = m_contextList.begin(); iter != m_contextList.end(); ++iter)
393       (*iter)->Start();
394     m_contextMutex.Signal();
395     return true;
396   }
397 
398   Context * context = StartForeground();
399   if (context == NULL)
400     return false;
401 
402   return RunContext(context);
403 }
404 
405 
StartForeground()406 PCLI::Context * PCLI::StartForeground()
407 {
408   if (m_contextList.size() != 1) {
409     PTRACE(2, "PCLI\tCan only start in foreground if have one context.");
410     return NULL;
411   }
412 
413   Context * context = m_contextList.front();
414   if (!context->IsOpen()) {
415     PTRACE(2, "PCLI\tCannot start foreground processing, context not open.");
416     return NULL;
417   }
418 
419   context->OnStart();
420 
421   return context;
422 }
423 
424 
RunContext(Context * context)425 bool PCLI::RunContext(Context * context)
426 {
427   while (context->ReadAndProcessInput())
428     ;
429   return true;
430 }
431 
432 
Stop()433 void PCLI::Stop()
434 {
435   m_contextMutex.Wait();
436   for (ContextList_t::iterator iter = m_contextList.begin(); iter != m_contextList.end(); ++iter)
437     (*iter)->Stop();
438   m_contextMutex.Signal();
439 
440   GarbageCollection();
441 }
442 
443 
StartContext(PChannel * channel,bool autoDelete,bool runInBackground)444 bool PCLI::StartContext(PChannel * channel, bool autoDelete, bool runInBackground)
445 {
446   PCLI::Context * context = AddContext();
447   if (context == NULL)
448     return false;
449 
450   if (!context->Open(channel, autoDelete)) {
451     PTRACE(2, "PCLI\tCould not open context: " << context->GetErrorText());
452     return false;
453   }
454 
455   if (runInBackground)
456     return context->Start();
457 
458   return true;
459 }
460 
461 
StartContext(PChannel * readChannel,PChannel * writeChannel,bool autoDeleteRead,bool autoDeleteWrite,bool runInBackground)462 bool PCLI::StartContext(PChannel * readChannel,
463                         PChannel * writeChannel,
464                         bool autoDeleteRead,
465                         bool autoDeleteWrite,
466                         bool runInBackground)
467 {
468   PCLI::Context * context = AddContext();
469   if (context == NULL)
470     return false;
471 
472   if (!context->Open(readChannel, writeChannel, autoDeleteRead, autoDeleteWrite)) {
473     PTRACE(2, "PCLI\tCould not open context: " << context->GetErrorText());
474     return false;
475   }
476 
477   if (runInBackground)
478     return context->Start();
479 
480   return true;
481 }
482 
483 
CreateContext()484 PCLI::Context * PCLI::CreateContext()
485 {
486   return new Context(*this);
487 }
488 
489 
AddContext(Context * context)490 PCLI::Context * PCLI::AddContext(Context * context)
491 {
492   if (context == NULL) {
493     context = CreateContext();
494     if (context == NULL) {
495       PTRACE(2, "PCLI\tCould not create a context!");
496       return context;
497     }
498   }
499 
500   m_contextMutex.Wait();
501   m_contextList.push_back(context);
502   m_contextMutex.Signal();
503 
504   return context;
505 }
506 
507 
RemoveContext(Context * context)508 void PCLI::RemoveContext(Context * context)
509 {
510   if (!PAssert(context != NULL, PInvalidParameter))
511     return;
512 
513   context->Close();
514 
515   m_contextMutex.Wait();
516 
517   for (ContextList_t::iterator iter = m_contextList.begin(); iter != m_contextList.end(); ++iter) {
518     if (*iter == context) {
519       delete context;
520       m_contextList.erase(iter);
521       break;
522     }
523   }
524 
525   m_contextMutex.Signal();
526 }
527 
528 
GarbageCollection()529 void PCLI::GarbageCollection()
530 {
531   m_contextMutex.Wait();
532 
533   ContextList_t::iterator iter = m_contextList.begin();
534   while (iter != m_contextList.end()) {
535     Context * context = *iter;
536     if (context->IsProcessingCommand() || context->IsOpen())
537       ++iter;
538     else {
539       RemoveContext(context);
540       iter = m_contextList.begin();
541     }
542   }
543 
544   m_contextMutex.Signal();
545 }
546 
547 
OnReceivedLine(Arguments & args)548 void PCLI::OnReceivedLine(Arguments & args)
549 {
550   for (PINDEX nesting = 1; nesting <= args.GetCount(); ++nesting) {
551     PString names;
552     for (PINDEX i = 0; i < nesting; ++i)
553       names &= args[i];
554 
555     CommandMap_t::iterator cmd = m_commands.find(names);
556     if (cmd != m_commands.end()) {
557       args.Shift(nesting);
558       args.m_command = cmd->first;
559       args.m_usage = cmd->second.m_usage;
560       cmd->second.m_notifier(args, 0);
561       return;
562     }
563   }
564 
565   args.GetContext() << GetUnknownCommandError() << endl;
566 }
567 
568 
OnLogIn(const PString & username,const PString & password)569 bool PCLI::OnLogIn(const PString & username, const PString & password)
570 {
571   return m_username == username && m_password == password;
572 }
573 
574 
Broadcast(const PString & message) const575 void PCLI::Broadcast(const PString & message) const
576 {
577   for (ContextList_t::const_iterator iter = m_contextList.begin(); iter != m_contextList.end(); ++iter)
578     **iter << message << endl;
579   PTRACE(4, "PCLI\tBroadcast \"" << message << '"');
580 }
581 
582 
SetCommand(const char * command,const PNotifier & notifier,const char * help,const char * usage)583 bool PCLI::SetCommand(const char * command, const PNotifier & notifier, const char * help, const char * usage)
584 {
585   if (!PAssert(command != NULL && *command != '\0' && !notifier.IsNULL(), PInvalidParameter))
586     return false;
587 
588   bool good = true;
589 
590   PStringArray synonymArray = PString(command).Lines();
591   for (PINDEX s = 0; s < synonymArray.GetSize(); ++s) {
592     // Normalise command to remove any duplicate spaces, should only
593     // have one as in " conf  show   members   " -> "conf show members"
594     PStringArray nameArray = synonymArray[s].Tokenise(' ', false);
595     PString names;
596     for (PINDEX n = 0; n < nameArray.GetSize(); ++n)
597       names &= nameArray[n];
598 
599     if (m_commands.find(names) != m_commands.end())
600       good = false;
601     else {
602       InternalCommand & cmd = m_commands[names];
603       cmd.m_notifier = notifier;
604       cmd.m_help = help;
605       if (usage != NULL && *usage != '\0')
606         cmd.m_usage = names & usage;
607     }
608   }
609 
610   return good;
611 }
612 
613 
ShowHelp(Context & context)614 void PCLI::ShowHelp(Context & context)
615 {
616   PINDEX i;
617   CommandMap_t::const_iterator cmd;
618 
619   PINDEX maxCommandLength = GetHelpCommand().GetLength();
620   for (cmd = m_commands.begin(); cmd != m_commands.end(); ++cmd) {
621     PINDEX len = cmd->first.GetLength();
622     if (maxCommandLength < len)
623       maxCommandLength = len;
624   }
625 
626   PStringArray lines = GetHelpOnHelp().Lines();
627   for (i = 0; i < lines.GetSize(); ++i)
628     context << lines[i] << '\n';
629 
630   for (cmd = m_commands.begin(); cmd != m_commands.end(); ++cmd) {
631     if (cmd->second.m_help.IsEmpty() && cmd->second.m_usage.IsEmpty())
632       context << cmd->first;
633     else {
634       context << left << setw(maxCommandLength) << cmd->first << "   ";
635 
636       if (cmd->second.m_help.IsEmpty())
637         context << GetCommandUsagePrefix(); // Earlier conditon says must have usage
638       else {
639         lines = cmd->second.m_help.Lines();
640         context << lines[0];
641         for (i = 1; i < lines.GetSize(); ++i)
642           context << '\n' << setw(maxCommandLength+3) << ' ' << lines[i];
643       }
644 
645       lines = cmd->second.m_usage.Lines();
646       for (i = 0; i < lines.GetSize(); ++i)
647         context << '\n' << setw(maxCommandLength+5) << ' ' << lines[i];
648     }
649     context << '\n';
650   }
651 
652   context.flush();
653 }
654 
655 
656 ///////////////////////////////////////////////////////////////////////////////
657 
PCLIStandard(const char * prompt)658 PCLIStandard::PCLIStandard(const char * prompt)
659   : PCLI(prompt)
660 {
661 }
662 
663 
Start(bool runInBackground)664 bool PCLIStandard::Start(bool runInBackground)
665 {
666   if (m_contextList.empty())
667     StartContext(new PConsoleChannel(PConsoleChannel::StandardInput),
668                  new PConsoleChannel(PConsoleChannel::StandardOutput),
669                  true, true, runInBackground);
670   return PCLI::Start(runInBackground);
671 }
672 
StartForeground()673 PCLI::Context * PCLIStandard::StartForeground()
674 {
675   if (m_contextList.empty())
676     StartContext(new PConsoleChannel(PConsoleChannel::StandardInput),
677                  new PConsoleChannel(PConsoleChannel::StandardOutput),
678                  true, true, false);
679   return PCLI::StartForeground();
680 }
681 
682 
683 ///////////////////////////////////////////////////////////////////////////////
684 
PCLISocket(WORD port,const char * prompt,bool singleThreadForAll)685 PCLISocket::PCLISocket(WORD port, const char * prompt, bool singleThreadForAll)
686   : PCLI(prompt)
687   , m_singleThreadForAll(singleThreadForAll)
688   , m_listenSocket(port)
689   , m_thread(NULL)
690 {
691 }
692 
693 
~PCLISocket()694 PCLISocket::~PCLISocket()
695 {
696   Stop();
697   delete m_thread;
698 }
699 
700 
Start(bool runInBackground)701 bool PCLISocket::Start(bool runInBackground)
702 {
703   if (!Listen())
704     return false;
705 
706   if (runInBackground) {
707     if (m_thread != NULL)
708       return true;
709     m_thread = PThread::Create(PCREATE_NOTIFIER(ThreadMain), "CLI Server");
710     return m_thread != NULL;
711   }
712 
713   while (m_singleThreadForAll ? HandleSingleThreadForAll() : HandleIncoming())
714     GarbageCollection();
715   return true;
716 }
717 
718 
Stop()719 void PCLISocket::Stop()
720 {
721   m_listenSocket.Close();
722 
723   if (m_thread != NULL && PThread::Current() != m_thread) {
724     m_thread->WaitForTermination(10000);
725     delete m_thread;
726     m_thread = NULL;
727   }
728 
729   PCLI::Stop();
730 }
731 
732 
AddContext(Context * context)733 PCLI::Context * PCLISocket::AddContext(Context * context)
734 {
735   context = PCLI::AddContext(context);
736 
737   PTCPSocket * socket = dynamic_cast<PTCPSocket *>(context->GetReadChannel());
738   if (socket != NULL) {
739     m_contextMutex.Wait();
740     m_contextBySocket[socket] = context;
741     m_contextMutex.Signal();
742   }
743 
744   return context;
745 }
746 
747 
RemoveContext(Context * context)748 void PCLISocket::RemoveContext(Context * context)
749 {
750   if (context == NULL)
751     return;
752 
753   PTCPSocket * socket = dynamic_cast<PTCPSocket *>(context->GetReadChannel());
754   if (socket != NULL) {
755     m_contextMutex.Wait();
756 
757     ContextMap_t::iterator iter = m_contextBySocket.find(socket);
758     if (iter != m_contextBySocket.end())
759       m_contextBySocket.erase(iter);
760 
761     m_contextMutex.Signal();
762   }
763 
764   PCLI::RemoveContext(context);
765 }
766 
767 
Listen(WORD port)768 bool PCLISocket::Listen(WORD port)
769 {
770   if (!m_listenSocket.Listen(5, port, PSocket::CanReuseAddress)) {
771     PTRACE(2, "PCLI\tCannot open PCLI socket on port " << port
772            << ", error: " << m_listenSocket.GetErrorText());
773     return false;
774   }
775 
776   PTRACE(4, "PCLI\tCLI socket opened on port " << m_listenSocket.GetPort());
777   return true;
778 }
779 
780 
ThreadMain(PThread &,INT)781 void PCLISocket::ThreadMain(PThread &, INT)
782 {
783   PTRACE(4, "PCLI\tServer thread started on port " << GetPort());
784 
785   while (m_singleThreadForAll ? HandleSingleThreadForAll() : HandleIncoming())
786     GarbageCollection();
787 
788   PTRACE(4, "PCLI\tServer thread ended for port " << GetPort());
789 }
790 
791 
HandleSingleThreadForAll()792 bool PCLISocket::HandleSingleThreadForAll()
793 {
794   // create list of listening sockets
795   PSocket::SelectList readList;
796   readList += m_listenSocket;
797 
798   m_contextMutex.Wait();
799   for (ContextMap_t::iterator iter = m_contextBySocket.begin(); iter != m_contextBySocket.end(); ++iter)
800     readList += *iter->first;
801   m_contextMutex.Signal();
802 
803   // wait for something to become available
804   if (PIPSocket::Select(readList) == PChannel::NoError) {
805     // process sockets
806     for (PSocket::SelectList::iterator socket = readList.begin(); socket != readList.end(); ++socket) {
807       if (&*socket == &m_listenSocket)
808         HandleIncoming();
809       else {
810         ContextMap_t::iterator iterContext = m_contextBySocket.find(&*socket);
811         if (iterContext != m_contextBySocket.end()) {
812           char buffer[1024];
813           if (socket->Read(buffer, sizeof(buffer)-1)) {
814             PINDEX count = socket->GetLastReadCount();
815             for (PINDEX i = 0; i < count; ++i) {
816               if (!iterContext->second->ProcessInput(buffer[i]))
817                 socket->Close();
818             }
819           }
820           else
821             socket->Close();
822         }
823       }
824     }
825   }
826 
827   return m_listenSocket.IsOpen();
828 }
829 
830 
HandleIncoming()831 bool PCLISocket::HandleIncoming()
832 {
833   PTCPSocket * socket = CreateSocket();
834   if (socket->Accept(m_listenSocket)) {
835     PTRACE(3, "PCLI\tIncoming connection from " << socket->GetPeerHostName());
836     Context * context = CreateContext();
837     if (context != NULL && context->Open(socket, true)) {
838       if (m_singleThreadForAll)
839         context->OnStart();
840       else
841         context->Start();
842       AddContext(context);
843       return true;
844     }
845   }
846 
847   PTRACE(2, "PCLI\tError accepting connection: " << m_listenSocket.GetErrorText());
848   delete socket;
849   return false;
850 }
851 
852 
CreateSocket()853 PTCPSocket * PCLISocket::CreateSocket()
854 {
855   return new PTCPSocket();
856 }
857 
858 
859 ///////////////////////////////////////////////////////////////////////////////
860 
PCLITelnet(WORD port,const char * prompt,bool singleThreadForAll)861 PCLITelnet::PCLITelnet(WORD port, const char * prompt, bool singleThreadForAll)
862   : PCLISocket(port, prompt, singleThreadForAll)
863 {
864 }
865 
866 
CreateSocket()867 PTCPSocket * PCLITelnet::CreateSocket()
868 {
869   return new PTelnetSocket();
870 }
871 
872 
873 ///////////////////////////////////////////////////////////////////////////////
874