#include #include "version.h" #include #ifdef PLATFORM_POSIX #include #endif//PLATFORM_POSIX using namespace Upp; enum { DEFAULT_PORT = 2346 }; #ifdef _DEBUG #define DO_SVRLOG 1 #else #define DO_SVRLOG 0 #endif #if DO_SVRLOG #define SVRLOG(x) { StringStream ss; ss << x << '\n'; RLOG(ss.GetResult()); fputs(ss.GetResult(), stderr); fflush(stderr); } #else #define SVRLOG(x) #endif /* void DeleteFolderDeep(const char *dir) { FindFile ff(String(dir) + "/*.*"); while(ff) { String name = ff.GetName(); String p = AppendFileName(dir, name); if(ff.IsFile()) DeleteFile(p); else if(ff.IsFolder()) DeleteFolderDeep(p); ff.Next(); } DirectoryDelete(dir); } */ static bool quit_signal = false; static void QuitSignal(int) { puts("Termination signal received, quitting..."); quit_signal = true; } class CommandServer { public: typedef CommandServer CLASSNAME; CommandServer(); virtual ~CommandServer(); CommandServer& Logging(bool l) { logging = l; return *this; } CommandServer& Timing(bool t) { timing = t; return *this; } CommandServer& Filter(const char *ip); void Execute(Socket& socket); bool StartServer(int port, const String& cmdline); void StopServer(int port); void RunServer(int port); void RunClient(); void Quit() { quit_flag = true; } protected: String GetConInput(const char *prompt); virtual void ExecTimer(); // void RunCommand(String cmd, Socket& socket); String CheckFiles(const char *f); String FileRemove(const char *f); String DeleteFolderDeep(const char *f); String UploadFiles(const char *f); String DownloadFiles(const char *f); String CreateDir(const char *f); String GetFileInfo(const char *f); protected: class Connection { public: Connection(Socket socket) : socket(socket), eof(false) {} virtual ~Connection(); bool IsEof() const { return eof; } String GetName() const { return name; } virtual bool Run() = 0; protected: String name; Socket socket; bool eof; }; class CommandConnection : public Connection { public: CommandConnection(Socket& socket, const char *command); virtual bool Run(); private: One slave; }; Array connections; int timer_chunk; bool logging; bool timing; bool quit_flag; Index ip_filter; // int thread_count; }; ////////////////////////////////////////////////////////////////////// // CommandServer::Connection:: CommandServer::Connection::~Connection() { if(socket.IsOpen()) socket.StopWrite(); } ////////////////////////////////////////////////////////////////////// // CommandServer::CommandConnection:: CommandServer::CommandConnection::CommandConnection(Socket& _socket, const char *_command) : Connection(_socket) { _socket.Clear(); try { String environment; String pathlist; if(*_command == ':') { const char *b = ++_command; while(*_command && *_command++ != '\n') ; environment = ASCII85Decode(String(b, _command)); for(b = environment; *b; b += strlen(b) + 1) if(!MemICmp(b, "PATH=", 5)) { pathlist = b + 5; break; } } if(*_command == '=') _command++; String command = _command; if(!IsNull(pathlist)) { String exec; if(*_command == '\"') { while(*++_command && (*_command != '\"' || *++_command == '\"')) exec.Cat(*_command); } else while(*_command && (byte)*_command > ' ') exec.Cat(*_command++); command = GetFileOnPath(exec, pathlist, true); #ifdef PLATFORM_WIN32 if(IsNull(command)) command = GetFileOnPath(exec + ".exe", pathlist, true); #endif if(IsNull(command)) command = exec; if(command.Find(' ') >= 0) command = '\"' + command + '\"'; command << ' ' << _command; } LOG("CommandServer::CommandConnection(" << command << ")"); slave = StartProcess(command, environment); LOG("CommandServer::CommandConnection -> OK"); socket.Write("+\0", 2); name << "[command]" << command; } catch(Exc e) { if(socket.IsOpen()) socket.Write('-' + e + '\0'); throw; } } bool CommandServer::CommandConnection::Run() { // SVRLOG("CommandConnection::Run"); if(eof) return true; ASSERT(socket.IsOpen()); bool work = false; String inp = socket.ReadUntil('\0', 0, 10000000); if(inp.IsVoid()) { // broken pipe slave -> Kill(); eof = true; return true; } if(!inp.IsEmpty()) { SVRLOG("-> input data: <" << inp << ">"); work = true; slave -> Write(inp); } String out = Null; eof = !slave -> Read(out); if(!out.IsEmpty()) { work = true; SVRLOG("-> output data: <" << out << ">"); socket.Write(out); } if(eof) { work = true; socket.Write("\0", 1); SVRLOG("Process terminated, exit code = " << slave -> GetExitCode()); String ec = IntStr(slave -> GetExitCode()); socket.Write(ec, ec.GetLength() + 1); socket.StopWrite(); } // SVRLOG("/ CommandConnection::Run -> " << (work ? "working" : "idle")); return work; } ////////////////////////////////////////////////////////////////////// // CommandServer:: CommandServer::CommandServer() { timer_chunk = 100; quit_flag = true; Logging(false); Timing(false); } CommandServer::~CommandServer() { } CommandServer& CommandServer::Filter(const char *ip) { Socket::Init(); hostent *he = gethostbyname(ip); if(he) ip_filter.FindAdd(Peek32be((in_addr *)(he -> h_addr_list[0]))); else fputs(NFormat("gethostbyname: adresa '%s' nebyla nalezena ...\n", ip), stderr); return *this; } String CommandServer::CheckFiles(const char *f) { int line = 0; String out = "OK\n"; while(*f) { line++; const char *b = f; while(*f && *f != '\n' && *f != '\t') f++; String fn = NativePath(String(b, f)); int t; if(*f != '\t' || IsNull(t = ScanInt(f + 1, &f))) return NFormat("ERROR(%s): file time expected at line %d", fn, line); Time time = Time(2000, 1, 1) + t; int l; if(*f != '\t' || IsNull(l = ScanInt(f + 1, &f))) return NFormat("ERROR(%s): file size expected at line %d", fn, line); FindFile ff(fn); if(ff && tabs(Time(ff.GetLastWriteTime()) - time) <= 2 && ff.GetLength() == l) out << fn << '\n'; while(*f && *f++ != '\n') ; } return out; } String CommandServer::FileRemove(const char *f) { String out; while(*f) { const char *b = f; while(*f && *f != '\n') f++; String fn(b, f); if(!IsNull(fn) && !FileDelete(NativePath(fn))) out << NFormat("%s: error deleting file\n", fn); while(*f && *f++ != '\n') ; } if(IsNull(out)) out = "OK"; return out; } String CommandServer::DeleteFolderDeep(const char *f) { while(*f) { const char *b = f; while(*f && *f != '\n') f++; String fn(b, f); if(!IsNull(fn)) Upp::DeleteFolderDeep(NativePath(fn)); while(*f && *f++ != '\n') ; } return "OK"; } String CommandServer::GetFileInfo(const char *f) { int td = ScanInt(f, &f) - (int)(GetSysTime() - Time(2000, 1, 1)); if(*f++ != ':') return "ERROR(@): ':' expected after time delta"; int line = 0; String output; while(*f) { line++; const char *b = f; while(*f && *f != '\n') f++; String fn(b, f); FindFile ff; output << fn; if(ff.Search(NativePath(fn))) output << '\t' << ((int)(Time(ff.GetLastWriteTime()) - Time(2000, 1, 1)) + td) << '\t' << (ff.IsFile() ? (int)ff.GetLength() : -1); output << "\n"; while(*f && *f++ != '\n') ; } return output; } String CommandServer::UploadFiles(const char *f) { SVRLOG("CommandServer::UploadFiles"); int line = 0; while(*f) { ++line; const char *b = f; while(*f && *f != '\t' && *f != '\n') f++; String fn = NativePath(String(b, f)); if(IsNull(fn)) return NFormat("ERROR: file name expected at line %d", line); SVRLOG("Updating file '" << fn << "'..."); int t; if(*f != '\t' || IsNull(t = ScanInt(f + 1, &f))) return NFormat("ERROR(%s): file time expected at line %d", fn, line); Time time = Time(2000, 1, 1) + t; int l; if(*f != '\t' || IsNull(l = ScanInt(f + 1, &f))) return NFormat("ERROR(%s): file size expected at line %d", fn, line); if(*f != '\t') return NFormat("ERROR(%s): file content expected at line %d", fn, line); b = ++f; while(*f && *f != '\n') f++; if(*f != '\n') return NFormat("ERROR(%s): unexpected end of file at line %d", fn, line); SVRLOG("ASCII85 length = " << (int)(f - b)); String bz = ASCII85Decode((const byte *)b, f - b); SVRLOG("BZ compressed length = " << bz.GetLength()); String content = BZ2Decompress(bz); SVRLOG("Content length = " << content.GetLength()); if(content.GetLength() != l) return NFormat("ERROR(%s): file size (%d) doesn't match decompressed size %d", fn, l, content.GetLength()); RealizePath(fn); if(!SaveFile(fn, content) || !FileSetTime(fn, time)) return NFormat("ERROR(%s): cannot write file", fn); SVRLOG(NFormat("FileSetTime(%s, %`) -> %`", fn, time, FileGetTime(fn))); while(*f && *f++ != '\n') ; } SVRLOG("// CommandServer::UploadFiles"); return "OK"; } String CommandServer::DownloadFiles(const char *f) { int line = 0; String output; while(*f) { line++; const char *b = f; while(*f && *f != '\n') f++; String fn(b, f); FindFile ff; output << fn; String nfn = NativePath(fn); if(ff.Search(nfn)) { String data = LoadFile(nfn); output << '\t' << data.GetLength() << '\t' << ASCII85Encode(BZ2Compress(data)); } output << "\n"; while(*f && *f++ != '\n') ; } return output; } String CommandServer::CreateDir(const char *f) { RealizeDirectory(f); return "OK"; } /* void CommandServer::RunCommand(const String& _command, Socket& socket) { String command = _command; #ifdef PLATFORM_WIN32 if(*command == '/') command = "x:" + command; #endif LOG("CommandServer::RunCommand(" << command << ")"); One process = StartProcess(command); LOG("CommandServer::RunCommand -> OK"); socket.Send("+\0", 2); bool work = false, running; do { if(socket.Peek()) { SVRLOG("-> socket peek"); String inp = socket.Recv(0 | Socket::MULTIPART); SVRLOG("-> input data: <" << inp << ">"); if(!inp.IsEmpty()) { work = true; process -> Write(inp); } } String out = Null; running = process -> Read(out); // SVRLOG("-> output data: <" << out << ">"); if(!out.IsEmpty()) { work = true; socket.Send(out); } else MtSleep(200); // SVRLOG("work = " << work << ", running = " << running); // if(!work && running) } while(running); socket.Send("\0", 1); LOG("Process terminated, exit code = " << process -> GetExitCode()); String ec = IntStr(process -> GetExitCode()); socket.Send(ec, ec.GetLength() + 1); } */ void CommandServer::Execute(Socket& socket) { String cmd = socket.ReadUntil('\0', Null, 10000000); if(logging) puts(cmd); SVRLOG(cmd); String out; const char *data = cmd; switch(*data++) { case '?': out = "+" TSERV_VERSION "\x1A"; break; case '.': Quit(); out = ".QUITTING\x1A"; break; case ':': case '=': connections.Add(new CommandConnection(socket, cmd)); return; case '-': out = FileRemove(data); break; case '~': out = DeleteFolderDeep(data); break; case '<': out = CheckFiles(data); break; case '>': out = UploadFiles(data); break; case '^': out = DownloadFiles(data); break; case '*': out = CreateDir(data); break; case '@': out = GetFileInfo(data); break; default: throw Exc(NFormat("Invalid command: '%s'", cmd)); } socket.Write(out); socket.Write("\0", 1); socket.Close(); } void CommandServer::ExecTimer() { } bool CommandServer::StartServer(int port, const String& cmdline) { #ifdef PLATFORM_WIN32 STARTUPINFO sinfo; GetStartupInfo(&sinfo); PROCESS_INFORMATION pinfo; Buffer cmd(cmdline.GetLength() + 1); memcpy(cmd, cmdline, cmdline.GetLength() + 1); if(!CreateProcess(NULL, cmd, NULL, NULL, false, CREATE_NO_WINDOW, NULL, NULL, &sinfo, &pinfo)) throw Exc(NFormat("Cannot create process '%s':\n%s", cmdline, GetErrorMessage(GetLastError()))); // detach from process CloseHandle(pinfo.hThread); CloseHandle(pinfo.hProcess); #else pid_t pid = fork(); if(pid == -1) throw Exc(NFormat("Error executing fork(), error code: %d", errno)); // printf("fork -> %d\n", pid); if(!pid) { // new process, return true to fall through to server loop Logging(false); Timing(false); return true; } #endif Socket::Init(); String sockerr; String self = Socket::GetHostName(); Socket socket; if(!ClientSocket(socket, self, port)) throw Exc(NFormat("Error opening server %s:%d: %s", self, port, Socket::GetErrorText())); socket.Write("?\n"); String res = socket.ReadUntil('\x1A'); if(*res == '\x08') throw Exc(res.Mid(1)); if(res != TSERV_VERSION) throw Exc("Cannot run server process."); return false; } void CommandServer::StopServer(int port) { Socket::Init(); Socket socket; if(!ClientSocket(socket, Socket::GetHostName(), port)) throw Exc(NFormat("Error opening server at port %d", port)); socket.Write(".\n"); socket.ReadUntil('\x1A'); } void CommandServer::RunServer(int port) { // thread_count = 0; puts(NFormat("Running server at port %d", port)); if(logging) puts(NFormat("Creating server socket at port %d ...", port)); String error; Socket server; if(!ServerSocket(server, port, true, 100)) { puts(NFormat("Error creating server socket at port %d: %s", port, Socket::GetErrorText())); return; } server.NoLinger(); if(logging) { if(ip_filter.IsEmpty()) puts("IP Filtering: off (accepting requests from all IP addresses)"); else { fputs("IP filter:", stdout); for(int i = 0; i < ip_filter.GetCount(); i++) fputs(' ' + FormatIP(ip_filter[i]), stdout); putc('\n', stdout); } puts("Running accept loop ..."); } dword ipaddr; dword ticks = GetTickCount() + 1000; for(quit_flag = false; !quit_flag && !quit_signal;) { bool work = false; Socket connection; if(server.Accept(connection, &ipaddr, true, timer_chunk)) { work = true; if(logging) { fputc('\r', stderr); fputs(Format(GetSysTime()), stderr); fflush(stderr); } ExecTimer(); if(!connection.IsOpen()) continue; try { if(!ip_filter.IsEmpty() && ip_filter.Find(ipaddr) < 0) throw Exc(NFormat("Invalid request source IP address: %s", FormatIP(ipaddr))); Execute(connection); } catch(Exc e) { if(connection.IsOpen()) connection.Write('-' + e + '\0'); if(logging) puts(NFormat("ERROR: %s", e)); } } if(!connections.IsEmpty()) { for(int i = connections.GetCount(); --i >= 0;) { Connection& conn = connections[i]; bool err = false; try { if(conn.Run()) work = true; } catch(Exc e) { puts(NFormat("%s: %s", conn.GetName(), e)); err = true; } if(err || conn.IsEof()) { connections.Remove(i); continue; } } } if(!work) Sleep(timer_chunk); dword new_ticks = GetTickCount(); if(new_ticks >= ticks) { ticks = new_ticks + 1000; fputs(String().Cat() << "\r" << Format(GetSysTime()), stderr); fflush(stderr); } } if(logging) fputs("\nServer is shutting down ... \n", stderr); } /* static void PutConsole(const char *p) { enum { ROW_BATCH = 20 }; int rows = ROW_BATCH; int col = 0; const char *b = p; while(*p) { char c = *p; putchar(c); if(*p++ == '\n' || ++col >= 70) { col = 0; if(*p && --rows <= 0) { rows = ROW_BATCH; fputs("\n==== Pokracovat pomoci Enter ====\n", stdout); fflush(stdout); bool quit = false; for(int c; (c = getchar()) >= 0 && c != '\n'; quit |= (c == 'Q' || c == 'q')) ; if(quit) break; } } } if(p > b && p[-1] != '\n') putchar('\n'); } */ String CommandServer::GetConInput(const char *prompt) { fputc('\n', stdout); fputs(prompt, stdout); int i = getchar(); if(i == EOF) return String::GetVoid(); String s; while(i != '\n') { s.Cat(i); if((i = getchar()) == EOF) break; } return s; } void CommandServer::RunClient() { Socket::Init(); quit_flag = false; for(String in; !quit_flag && !(in = GetConInput("run> ")).IsVoid();) { if(!CompareNoCase(in, ".")) break; try { One sp = StartProcess(in); for(String s; sp -> Read(s); fputs(s, stdout)) { // puts("Flushing application..."); Sleep(500); } printf("\nTerminated, exit code = %d\n", sp -> GetExitCode()); } catch(Exc e) { puts("Error: " + e); } } } void TryMain() { bool start = false; bool stop = false; int port = -1; CommandServer server; Vector cmdline; cmdline <<= CommandLine(); for(int c = 0; c < cmdline.GetCount(); c++) { const char *arg = cmdline[c]; if(*arg == '-') switch(arg++, *arg++) { case 'r': start = true; break; case 's': stop = true; break; default: throw Exc(NFormat("invalid option: '%s'", cmdline[c])); } else if(IsDigit(*arg)) { // port number port = atoi(arg); } } bool noport = (port < 0); if(noport) port = DEFAULT_PORT; if(stop) { server.StopServer(port); return; } if(noport) { // run client against host server.RunClient(); return; } if(start) { String cmd_line; cmd_line << port; if(!server.StartServer(port, cmd_line)) return; } server.RunServer(port); } CONSOLE_APP_MAIN { #ifdef PLATFORM_POSIX signal(SIGPIPE, SIG_IGN); signal(SIGINT, &QuitSignal); signal(SIGTERM, &QuitSignal); #endif//PLATFORM_POSIX puts(String().Cat() << "TSERV " TSERV_VERSION ", release date: " << TSERV_DATE << "\n" TSERV_COPYRIGHT); /* Vector test; for(int i = 0; i < 1000; i++) test.Add(GetRandomIdent(10000)); test.Clear(); puts("Done !"); SetExitCode(111); return; */ int retcode = 1; try { // SetLanguage(LNG_ENGLISH); TryMain(); } catch(Exc e) { puts("Error: " + e); SetExitCode(1); } }