From 2b9e988dd563d66a8a341a359d17c51032cbca40 Mon Sep 17 00:00:00 2001 From: Klaus Schmidinger Date: Wed, 29 Apr 2015 13:10:06 +0200 Subject: [PATCH] The SVDRP port now accepts multiple concurrent connections --- HISTORY | 14 +++- interface.c | 12 +-- interface.h | 7 +- svdrp.c | 234 ++++++++++++++++++++++++++++++++++++++++++---------- svdrp.h | 87 +------------------ vdr.c | 14 +++- 6 files changed, 221 insertions(+), 147 deletions(-) diff --git a/HISTORY b/HISTORY index 373e881f..20a6b682 100644 --- a/HISTORY +++ b/HISTORY @@ -8596,7 +8596,7 @@ Video Disk Recorder Revision History - Bumped all version numbers to 2.2.0. - Official release. -2015-04-19: Version 2.3.1 +2015-04-29: Version 2.3.1 - The new function cOsd::MaxPixmapSize() can be called to determine the maximum size a cPixmap may have on the current OSD. The 'osddemo' example has been modified @@ -8645,3 +8645,15 @@ Video Disk Recorder Revision History //#define DEPRECATED_GETBITMAP in osd.h as a quick workaround. In the long run the plugin will need to be adapted. - The -u option now also accepts a numerical user id (suggested by Derek Kelly). +- The SVDRP port now accepts multiple concurrent connections. You can now keep an + SVDRP connection open as long as you wish, without preventing others from + connecting. Note, though, that SVDRP connections still get closed automatically + if there has been no activity for 300 seconds (configurable via + "Setup/Miscellaneous/SVDRP timeout (s)"). +- The SVDRP log messages have been unified and now always contain the IP and port + number of the remote host. +- SVDRP connections are now handled in a separate thread, which makes them more + responsive. Note that there is only one thread that handles all concurrent SVDRP + connections. That way each SVDRP command is guaranteed to be processed separately, + without interfering with any other SVDRP commands that might be issued at the same + time. diff --git a/interface.c b/interface.c index 6bf7ffce..dcb766ca 100644 --- a/interface.c +++ b/interface.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: interface.c 3.1 2015/01/11 13:37:47 kls Exp $ + * $Id: interface.c 4.1 2015/04/28 11:16:06 kls Exp $ */ #include "interface.h" @@ -19,27 +19,19 @@ cInterface *Interface = NULL; -cInterface::cInterface(int SVDRPport) +cInterface::cInterface(void) { interrupted = false; - SVDRP = NULL; - if (SVDRPport) - SVDRP = new cSVDRP(SVDRPport); } cInterface::~cInterface() { - delete SVDRP; } eKeys cInterface::GetKey(bool Wait) { if (!cRemote::HasKeys()) Skins.Flush(); - if (SVDRP) { - if (SVDRP->Process()) - Wait = false; - } if (!cRemote::IsLearning()) return cRemote::Get(Wait ? 1000 : 10); else diff --git a/interface.h b/interface.h index 2b3f979a..4131cf3a 100644 --- a/interface.h +++ b/interface.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: interface.h 1.31 2004/05/01 11:11:13 kls Exp $ + * $Id: interface.h 4.1 2015/04/28 11:15:11 kls Exp $ */ #ifndef __INTERFACE_H @@ -13,17 +13,14 @@ #include "config.h" #include "remote.h" #include "skins.h" -#include "svdrp.h" class cInterface { private: bool interrupted; - cSVDRP *SVDRP; bool QueryKeys(cRemote *Remote, cSkinDisplayMenu *DisplayMenu); public: - cInterface(int SVDRPport = 0); + cInterface(void); ~cInterface(); - bool HasSVDRPConnection(void) { return SVDRP && SVDRP->HasConnection(); } void Interrupt(void) { interrupted = true; } eKeys GetKey(bool Wait = true); eKeys Wait(int Seconds = 0, bool KeepChar = false); diff --git a/svdrp.c b/svdrp.c index 2b5edb2c..f249d169 100644 --- a/svdrp.c +++ b/svdrp.c @@ -10,7 +10,7 @@ * and interact with the Video Disk Recorder - or write a full featured * graphical interface that sits on top of an SVDRP connection. * - * $Id: svdrp.c 3.6 2015/01/12 11:16:27 kls Exp $ + * $Id: svdrp.c 4.1 2015/04/29 13:10:01 kls Exp $ */ #include "svdrp.h" @@ -33,14 +33,32 @@ #include "keys.h" #include "menu.h" #include "plugin.h" +#include "recording.h" #include "remote.h" #include "skins.h" +#include "thread.h" #include "timers.h" #include "tools.h" #include "videodir.h" // --- cSocket --------------------------------------------------------------- +class cSocket { +private: + int port; + int sock; + int queue; + cString lastAcceptedConnection; +public: + cSocket(int Port, int Queue = 1); + ~cSocket(); + bool Open(void); + void Close(void); + int Socket(void) const { return sock; } + int Accept(void); + const char *LastAcceptedConnection(void) const { return lastAcceptedConnection; } + }; + cSocket::cSocket(int Port, int Queue) { port = Port; @@ -100,6 +118,7 @@ bool cSocket::Open(void) LOG_ERROR; return false; } + isyslog("SVDRP listening on port %d/tcp", port); } return true; } @@ -110,7 +129,7 @@ int cSocket::Accept(void) struct sockaddr_in clientname; uint size = sizeof(clientname); int newsock = accept(sock, (struct sockaddr *)&clientname, &size); - if (newsock > 0) { + if (newsock >= 0) { bool accepted = SVDRPhosts.Acceptable(clientname.sin_addr.s_addr); if (!accepted) { const char *s = "Access denied!\n"; @@ -119,7 +138,8 @@ int cSocket::Accept(void) close(newsock); newsock = -1; } - isyslog("connect from %s, port %hu - %s", inet_ntoa(clientname.sin_addr), ntohs(clientname.sin_port), accepted ? "accepted" : "DENIED"); + lastAcceptedConnection = cString::sprintf("%s:%hu", inet_ntoa(clientname.sin_addr), ntohs(clientname.sin_port)); + isyslog("SVDRP %s connection %s", *lastAcceptedConnection, accepted ? "accepted" : "DENIED"); } else if (errno != EINTR && errno != EAGAIN) LOG_ERROR; @@ -130,6 +150,19 @@ int cSocket::Accept(void) // --- cPUTEhandler ---------------------------------------------------------- +class cPUTEhandler { +private: + FILE *f; + int status; + const char *message; +public: + cPUTEhandler(void); + ~cPUTEhandler(); + bool Process(const char *s); + int Status(void) { return status; } + const char *Message(void) { return message; } + }; + cPUTEhandler::cPUTEhandler(void) { if ((f = tmpfile()) != NULL) { @@ -385,17 +418,77 @@ const char *GetHelpPage(const char *Cmd, const char **p) return NULL; } -char *cSVDRP::grabImageDir = NULL; +static cString grabImageDir; -cSVDRP::cSVDRP(int Port) -:socket(Port) +class cSVDRP { +private: + int socket; + cString connection; + cFile file; + cRecordings recordings; + cPUTEhandler *PUTEhandler; + int numChars; + int length; + char *cmdLine; + time_t lastActivity; + void Close(bool SendReply = false, bool Timeout = false); + bool Send(const char *s, int length = -1); + void Reply(int Code, const char *fmt, ...) __attribute__ ((format (printf, 3, 4))); + void PrintHelpTopics(const char **hp); + void CmdCHAN(const char *Option); + void CmdCLRE(const char *Option); + void CmdDELC(const char *Option); + void CmdDELR(const char *Option); + void CmdDELT(const char *Option); + void CmdEDIT(const char *Option); + void CmdGRAB(const char *Option); + void CmdHELP(const char *Option); + void CmdHITK(const char *Option); + void CmdLSTC(const char *Option); + void CmdLSTE(const char *Option); + void CmdLSTR(const char *Option); + void CmdLSTT(const char *Option); + void CmdMESG(const char *Option); + void CmdMODC(const char *Option); + void CmdMODT(const char *Option); + void CmdMOVC(const char *Option); + void CmdMOVR(const char *Option); + void CmdNEWC(const char *Option); + void CmdNEWT(const char *Option); + void CmdNEXT(const char *Option); + void CmdPLAY(const char *Option); + void CmdPLUG(const char *Option); + void CmdPUTE(const char *Option); + void CmdREMO(const char *Option); + void CmdSCAN(const char *Option); + void CmdSTAT(const char *Option); + void CmdUPDT(const char *Option); + void CmdUPDR(const char *Option); + void CmdVOLU(const char *Option); + void Execute(char *Cmd); +public: + cSVDRP(int Socket, const char *Connection); + ~cSVDRP(); + bool HasConnection(void) { return file.IsOpen(); } + bool Process(void); + }; + +cSVDRP::cSVDRP(int Socket, const char *Connection) { + socket = Socket; + connection = Connection; PUTEhandler = NULL; numChars = 0; length = BUFSIZ; cmdLine = MALLOC(char, length); - lastActivity = 0; - isyslog("SVDRP listening on port %d", Port); + lastActivity = time(NULL); + if (file.Open(socket)) { + //TODO how can we get the *full* hostname? + char buffer[BUFSIZ]; + gethostname(buffer, sizeof(buffer)); + time_t now = time(NULL); + Reply(220, "%s SVDRP VideoDiskRecorder %s; %s; %s", buffer, VDRVERSION, *TimeToString(now), cCharSetConv::SystemCharacterTable() ? cCharSetConv::SystemCharacterTable() : "UTF-8"); + } } cSVDRP::~cSVDRP() @@ -413,10 +506,11 @@ void cSVDRP::Close(bool SendReply, bool Timeout) gethostname(buffer, sizeof(buffer)); Reply(221, "%s closing connection%s", buffer, Timeout ? " (timeout)" : ""); } - isyslog("closing SVDRP connection"); //TODO store IP#??? + isyslog("SVDRP %s connection closed", *connection); file.Close(); DELETENULL(PUTEhandler); } + close(socket); } bool cSVDRP::Send(const char *s, int length) @@ -454,7 +548,7 @@ void cSVDRP::Reply(int Code, const char *fmt, ...) } else { Reply(451, "Zero return code - looks like a programming error!"); - esyslog("SVDRP: zero return code!"); + esyslog("SVDRP %s zero return code!", *connection); } } } @@ -641,7 +735,7 @@ void cSVDRP::CmdDELC(const char *Option) Channels.Del(channel); Channels.ReNumber(); Channels.SetModified(true); - isyslog("channel %s deleted", Option); + isyslog("SVDRP %s channel %s deleted", *connection, Option); if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) { if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring()) Channels.SwitchTo(CurrentChannel->Number()); @@ -714,7 +808,7 @@ void cSVDRP::CmdDELT(const char *Option) cTimer *timer = Timers.Get(strtol(Option, NULL, 10) - 1); if (timer) { if (!timer->Recording()) { - isyslog("deleting timer %s", *timer->ToDescr()); + isyslog("SVDRP %s deleting timer %s", *connection, *timer->ToDescr()); Timers.Del(timer); Timers.SetModified(); Reply(250, "Timer \"%s\" deleted", Option); @@ -831,7 +925,7 @@ void cSVDRP::CmdGRAB(const char *Option) // canonicalize the file name: char RealFileName[PATH_MAX]; if (FileName) { - if (grabImageDir) { + if (*grabImageDir) { cString s(FileName); FileName = s; const char *slash = strrchr(FileName, '/'); @@ -868,7 +962,7 @@ void cSVDRP::CmdGRAB(const char *Option) int fd = open(FileName, O_WRONLY | O_CREAT | O_NOFOLLOW | O_TRUNC, DEFFILEMODE); if (fd >= 0) { if (safe_write(fd, Image, ImageSize) == ImageSize) { - dsyslog("grabbed image to %s", FileName); + dsyslog("SVDRP %s grabbed image to %s", *connection, FileName); Reply(250, "Grabbed image %s", Option); } else { @@ -1195,7 +1289,7 @@ void cSVDRP::CmdLSTT(const char *Option) void cSVDRP::CmdMESG(const char *Option) { if (*Option) { - isyslog("SVDRP message: '%s'", Option); + isyslog("SVDRP %s message '%s'", *connection, Option); Skins.QueueMessage(mtInfo, Option); Reply(250, "Message queued"); } @@ -1219,7 +1313,7 @@ void cSVDRP::CmdMODC(const char *Option) *channel = ch; Channels.ReNumber(); Channels.SetModified(true); - isyslog("modifed channel %d %s", channel->Number(), *channel->ToText()); + isyslog("SVDRP %s modifed channel %d %s", *connection, channel->Number(), *channel->ToText()); Reply(250, "%d %s", channel->Number(), *channel->ToText()); } else @@ -1262,7 +1356,7 @@ void cSVDRP::CmdMODT(const char *Option) } *timer = t; Timers.SetModified(); - isyslog("timer %s modified (%s)", *timer->ToDescr(), timer->HasFlags(tfActive) ? "active" : "inactive"); + isyslog("SVDRP %s timer %s modified (%s)", *connection, *timer->ToDescr(), timer->HasFlags(tfActive) ? "active" : "inactive"); Reply(250, "%d %s", timer->Index() + 1, *timer->ToText()); } else @@ -1306,7 +1400,7 @@ void cSVDRP::CmdMOVC(const char *Option) else cDevice::SetCurrentChannel(CurrentChannel); } - isyslog("channel %d moved to %d", FromNumber, ToNumber); + isyslog("SVDRP %s channel %d moved to %d", *connection, FromNumber, ToNumber); Reply(250,"Channel \"%d\" moved to \"%d\"", From, To); } else @@ -1382,7 +1476,7 @@ void cSVDRP::CmdNEWC(const char *Option) Channels.Add(channel); Channels.ReNumber(); Channels.SetModified(true); - isyslog("new channel %d %s", channel->Number(), *channel->ToText()); + isyslog("SVDRP %s new channel %d %s", *connection, channel->Number(), *channel->ToText()); Reply(250, "%d %s", channel->Number(), *channel->ToText()); } else @@ -1402,7 +1496,7 @@ void cSVDRP::CmdNEWT(const char *Option) if (timer->Parse(Option)) { Timers.Add(timer); Timers.SetModified(); - isyslog("timer %s added", *timer->ToDescr()); + isyslog("SVDRP %s timer %s added", *connection, *timer->ToDescr()); Reply(250, "%d %s", timer->Index() + 1, *timer->ToText()); return; } @@ -1622,11 +1716,11 @@ void cSVDRP::CmdUPDT(const char *Option) t->Parse(Option); delete timer; timer = t; - isyslog("timer %s updated", *timer->ToDescr()); + isyslog("SVDRP %s timer %s updated", *connection, *timer->ToDescr()); } else { Timers.Add(timer); - isyslog("timer %s added", *timer->ToDescr()); + isyslog("SVDRP %s timer %s added", *connection, *timer->ToDescr()); } Timers.SetModified(); Reply(250, "%d %s", timer->Index() + 1, *timer->ToText()); @@ -1729,19 +1823,7 @@ void cSVDRP::Execute(char *Cmd) bool cSVDRP::Process(void) { - bool NewConnection = !file.IsOpen(); - bool SendGreeting = NewConnection; - - if (file.IsOpen() || file.Open(socket.Accept())) { - if (SendGreeting) { - //TODO how can we get the *full* hostname? - char buffer[BUFSIZ]; - gethostname(buffer, sizeof(buffer)); - time_t now = time(NULL); - Reply(220, "%s SVDRP VideoDiskRecorder %s; %s; %s", buffer, VDRVERSION, *TimeToString(now), cCharSetConv::SystemCharacterTable() ? cCharSetConv::SystemCharacterTable() : "UTF-8"); - } - if (NewConnection) - lastActivity = time(NULL); + if (file.IsOpen()) { while (file.Ready(false)) { unsigned char c; int r = safe_read(file, &c, 1); @@ -1781,7 +1863,7 @@ bool cSVDRP::Process(void) cmdLine = NewBuffer; } else { - esyslog("ERROR: out of memory"); + esyslog("SVDRP %s ERROR: out of memory", *connection); Close(); break; } @@ -1792,23 +1874,87 @@ bool cSVDRP::Process(void) lastActivity = time(NULL); } else if (r <= 0) { - isyslog("lost connection to SVDRP client"); + isyslog("SVDRP %s lost connection to client", *connection); Close(); } } if (Setup.SVDRPTimeout && time(NULL) - lastActivity > Setup.SVDRPTimeout) { - isyslog("timeout on SVDRP connection"); + isyslog("SVDRP %s timeout on connection", *connection); Close(true, true); } - return true; } - return false; + return file.IsOpen(); } -void cSVDRP::SetGrabImageDir(const char *GrabImageDir) +void SetSVDRPGrabImageDir(const char *GrabImageDir) { - free(grabImageDir); - grabImageDir = GrabImageDir ? strdup(GrabImageDir) : NULL; + grabImageDir = GrabImageDir; } -//TODO more than one connection??? +// --- cSVDRPHandler --------------------------------------------------------- + +class cSVDRPHandler : public cThread { +private: + cSocket socket; + cVector connections; + void ProcessConnections(void); +protected: + virtual void Action(void); +public: + cSVDRPHandler(int Port); + virtual ~cSVDRPHandler(); + }; + +cSVDRPHandler::cSVDRPHandler(int Port) +:cThread("SVDRP handler", true) +,socket(Port) +{ +} + +cSVDRPHandler::~cSVDRPHandler() +{ + Cancel(3); +} + +void cSVDRPHandler::ProcessConnections(void) +{ + for (int i = 0; i < connections.Size(); i++) { + if (connections[i]) { + if (!connections[i]->Process()) { + delete connections[i]; + connections.Remove(i); + i--; + } + } + } +} + +void cSVDRPHandler::Action(void) +{ + if (socket.Open()) { + while (Running()) { + cFile::AnyFileReady(socket.Socket(), 1000); + int NewSocket = socket.Accept(); + if (NewSocket >= 0) + connections.Append(new cSVDRP(NewSocket, socket.LastAcceptedConnection())); + ProcessConnections(); + } + socket.Close(); + } +} + +static cSVDRPHandler *SVDRPHandler = NULL; + +void StartSVDRPHandler(int Port) +{ + if (Port && !SVDRPHandler) { + SVDRPHandler = new cSVDRPHandler(Port); + SVDRPHandler->Start(); + } +} + +void StopSVDRPHandler(void) +{ + delete SVDRPHandler; + SVDRPHandler = NULL; +} diff --git a/svdrp.h b/svdrp.h index 8ac419af..2b426ed6 100644 --- a/svdrp.h +++ b/svdrp.h @@ -4,93 +4,14 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: svdrp.h 3.2 2013/10/21 07:42:03 kls Exp $ + * $Id: svdrp.h 4.1 2015/04/29 13:10:06 kls Exp $ */ #ifndef __SVDRP_H #define __SVDRP_H -#include "recording.h" -#include "tools.h" - -class cSocket { -private: - int port; - int sock; - int queue; - void Close(void); -public: - cSocket(int Port, int Queue = 1); - ~cSocket(); - bool Open(void); - int Accept(void); - }; - -class cPUTEhandler { -private: - FILE *f; - int status; - const char *message; -public: - cPUTEhandler(void); - ~cPUTEhandler(); - bool Process(const char *s); - int Status(void) { return status; } - const char *Message(void) { return message; } - }; - -class cSVDRP { -private: - cSocket socket; - cFile file; - cRecordings recordings; - cPUTEhandler *PUTEhandler; - int numChars; - int length; - char *cmdLine; - time_t lastActivity; - static char *grabImageDir; - void Close(bool SendReply = false, bool Timeout = false); - bool Send(const char *s, int length = -1); - void Reply(int Code, const char *fmt, ...) __attribute__ ((format (printf, 3, 4))); - void PrintHelpTopics(const char **hp); - void CmdCHAN(const char *Option); - void CmdCLRE(const char *Option); - void CmdDELC(const char *Option); - void CmdDELR(const char *Option); - void CmdDELT(const char *Option); - void CmdEDIT(const char *Option); - void CmdGRAB(const char *Option); - void CmdHELP(const char *Option); - void CmdHITK(const char *Option); - void CmdLSTC(const char *Option); - void CmdLSTE(const char *Option); - void CmdLSTR(const char *Option); - void CmdLSTT(const char *Option); - void CmdMESG(const char *Option); - void CmdMODC(const char *Option); - void CmdMODT(const char *Option); - void CmdMOVC(const char *Option); - void CmdMOVR(const char *Option); - void CmdNEWC(const char *Option); - void CmdNEWT(const char *Option); - void CmdNEXT(const char *Option); - void CmdPLAY(const char *Option); - void CmdPLUG(const char *Option); - void CmdPUTE(const char *Option); - void CmdREMO(const char *Option); - void CmdSCAN(const char *Option); - void CmdSTAT(const char *Option); - void CmdUPDT(const char *Option); - void CmdUPDR(const char *Option); - void CmdVOLU(const char *Option); - void Execute(char *Cmd); -public: - cSVDRP(int Port); - ~cSVDRP(); - bool HasConnection(void) { return file.IsOpen(); } - bool Process(void); - static void SetGrabImageDir(const char *GrabImageDir); - }; +void SetSVDRPGrabImageDir(const char *GrabImageDir); +void StartSVDRPHandler(int Port); +void StopSVDRPHandler(void); #endif //__SVDRP_H diff --git a/vdr.c b/vdr.c index d8b2b1a5..c196f8a3 100644 --- a/vdr.c +++ b/vdr.c @@ -22,7 +22,7 @@ * * The project's page is at http://www.tvdr.de * - * $Id: vdr.c 4.2 2015/04/19 12:38:12 kls Exp $ + * $Id: vdr.c 4.3 2015/04/29 09:18:54 kls Exp $ */ #include @@ -65,6 +65,7 @@ #include "sourceparams.h" #include "sources.h" #include "status.h" +#include "svdrp.h" #include "themes.h" #include "timers.h" #include "tools.h" @@ -375,7 +376,7 @@ int main(int argc, char *argv[]) break; case 'g' | 0x100: return GenerateIndex(optarg) ? 0 : 2; - case 'g': cSVDRP::SetGrabImageDir(*optarg != '-' ? optarg : NULL); + case 'g': SetSVDRPGrabImageDir(*optarg != '-' ? optarg : NULL); break; case 'h': DisplayHelp = true; break; @@ -831,7 +832,7 @@ int main(int argc, char *argv[]) // User interface: - Interface = new cInterface(SVDRPport); + Interface = new cInterface; // Default skins: @@ -913,6 +914,10 @@ int main(int argc, char *argv[]) sd_notify(0, "READY=1\nSTATUS=Ready"); #endif + // SVDRP: + + StartSVDRPHandler(SVDRPport); + // Main program loop: #define DELETE_MENU ((IsInfoMenu &= (Menu == NULL)), delete Menu, Menu = NULL) @@ -1418,7 +1423,7 @@ int main(int argc, char *argv[]) // Keep the recordings handler alive: RecordingsHandler.Active(); - if ((Now - LastInteract) > ACTIVITYTIMEOUT && !cRecordControls::Active() && !RecordingsHandler.Active() && !Interface->HasSVDRPConnection() && (Now - cRemote::LastActivity()) > ACTIVITYTIMEOUT) { + if ((Now - LastInteract) > ACTIVITYTIMEOUT && !cRecordControls::Active() && !RecordingsHandler.Active() && (Now - cRemote::LastActivity()) > ACTIVITYTIMEOUT) { // Handle housekeeping tasks // Shutdown: @@ -1466,6 +1471,7 @@ Exit: signal(SIGPIPE, SIG_DFL); signal(SIGALRM, SIG_DFL); + StopSVDRPHandler(); PluginManager.StopPlugins(); cRecordControls::Shutdown(); RecordingsHandler.DelAll();