ultimatepp/uppsrc2/TServ/tserv.cpp
cxl 9516965bb8 Added uppsrc2 nest
git-svn-id: svn://ultimatepp.org/upp/trunk@1175 f0d560ea-af0d-0410-9eb7-867de7ffcac7
2009-05-14 06:44:11 +00:00

843 lines
18 KiB
C++

#include <Web/Web.h>
#include "version.h"
#include <plugin/bz2/bz2.h>
#ifdef PLATFORM_POSIX
#include <signal.h>
#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<SlaveProcess> slave;
};
Array<Connection> connections;
int timer_chunk;
bool logging;
bool timing;
bool quit_flag;
Index<dword> 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<SlaveProcess> 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<char> 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<SlaveProcess> 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<String> 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<String> 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);
}
}