#include "baseserv.h" #include #include #include #include /////////////////////////////////////////////////////////// // TLurchServer declaration /////////////////////////////////////////////////////////// //classe TProcessHashMap derivata da wxHashMap (è l'equivalente di TAssoc_array) WX_DECLARE_HASH_MAP( wxString, wxProcess*, wxStringHash, wxStringEqual, TProcessHashMap ); class TLurchServer : public TBaseServerApp { TProcessHashMap m_hmProcMap; wxSemaphore m_Semaphore; wxTimer m_PingTimer; wxArrayString m_aServers, m_aAutoRun; DECLARE_EVENT_TABLE(); protected: virtual const wxChar* GetAppName() const; virtual bool Initialization(); virtual bool Deinitialization(); void OnTimer(wxTimerEvent& evt); void OnEndProcess(wxProcessEvent& evt); TXmlItem& AddMiniForm(TXmlItem& tr, const wxChar* action, const wxChar* app, const wxChar* prompt) const; void EnumerateVariables(const wxString& strApp, wxArrayString& arr) const; const wxArrayString& GetServersList() const ; const wxArrayString& GetAutoRunList() const; const TProcessHashMap& GetRunningServers() const { return m_hmProcMap; } bool PingProcess(const wxString& strApp); void StopProcess(const wxString& strApp); wxString StartProcess(const wxString& strApp); TXmlItem& AddTableRow(TXmlItem& table, const wxChar* prompt, const wxString& value) const; public: void GenerateFile(wxString& strFilename); bool IsMagicName(wxString& strFilename) const; bool IsCgiName(wxString strFilename) const; void ProcessHttpGet(wxString cmd, wxSocketBase& outs); void ProcessHttpPost(wxString cmd, wxSocketBase& outs); bool ForgetProcess(const wxString& strApp); //metodi riguardanti l'interfaccia html void ProcessFormStart(const THashTable& args, wxSocketBase& sock); void ProcessFormStop(const THashTable& args, wxSocketBase& sock); void ProcessFormRestart(const THashTable& args, wxSocketBase& sock); void ProcessFormConfig(const THashTable& args, wxSocketBase& sock); void ProcessFormUpdate(THashTable& args, wxSocketBase& sock); void CallCgi(wxString& strFileName, wxSocketBase& sock); TLurchServer(); }; ////////////////////////////////////////////////////////// // TLurchServer implementation /////////////////////////////////////////////////////////// BEGIN_EVENT_TABLE(TLurchServer, TBaseServerApp) EVT_TIMER(wxID_ANY, TLurchServer::OnTimer) EVT_END_PROCESS(wxID_ANY, TLurchServer::OnEndProcess) END_EVENT_TABLE() bool TLurchServer::PingProcess(const wxString& strApp) { bool bPinged = false; const unsigned short nPort = GetConfigInt("Port", 0, strApp); if (nPort > 0) { int nTimeOut = m_PingTimer.GetInterval()/3; // msec if (nTimeOut < 1000) nTimeOut = 1000; else if (nTimeOut > 5000) nTimeOut = 5000; wxIPV4address ipAddress; //ipAddress.AnyAddress(); // NON funziona con ESET ipAddress.LocalHost(); // Pare funzionare sempre ipAddress.Service(nPort); wxSocketClient sSock(wxSOCKET_NOWAIT); sSock.Connect(ipAddress, false); if (sSock.WaitOnConnect(0, nTimeOut)) { sSock.Discard(); // Tralascia eventuali dati in arrivo non richiesti const wxString strPing = wxT("PING\n"); sSock.Write(strPing, strPing.Len()); if (sSock.WaitForWrite(0, nTimeOut)) { ::wxMilliSleep(nTimeOut); // Triste necessita' :-( Aspetto qui ... char buffer[8]; memset(buffer, 0, sizeof(buffer)); sSock.Read(buffer, 4); if (sSock.WaitForRead(0, 1)) // ... ma poi non aspetto qua :-) bPinged = wxStrncmp(buffer, wxT("PONG"), 4) == 0; sSock.Discard(); // Tralascia ulteriori dati in arrivo } else { wxString strMsg; strMsg << _("Ping Host - ") << ipAddress.Hostname() << ":" << nPort << _(" - Write Error ") << sSock.LastError() ; WriteLog(strMsg); } } else { wxString strMsg; strMsg << _("Ping Host - ") << ipAddress.Hostname() << ":" << nPort << _(" - Connection Error ") << sSock.LastError() ; WriteLog(strMsg); } /* wxDatagramSocket dSock(ipAddress, wxSOCKET_NOWAIT); ipAddress.Service(1884); dSock.SendTo(ipAddress, "PING\n", 5); */ } return bPinged; } void TLurchServer::OnTimer(wxTimerEvent& evt) { const wxSemaError se = IsRunning() ? m_Semaphore.TryWait() : wxSEMA_MISC_ERROR; if (se == wxSEMA_NO_ERROR) { const wxArrayString& aServers = GetAutoRunList(); const size_t nServers = aServers.GetCount(); if (nServers > 0) // Is anybody out there? { const int nRandomIndex = ::rand() % nServers; const wxString& strApp = aServers[nRandomIndex]; // Usually "Authorization" if (!PingProcess(strApp)) { if (!strApp.IsEmpty()) // Dummy test { const wxSingleInstanceChecker sic(strApp); if (sic.IsAnotherRunning()) { wxProcess* pProcess = m_hmProcMap[strApp]; wxKillError ke = pProcess->Kill(pProcess->GetPid()); if (ke == wxKILL_OK) WriteLog(strApp + _(" killed!")); else { wxString strMsg; strMsg << _("Error ") << ke << _(" killing ") << strApp; WriteLog(strMsg); } wxSleep(2); } } StartProcess(strApp); } } m_Semaphore.Post(); } else evt.Skip(); } void TLurchServer::OnEndProcess(wxProcessEvent& evt) { TProcessHashMap::const_iterator it; for (it = m_hmProcMap.begin(); it != m_hmProcMap.end(); ++it) { const wxProcess* p = it->second; if (p->GetPid() == evt.GetPid()) { ForgetProcess(it->first); break; } } } const wxChar* TLurchServer::GetAppName() const { return wxT("Lurch"); } const wxArrayString& TLurchServer::GetServersList() const { if (m_aServers.IsEmpty()) { wxFileInputStream ini(GetConfigName()); const size_t size = ini.GetSize(); wxChar* buff = new wxChar[size]; ini.Read(buff, size); const char* aperta = strchr(buff, '['); while (aperta != NULL) { char* chiusa = (char*)strchr(aperta+1, ']'); if (chiusa != NULL) { *chiusa = '\0'; wxString str = aperta+1; ((wxArrayString&)m_aServers).Add(str); aperta = strchr(chiusa+1, '['); } else break; } } return m_aServers; } const wxArrayString& TLurchServer::GetAutoRunList() const { if (m_aAutoRun.IsEmpty()) { const wxArrayString& as = GetServersList(); for (size_t i = 0; i < as.GetCount(); i++) { const bool bAutorun = GetConfigBool("Autorun", false, as[i]); if (bAutorun) ((wxArrayString&)m_aAutoRun).Add(as[i]); } } return m_aAutoRun; } TXmlItem& TLurchServer::AddMiniForm(TXmlItem& tr, const wxChar* action, const wxChar* app, const wxChar* prompt) const { TXmlItem& td = tr.AddChild("td").SetAttr("width", "10%"); td.SetAttr("align", "center"); td.SetAttr("valign", "center"); // Lo ignora ma insisto TXmlItem& form = td.AddChild("form"); form.SetAttr("action", action); TXmlItem& name = form.AddChild("input"); name.SetAttr("type", "hidden"); name.SetAttr("name", "App"); name.SetAttr("value", app); TXmlItem& submit = form.AddChild("input"); submit.SetAttr("type", "submit"); submit.SetAttr("value", prompt); return submit; } TXmlItem& TLurchServer::AddTableRow(TXmlItem& table, const wxChar* prompt, const wxString& value) const { TXmlItem& tr = table.AddChild("tr"); tr.AddChild("td").AddEnclosedText(prompt); tr.AddChild("td").AddEnclosedText(value); return tr; } void TLurchServer::GenerateFile(wxString& strFilename) { TXmlItem html; TXmlItem& body = CreatePageBody(html); if (strFilename == "index") { TXmlItem& table = body.AddChild("table"); table.SetAttr("border", "1"); table.SetAttr("width", "100%"); strFilename = GetTempFilename(); const wxString strLurchName = GetAppName(); const wxArrayString& arr = GetServersList(); for (size_t i = 0; i < arr.GetCount(); i++) { const wxString& strApp = arr[i]; const bool bLurch = strApp == strLurchName; const wxString strHost = GetConfigString("Host", wxGetHostName(), strApp); const int nPort = GetConfigInt("Port", 3883, strApp); const wxString strIcon = GetConfigString("Icon", "euro.gif", strApp); const wxSingleInstanceChecker sic(strApp); const bool bRunning = sic.IsAnotherRunning(); TXmlItem& tr = table.AddChild("tr"); TXmlItem& td0 = tr.AddChild("td"); td0.SetAttr("width", "10%"); td0.SetAttr("align", "center"); TXmlItem& a = td0.AddChild("a"); if (!bLurch && bRunning) { a.SetAttr("href", wxString::Format("http://%s:%d/index.htm", strHost.c_str(), nPort)); a.SetAttr("target", "_blank"); } TXmlItem& img = a.AddChild("img"); img.SetAttr("src", strIcon); img.SetAttr("border", 0L); img.SetAttr("alt", arr[i]); const bool bAutoRun = bLurch || GetAutoRunList().Index(arr[i]) != wxNOT_FOUND; TXmlItem& buttStart = AddMiniForm(tr, "start.cgi", arr[i], _("Start")); if (bRunning || bAutoRun) buttStart.SetAttr(wxT("type"), wxT("hidden")); TXmlItem& buttStop = AddMiniForm(tr, "kill.cgi", arr[i], _("Stop")); if (!bLurch && (!bRunning || bAutoRun)) buttStop.SetAttr(wxT("type"), wxT("hidden")); TXmlItem& buttRestart = AddMiniForm(tr, "restart.cgi", arr[i], _("Restart")); if (!(bRunning && !bLurch)) buttRestart.SetAttr(wxT("type"), wxT("hidden")); TXmlItem& a3 = tr.AddChild("td").AddChild("a"); if (!bLurch && bRunning) { a3.SetAttr("href", wxString::Format("http://%s:%d/index.htm", strHost.c_str(), nPort)); a3.SetAttr("target", "_blank"); } a3 << arr[i] << " Server"; } body.AddChild("hr"); TXmlItem& panel = body.AddChild("table"); panel.SetAttr("border", "1"); panel.SetAttr("width", "100%"); panel.AddChild("caption").AddEnclosedText("Options"); AddTableRow(panel, wxT("Working directory"), wxGetCwd()); wxString strHost; strHost << wxGetHostName() << wxT(":") << GetDefaultPort(); AddTableRow(panel, wxT("Host Name"), strHost); int y, v, t, p; GetVersionInfo(y, v, t, p); wxString strVer; strVer.Printf(wxT("%d.%d.%d"), v, t, p); AddTableRow(panel, wxT("Version"), strVer); wxString strFreq; strFreq << m_PingTimer.GetInterval()/1000; AddTableRow(panel, wxT("Ping Frequency"), strFreq); html.Save(strFilename); } } bool TLurchServer::IsMagicName(wxString& strFilename) const { wxString strName; wxSplitPath(strFilename, NULL, &strName, NULL); strName.MakeLower(); if (strName == "index") { strFilename = strName; return true; } if (strName == "log") { strFilename = GetLogFileName(); } return false; } bool TLurchServer::IsCgiName(wxString strFilename) const { const int q = strFilename.Find('?'); if (q > 0) strFilename.Truncate(q); wxString strExt; wxSplitPath(strFilename, NULL, NULL, &strExt); strExt.MakeLower(); return strExt == "cgi" || strExt == "exe"; } wxString TLurchServer::StartProcess(const wxString& strApp) { bool ok = false; wxString strMessage; if (!strApp.IsEmpty()) { const wxSingleInstanceChecker sic(strApp); ok = !sic.IsAnotherRunning(); } if (ok) { const wxString strRun = GetConfigString("Run", "", strApp); if (wxFileExists(strRun)) { wxFileName fnPath; fnPath.SetPath(GetServerPath()); fnPath.SetFullName(strRun); wxProcess* pProcess = wxProcess::Open(fnPath.GetFullPath()); if (pProcess == NULL) { strMessage.Printf(_("Can't run %s executable (%s)"), strApp.c_str(), strRun.c_str()); } else { m_hmProcMap[strApp] = pProcess; // memorizza il numero del processo che ha lanciato per poterlo usare in fase di controllo strMessage = wxEmptyString; ::wxSleep(1); // Lascia il tempo di inizializzarsi prima di compinciare a pingarlo! } } else strMessage.Printf(_("Can't find %s executable (%s)"), strApp.c_str(), strRun.c_str()); } else strMessage.Printf(_("%s il already running"), strApp.c_str()); if (strMessage.IsEmpty()) WriteLog(strApp + _(" started.")); else WriteLog(strMessage); return strMessage; } void TLurchServer::ProcessFormStart(const THashTable& args, wxSocketBase& sock) { const wxString strApp = args.Get("App"); if (!strApp.IsEmpty()) // Dummy test { const wxString strMessage = StartProcess(strApp); if (strMessage.IsEmpty()) MessageBox(_("Server Started"), strApp, sock); else MessageBox(_("ERROR"), strMessage, sock); } } bool TLurchServer::ForgetProcess(const wxString& strApp) { WriteLog(strApp + _(" terminated.")); return m_hmProcMap.erase(strApp) != 0; } void TLurchServer::StopProcess(const wxString& strApp) { const wxString strHost = GetConfigString("Host", "localhost", strApp); const int nPort = GetConfigInt("Port", 0, strApp); if (nPort > 0) { wxIPV4address addr; addr.Hostname(strHost); addr.Service(nPort); wxSocketClient sock; if (sock.Connect(addr)) { // Prima provo con le buone inviando una richiesta di stop const wxString str = "GET /stop.cgi HTTP/1.1\r\n\r\n"; sock.Write(str, str.Length()); if (addr.IsLocalHost()) { const wxSingleInstanceChecker sic(strApp); int i; for (i = 5; i >= 0 && sic.IsAnotherRunning(); i--) wxSleep(1); // Se le buone non hanno funzionato ... passo alle cattive if (i < 0) { wxProcess* p = m_hmProcMap[strApp]; if (p != NULL) { p->Kill(p->GetPid()); ForgetProcess(strApp); } } } } } } void TLurchServer::ProcessFormStop(const THashTable& args, wxSocketBase& sock) { if (m_Semaphore.Wait() == wxSEMA_NO_ERROR) { const wxString strApp = args.Get("App"); if (strApp == GetAppName() || strApp.IsEmpty()) // Stop myself! { wxString strCmd = wxT("stop.cgi"); // Exits main loop; TBaseServerApp::CanProcessCommand(strCmd, sock); } else { StopProcess(strApp); MessageBox("Server stopped", strApp, sock); } m_Semaphore.Post(); } } void TLurchServer::ProcessFormRestart(const THashTable& args, wxSocketBase& sock) { const wxString strApp = args.Get("App"); if (!strApp.IsEmpty() && strApp != GetAppName()) { if (m_Semaphore.Wait() == wxSEMA_NO_ERROR) { StopProcess(strApp); ::wxSleep(2); ProcessFormStart(args, sock); m_Semaphore.Post(); } } } void TLurchServer::EnumerateVariables(const wxString& strApp, wxArrayString& arr) const { wxFileInputStream inf(GetConfigName()); wxString strParagraph = wxString::Format("[%s]", strApp.c_str()); wxString str; bool bFound = false; while (inf.Ok()) { inf >> str; if (str == strParagraph) { bFound = true; break; } } if (bFound) { while (inf.Ok()) { inf >> str; if (str.IsEmpty() || str[0u] == '[') break; const int nEqual = str.Find('='); if (nEqual > 0) { str.Truncate(nEqual); str.Trim(false); str.Trim(true); arr.Add(str); } } arr.Sort(); } } void TLurchServer::ProcessFormConfig(const THashTable& args, wxSocketBase& sock) { const wxString strApp = args.Get("App"); wxArrayString arr; EnumerateVariables(strApp, arr); TXmlItem html; TXmlItem& body = CreatePageBody(html); TXmlItem& form = body.AddChild("form"); form.SetAttr("action", "update.cgi").SetAttr("method", "post"); TXmlItem& table = form.AddChild("table").SetAttr("width", "100%").SetAttr("border", "1"); table.AddChild("caption").AddChild("h2") << strApp; TXmlItem& thead = table.AddChild("thead"); thead.AddChild("th").SetAttr("width", "15%") << "Property"; thead.AddChild("th").SetAttr("width", "85%") << "Value"; for (size_t v = 0; v < arr.GetCount(); v++) { TXmlItem& tr = table.AddChild("tr"); tr.AddChild("td") << arr[v]; TXmlItem& input = tr.AddChild("td").AddChild("input"); input.SetAttr("type", "text"); input.SetAttr("name", arr[v]); input.SetAttr("size", "80"); input.SetAttr("maxlength", "256"); input.SetAttr("value", GetConfigString(arr[v], "", strApp)); } TXmlItem& app = form.AddChild("input").SetAttr("type", "hidden"); app.SetAttr("name", "App"); app.SetAttr("value", strApp); TXmlItem& submit = form.AddChild("br").AddChild("center").AddChild("input"); submit.SetAttr("type", "submit"); submit.SetAttr("value", "Update Parameters"); body.AddChild("br"); AddLinkButton(body.AddChild("center"), "Return to main page", "/"); const wxString strFilename = GetTempFilename(); html.Save(strFilename); SendFile(strFilename, sock); } void TLurchServer::ProcessFormUpdate(THashTable& args, wxSocketBase& sock) { const wxString strApp = args.Get("App"); args.BeginFind(); for (wxHashTable::Node* pNode = args.Next(); pNode; pNode = args.Next()) { const wxString strKey = pNode->GetKeyString(); if (strKey != "App") { const wxString strVal = args.Get(strKey); SetConfigString(strKey, strVal, strApp); } } const wxString msg = wxString::Format("%s parameters updated", strApp.c_str()); MessageBox("Success!", msg, sock); } void TLurchServer::CallCgi(wxString& strFileName, wxSocketBase& sock) { wxString strName, strExt, strArgs; const int q = strFileName.Find('?'); if (q > 0) { strArgs = strFileName.Mid(q+1); strFileName.Truncate(q); } wxSplitPath(strFileName, NULL, &strName, &strExt); THashTable hashArgs(13); ParseArguments(strArgs, hashArgs); if (strExt == "cgi") { strName.MakeLower(); if (strName == "start") ProcessFormStart(hashArgs, sock); else if (strName == "kill") ProcessFormStop(hashArgs, sock); else if (strName == "restart") ProcessFormRestart(hashArgs, sock); else if (strName == "config") ProcessFormConfig(hashArgs, sock); else if (strName == "update") ProcessFormUpdate(hashArgs, sock); } } void TLurchServer::ProcessHttpGet(wxString cmd, wxSocketBase& outs) { const int stop = cmd.Find(" HTTP"); wxString str = cmd.Mid(4, stop-4).Trim(); if (str == "/") str += "index.htm"; wxString strFilename = GetDocumentRoot() + str; if (IsCgiName(strFilename)) CallCgi(strFilename, outs); else { if (IsMagicName(strFilename)) GenerateFile(strFilename); SendFile(strFilename, outs); } } void TLurchServer::ProcessHttpPost(wxString cmd, wxSocketBase& outs) { const int stop = cmd.Find(" HTTP"); wxString strFileName = cmd.Mid(5, stop-5).Trim(); wxString strName, args; wxSplitPath(strFileName, NULL, &strName, NULL); const int pos = cmd.Find("\r\n\r\n"); if (pos > 0) args = cmd.Mid(pos+4); THashTable hashArgs(17); ParseArguments(args, hashArgs); if (strName == "update") ProcessFormUpdate(hashArgs, outs); } bool TLurchServer::Initialization() { const wxArrayString& arr = GetAutoRunList(); const size_t nAuto = arr.GetCount(); if (nAuto > 0) { for (size_t i = 0; i < nAuto; i++) StartProcess(arr[i]); const int nFreq = GetConfigInt("PingFreq"); if (nFreq > 0) { m_PingTimer.Start(nFreq * 1000); // sec to msec m_Semaphore.Post(); // GREEN! } } return true; } bool TLurchServer::Deinitialization() { m_PingTimer.Stop(); // Stop ping process! // kill all autorun processes even if I didn't start them const wxArrayString& ar = GetAutoRunList(); for (size_t i = 0; i < ar.GetCount(); i++) StopProcess(ar[i]); // kill all still running (die hard) processes const TProcessHashMap& hmServers = GetRunningServers(); if (!hmServers.empty()) { // Fill a safe array of names before killing the processes stored in the hash map wxArrayString aRunner; // Array of the names of the runners TProcessHashMap::const_iterator it; for (it = hmServers.begin(); it != hmServers.end(); ++it) aRunner.Add(it->first); for (size_t i = 0; i < aRunner.GetCount(); i++) StopProcess(aRunner[i]); } return true; } // Istanziare l'applicazione principale IMPLEMENT_APP(TLurchServer) TLurchServer::TLurchServer() : m_Semaphore(0 /* RED! */, 1 /* mutex like behaviour */) { }