diff options
Diffstat (limited to 'lib/server')
32 files changed, 2675 insertions, 230 deletions
diff --git a/lib/server/ConnectionException.txt b/lib/server/ConnectionException.txt index 5056754f..c3429116 100644 --- a/lib/server/ConnectionException.txt +++ b/lib/server/ConnectionException.txt @@ -10,7 +10,7 @@ SocketConnectError 15 Probably a network issue between client and server, bad TLSHandshakeFailed 30 TLSShutdownFailed 32 TLSWriteFailed 33 Probably a network issue between client and server. -TLSReadFailed 34 Probably a network issue between client and server. +TLSReadFailed 34 Probably a network issue between client and server, or a problem with the server. TLSNoPeerCertificate 36 TLSPeerCertificateInvalid 37 Check certification process TLSClosedWhenWriting 38 diff --git a/lib/server/Daemon.cpp b/lib/server/Daemon.cpp index 3366383a..facac900 100644 --- a/lib/server/Daemon.cpp +++ b/lib/server/Daemon.cpp @@ -1,4 +1,4 @@ -// distribution boxbackup-0.10 (svn version: 494) +// distribution boxbackup-0.11rc1 (svn version: 2023_2024) // // Copyright (c) 2003 - 2006 // Ben Summers and contributors. All rights reserved. @@ -57,16 +57,19 @@ #include <string.h> #include <stdarg.h> -#ifdef HAVE_SYSLOG_H - #include <syslog.h> +#ifdef WIN32 + #include <ws2tcpip.h> #endif +#include <iostream> + #include "Daemon.h" #include "Configuration.h" #include "ServerException.h" #include "Guards.h" #include "UnixUser.h" #include "FileModificationTime.h" +#include "Logging.h" #include "MemLeakFindOn.h" @@ -82,11 +85,16 @@ Daemon *Daemon::spDaemon = 0; // // -------------------------------------------------------------------------- Daemon::Daemon() - : mpConfiguration(0), + : mpConfiguration(NULL), mReloadConfigWanted(false), - mTerminateWanted(false) + mTerminateWanted(false), + mSingleProcess(false), + mRunInForeground(false), + mKeepConsoleOpenAfterFork(false), + mHaveConfigFile(false), + mAppName(DaemonName()) { - if(spDaemon != 0) + if(spDaemon != NULL) { THROW_EXCEPTION(ServerException, AlreadyDaemonConstructed) } @@ -113,56 +121,267 @@ Daemon::~Daemon() delete mpConfiguration; mpConfiguration = 0; } + + ASSERT(spDaemon == this); + spDaemon = NULL; } // -------------------------------------------------------------------------- // // Function -// Name: Daemon::Main(const char *, int, const char *[]) -// Purpose: Starts the daemon off -- equivalent of C main() function -// Created: 2003/07/29 +// Name: Daemon::GetOptionString() +// Purpose: Returns the valid Getopt command-line options. +// This should be overridden by subclasses to add +// their own options, which should override +// ProcessOption, handle their own, and delegate to +// ProcessOption for the standard options. +// Created: 2007/09/18 // // -------------------------------------------------------------------------- -int Daemon::Main(const char *DefaultConfigFile, int argc, const char *argv[]) +std::string Daemon::GetOptionString() { - // Banner (optional) + return "c:" + #ifndef WIN32 + "DFk" + #endif + "hqvVt:T"; +} + +void Daemon::Usage() +{ + std::cout << + DaemonBanner() << "\n" + "\n" + "Usage: " << mAppName << " [options] [config file]\n" + "\n" + "Options:\n" + " -c <file> Use the specified configuration file. If -c is omitted, the last\n" + " argument is the configuration file, or else the default \n" + " [" << GetConfigFileName() << "]\n" +#ifndef WIN32 + " -D Debugging mode, do not fork, one process only, one client only\n" + " -F Do not fork into background, but fork to serve multiple clients\n" + " -k Keep console open after fork, keep writing log messages to it\n" +#endif + " -q Run more quietly, reduce verbosity level by one, can repeat\n" + " -v Run more verbosely, increase verbosity level by one, can repeat\n" + " -V Run at maximum verbosity\n" + " -t <tag> Tag console output with specified marker\n" + " -T Timestamp console output\n"; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Daemon::ProcessOption(int option) +// Purpose: Processes the supplied option (equivalent to the +// return code from getopt()). Return zero if the +// option was handled successfully, or nonzero to +// abort the program with that return value. +// Created: 2007/09/18 +// +// -------------------------------------------------------------------------- +int Daemon::ProcessOption(signed int option) +{ + switch(option) { - const char *banner = DaemonBanner(); - if(banner != 0) + case 'c': { - printf("%s", banner); + mConfigFileName = optarg; + mHaveConfigFile = true; } - } + break; - std::string pidFileName; +#ifndef WIN32 + case 'D': + { + mSingleProcess = true; + } + break; - try - { - // Find filename of config file - mConfigFileName = DefaultConfigFile; - if(argc >= 2) + case 'F': { - // First argument is config file, or it's -c and the next arg is the config file - if(::strcmp(argv[1], "-c") == 0 && argc >= 3) - { - mConfigFileName = argv[2]; - } - else + mRunInForeground = true; + } + break; + + case 'k': + { + mKeepConsoleOpenAfterFork = true; + } + break; +#endif + + case 'h': + { + Usage(); + return 2; + } + break; + + case 'q': + { + if(mLogLevel == Log::NOTHING) { - mConfigFileName = argv[1]; + BOX_FATAL("Too many '-q': " + "Cannot reduce logging " + "level any more"); + return 2; } + mLogLevel--; } - - // Test mode with no daemonisation? - bool asDaemon = true; - if(argc >= 3) + break; + + case 'v': { - if(::strcmp(argv[2], "SINGLEPROCESS") == 0) + if(mLogLevel == Log::EVERYTHING) { - asDaemon = false; + BOX_FATAL("Too many '-v': " + "Cannot increase logging " + "level any more"); + return 2; } + mLogLevel++; + } + break; + + case 'V': + { + mLogLevel = Log::EVERYTHING; + } + break; + + case 't': + { + Console::SetTag(optarg); + } + break; + + case 'T': + { + Console::SetShowTime(true); + } + break; + + case '?': + { + BOX_FATAL("Unknown option on command line: " + << "'" << (char)optopt << "'"); + return 2; + } + break; + + default: + { + BOX_FATAL("Unknown error in getopt: returned " + << "'" << option << "'"); + return 1; + } + } + + return 0; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Daemon::Main(const char *, int, const char *[]) +// Purpose: Parses command-line options, and then calls +// Main(std::string& configFile, bool singleProcess) +// to start the daemon. +// Created: 2003/07/29 +// +// -------------------------------------------------------------------------- +int Daemon::Main(const char *DefaultConfigFile, int argc, const char *argv[]) +{ + // Find filename of config file + mConfigFileName = DefaultConfigFile; + mAppName = argv[0]; + + #ifdef NDEBUG + mLogLevel = Log::NOTICE; // need an int to do math with + #else + mLogLevel = Log::INFO; // need an int to do math with + #endif + + if (argc == 2 && strcmp(argv[1], "/?") == 0) + { + Usage(); + return 2; + } + + signed int c; + + // reset getopt, just in case anybody used it before. + // unfortunately glibc and BSD differ on this point! + // http://www.ussg.iu.edu/hypermail/linux/kernel/0305.3/0262.html + #if HAVE_DECL_OPTRESET == 1 + optind = 1; + optreset = 1; + #elif defined __GLIBC__ + optind = 0; + #else // Solaris, any others? + optind = 1; + #endif + + while((c = getopt(argc, (char * const *)argv, + GetOptionString().c_str())) != -1) + { + int returnCode = ProcessOption(c); + + if (returnCode != 0) + { + return returnCode; } + } + if (argc > optind && !mHaveConfigFile) + { + mConfigFileName = argv[optind]; optind++; + } + + if (argc > optind && ::strcmp(argv[optind], "SINGLEPROCESS") == 0) + { + mSingleProcess = true; optind++; + } + + if (argc > optind) + { + BOX_FATAL("Unknown parameter on command line: " + << "'" << std::string(argv[optind]) << "'"); + return 2; + } + + Logging::SetGlobalLevel((Log::Level)mLogLevel); + + return Main(mConfigFileName); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Daemon::Main(const std::string& rConfigFileName) +// Purpose: Starts the daemon off -- equivalent of C main() function +// Created: 2003/07/29 +// +// -------------------------------------------------------------------------- +int Daemon::Main(const std::string &rConfigFileName) +{ + // Banner (optional) + { + #ifndef NDEBUG + BOX_NOTICE(DaemonBanner()); + #endif + } + + std::string pidFileName; + + mConfigFileName = rConfigFileName; + + bool asDaemon = !mSingleProcess && !mRunInForeground; + + try + { // Load the configuration file. std::string errors; std::auto_ptr<Configuration> pconfig; @@ -178,16 +397,9 @@ int Daemon::Main(const char *DefaultConfigFile, int argc, const char *argv[]) if(e.GetType() == CommonException::ExceptionType && e.GetSubType() == CommonException::OSFileOpenError) { - fprintf(stderr, "%s: failed to start: " - "failed to open configuration file: " - "%s", DaemonName(), - mConfigFileName.c_str()); -#ifdef WIN32 - ::syslog(LOG_ERR, "%s: failed to start: " - "failed to open configuration file: " - "%s", DaemonName(), - mConfigFileName.c_str()); -#endif + BOX_FATAL("Failed to start: failed to open " + "configuration file: " + << mConfigFileName); return 1; } @@ -198,14 +410,8 @@ int Daemon::Main(const char *DefaultConfigFile, int argc, const char *argv[]) if(pconfig.get() == 0 || !errors.empty()) { // Tell user about errors - fprintf(stderr, "%s: Errors in config file %s:\n%s", - DaemonName(), mConfigFileName.c_str(), - errors.c_str()); -#ifdef WIN32 - ::syslog(LOG_ERR, "%s: Errors in config file %s:\n%s", - DaemonName(), mConfigFileName.c_str(), - errors.c_str()); -#endif + BOX_FATAL("Failed to start: errors in configuration " + "file: " << mConfigFileName << ": " << errors); // And give up return 1; } @@ -217,17 +423,6 @@ int Daemon::Main(const char *DefaultConfigFile, int argc, const char *argv[]) // Let the derived class have a go at setting up stuff in the initial process SetupInInitialProcess(); -#ifndef WIN32 - // Set signal handler - struct sigaction sa; - sa.sa_handler = SignalHandler; - sa.sa_flags = 0; - sigemptyset(&sa.sa_mask); // macro - if(::sigaction(SIGHUP, &sa, NULL) != 0 || ::sigaction(SIGTERM, &sa, NULL) != 0) - { - THROW_EXCEPTION(ServerException, DaemoniseFailed) - } - // Server configuration const Configuration &serverConfig( mpConfiguration->GetSubConfiguration("Server")); @@ -235,7 +430,8 @@ int Daemon::Main(const char *DefaultConfigFile, int argc, const char *argv[]) // Open PID file for writing pidFileName = serverConfig.GetKeyValue("PidFile"); FileHandleGuard<(O_WRONLY | O_CREAT | O_TRUNC), (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)> pidFile(pidFileName.c_str()); - + +#ifndef WIN32 // Handle changing to a different user if(serverConfig.KeyExists("User")) { @@ -264,7 +460,7 @@ int Daemon::Main(const char *DefaultConfigFile, int argc, const char *argv[]) default: // parent - _exit(0); + // _exit(0); return 0; break; @@ -278,7 +474,8 @@ int Daemon::Main(const char *DefaultConfigFile, int argc, const char *argv[]) // Set new session if(::setsid() == -1) { - ::syslog(LOG_ERR, "can't setsid"); + BOX_ERROR("Failed to setsid(): " << + strerror(errno)); THROW_EXCEPTION(ServerException, DaemoniseFailed) } @@ -301,24 +498,39 @@ int Daemon::Main(const char *DefaultConfigFile, int argc, const char *argv[]) break; } } -#endif // ! WIN32 - // open the log - ::openlog(DaemonName(), LOG_PID, LOG_LOCAL6); + // Set signal handler + // Don't do this in the parent, since it might be anything + // (e.g. test/bbackupd) + + struct sigaction sa; + sa.sa_handler = SignalHandler; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); // macro + if(::sigaction(SIGHUP, &sa, NULL) != 0 || ::sigaction(SIGTERM, &sa, NULL) != 0) + { + THROW_EXCEPTION(ServerException, DaemoniseFailed) + } +#endif // !WIN32 + // Log the start message - ::syslog(LOG_INFO, "Starting daemon (config: %s) (version " - BOX_VERSION ")", mConfigFileName.c_str()); + BOX_NOTICE("Starting daemon, version " << BOX_VERSION + << ", config: " << mConfigFileName); -#ifndef WIN32 // Write PID to file char pid[32]; + +#ifdef WIN32 + int pidsize = sprintf(pid, "%d", (int)GetCurrentProcessId()); +#else int pidsize = sprintf(pid, "%d", (int)getpid()); +#endif + if(::write(pidFile, pid, pidsize) != pidsize) { - ::syslog(LOG_ERR, "can't write pid file"); + BOX_FATAL("can't write pid file"); THROW_EXCEPTION(ServerException, DaemoniseFailed) } -#endif // Set up memory leak reporting #ifdef BOX_MEMORY_LEAK_TESTING @@ -329,7 +541,7 @@ int Daemon::Main(const char *DefaultConfigFile, int argc, const char *argv[]) } #endif // BOX_MEMORY_LEAK_TESTING - if(asDaemon) + if(asDaemon && !mKeepConsoleOpenAfterFork) { #ifndef WIN32 // Close standard streams @@ -352,44 +564,47 @@ int Daemon::Main(const char *DefaultConfigFile, int argc, const char *argv[]) { ::close(devnull); } -#endif // ! WIN32 // And definitely don't try and send anything to those file descriptors // -- this has in the past sent text to something which isn't expecting it. TRACE_TO_STDOUT(false); + Logging::ToConsole(false); +#endif // ! WIN32 } } catch(BoxException &e) { - fprintf(stderr, "%s: failed to start: exception %s (%d/%d)\n", - DaemonName(), e.what(), e.GetType(), e.GetSubType()); -#ifdef WIN32 - ::syslog(LOG_ERR, "%s: failed to start: " - "exception %s (%d/%d)\n", DaemonName(), - e.what(), e.GetType(), e.GetSubType()); -#endif + BOX_FATAL("Failed to start: exception " << e.what() + << " (" << e.GetType() + << "/" << e.GetSubType() << ")"); return 1; } catch(std::exception &e) { - fprintf(stderr, "%s: failed to start: exception %s\n", - DaemonName(), e.what()); -#ifdef WIN32 - ::syslog(LOG_ERR, "%s: failed to start: exception %s\n", - DaemonName(), e.what()); -#endif + BOX_FATAL("Failed to start: exception " << e.what()); return 1; } catch(...) { - fprintf(stderr, "%s: failed to start: unknown exception\n", - DaemonName()); -#ifdef WIN32 - ::syslog(LOG_ERR, "%s: failed to start: unknown exception\n", - DaemonName()); -#endif + BOX_FATAL("Failed to start: unknown error"); return 1; } + +#ifdef WIN32 + // Under win32 we must initialise the Winsock library + // before using sockets + + WSADATA info; + + if (WSAStartup(0x0101, &info) == SOCKET_ERROR) + { + // will not run without sockets + BOX_FATAL("Failed to initialise Windows Sockets"); + THROW_EXCEPTION(CommonException, Internal) + } +#endif + + int retcode = 0; // Main Daemon running try @@ -401,9 +616,8 @@ int Daemon::Main(const char *DefaultConfigFile, int argc, const char *argv[]) if(mReloadConfigWanted && !mTerminateWanted) { // Need to reload that config file... - ::syslog(LOG_INFO, "Reloading configuration " - "(config: %s)", - mConfigFileName.c_str()); + BOX_NOTICE("Reloading configuration file: " + << mConfigFileName); std::string errors; std::auto_ptr<Configuration> pconfig = Configuration::LoadAndVerify( @@ -414,12 +628,12 @@ int Daemon::Main(const char *DefaultConfigFile, int argc, const char *argv[]) if(pconfig.get() == 0 || !errors.empty()) { // Tell user about errors - ::syslog(LOG_ERR, "Errors in config " - "file %s:\n%s", - mConfigFileName.c_str(), - errors.c_str()); + BOX_FATAL("Error in configuration " + << "file: " << mConfigFileName + << ": " << errors); // And give up - return 1; + retcode = 1; + break; } // delete old configuration @@ -440,29 +654,31 @@ int Daemon::Main(const char *DefaultConfigFile, int argc, const char *argv[]) ::unlink(pidFileName.c_str()); // Log - ::syslog(LOG_INFO, "Terminating daemon"); + BOX_NOTICE("Terminating daemon"); } catch(BoxException &e) { - ::syslog(LOG_ERR, "%s: terminating due to exception %s " - "(%d/%d)", DaemonName(), e.what(), e.GetType(), - e.GetSubType()); - return 1; + BOX_FATAL("Terminating due to exception " << e.what() + << " (" << e.GetType() + << "/" << e.GetSubType() << ")"); + retcode = 1; } catch(std::exception &e) { - ::syslog(LOG_ERR, "%s: terminating due to exception %s", - DaemonName(), e.what()); - return 1; + BOX_FATAL("Terminating due to exception " << e.what()); + retcode = 1; } catch(...) { - ::syslog(LOG_ERR, "%s: terminating due to unknown exception", - DaemonName()); - return 1; + BOX_FATAL("Terminating due to unknown exception"); + retcode = 1; } + +#ifdef WIN32 + WSACleanup(); +#endif - return 0; + return retcode; } // -------------------------------------------------------------------------- @@ -539,9 +755,9 @@ const char *Daemon::DaemonName() const // Created: 1/1/04 // // -------------------------------------------------------------------------- -const char *Daemon::DaemonBanner() const +std::string Daemon::DaemonBanner() const { - return 0; + return "Generic daemon using the Box Application Framework"; } diff --git a/lib/server/Daemon.h b/lib/server/Daemon.h index ff36fba4..a500ec6a 100644 --- a/lib/server/Daemon.h +++ b/lib/server/Daemon.h @@ -1,4 +1,4 @@ -// distribution boxbackup-0.10 (svn version: 494) +// distribution boxbackup-0.11rc1 (svn version: 2023_2024) // // Copyright (c) 2003 - 2006 // Ben Summers and contributors. All rights reserved. @@ -79,14 +79,18 @@ private: public: int Main(const char *DefaultConfigFile, int argc, const char *argv[]); + + /* override this Main() if you want custom option processing: */ + virtual int Main(const std::string &rConfigFile); virtual void Run(); const Configuration &GetConfiguration() const; const std::string &GetConfigFileName() const {return mConfigFileName;} virtual const char *DaemonName() const; - virtual const char *DaemonBanner() const; + virtual std::string DaemonBanner() const; virtual const ConfigurationVerify *GetConfigVerify() const; + virtual void Usage(); bool StopRun() {return mReloadConfigWanted | mTerminateWanted;} bool IsReloadConfigWanted() {return mReloadConfigWanted;} @@ -103,18 +107,26 @@ public: protected: box_time_t GetLoadedConfigModifiedTime() const; + bool IsSingleProcess() { return mSingleProcess; } + virtual std::string GetOptionString(); + virtual int ProcessOption(signed int option); private: static void SignalHandler(int sigraised); box_time_t GetConfigFileModifiedTime() const; -private: std::string mConfigFileName; Configuration *mpConfiguration; box_time_t mLoadedConfigModifiedTime; bool mReloadConfigWanted; bool mTerminateWanted; + bool mSingleProcess; + bool mRunInForeground; + bool mKeepConsoleOpenAfterFork; + bool mHaveConfigFile; + int mLogLevel; // need an int to do math with static Daemon *spDaemon; + std::string mAppName; }; #define DAEMON_VERIFY_SERVER_KEYS {"PidFile", 0, ConfigTest_Exists, 0}, \ diff --git a/lib/server/LocalProcessStream.cpp b/lib/server/LocalProcessStream.cpp index 7b8e78c6..f694fa58 100644 --- a/lib/server/LocalProcessStream.cpp +++ b/lib/server/LocalProcessStream.cpp @@ -1,4 +1,4 @@ -// distribution boxbackup-0.10 (svn version: 494) +// distribution boxbackup-0.11rc1 (svn version: 2023_2024) // // Copyright (c) 2003 - 2006 // Ben Summers and contributors. All rights reserved. @@ -56,10 +56,15 @@ #endif #include "LocalProcessStream.h" -#include "SocketStream.h" #include "autogen_ServerException.h" #include "Utils.h" +#ifdef WIN32 + #include "FileStream.h" +#else + #include "SocketStream.h" +#endif + #include "MemLeakFindOn.h" #define MAX_ARGUMENTS 64 @@ -68,19 +73,22 @@ // // Function // Name: LocalProcessStream(const char *, pid_t &) -// Purpose: Run a new process, and return a stream giving access to it's -// stdin and stdout. Returns the PID of the new process -- this -// must be waited on at some point to avoid zombies. +// Purpose: Run a new process, and return a stream giving access +// to its stdin and stdout (stdout and stderr on +// Win32). Returns the PID of the new process -- this +// must be waited on at some point to avoid zombies +// (except on Win32). // Created: 12/3/04 // // -------------------------------------------------------------------------- std::auto_ptr<IOStream> LocalProcessStream(const char *CommandLine, pid_t &rPidOut) { +#ifndef WIN32 + // Split up command std::vector<std::string> command; SplitString(std::string(CommandLine), ' ', command); -#ifndef WIN32 // Build arguments char *args[MAX_ARGUMENTS + 4]; { @@ -139,10 +147,68 @@ std::auto_ptr<IOStream> LocalProcessStream(const char *CommandLine, pid_t &rPidO // Return the stream object and PID rPidOut = pid; return stream; + #else // WIN32 - ::syslog(LOG_ERR, "vfork not implemented - LocalProcessStream.cpp"); - std::auto_ptr<IOStream> stream; + + SECURITY_ATTRIBUTES secAttr; + secAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + secAttr.bInheritHandle = TRUE; + secAttr.lpSecurityDescriptor = NULL; + + HANDLE writeInChild, readFromChild; + if(!CreatePipe(&readFromChild, &writeInChild, &secAttr, 0)) + { + BOX_ERROR("Failed to CreatePipe for child process: " << + GetErrorMessage(GetLastError())); + THROW_EXCEPTION(ServerException, SocketPairFailed) + } + SetHandleInformation(readFromChild, HANDLE_FLAG_INHERIT, 0); + + PROCESS_INFORMATION procInfo; + STARTUPINFO startupInfo; + + ZeroMemory(&procInfo, sizeof(procInfo)); + ZeroMemory(&startupInfo, sizeof(startupInfo)); + startupInfo.cb = sizeof(startupInfo); + startupInfo.hStdError = writeInChild; + startupInfo.hStdOutput = writeInChild; + startupInfo.hStdInput = INVALID_HANDLE_VALUE; + startupInfo.dwFlags |= STARTF_USESTDHANDLES; + + CHAR* commandLineCopy = (CHAR*)malloc(strlen(CommandLine) + 1); + strcpy(commandLineCopy, CommandLine); + + BOOL result = CreateProcess(NULL, + commandLineCopy, // command line + NULL, // process security attributes + NULL, // primary thread security attributes + TRUE, // handles are inherited + 0, // creation flags + NULL, // use parent's environment + NULL, // use parent's current directory + &startupInfo, // STARTUPINFO pointer + &procInfo); // receives PROCESS_INFORMATION + + free(commandLineCopy); + + if(!result) + { + BOX_ERROR("Failed to CreateProcess: '" << CommandLine << + "': " << GetErrorMessage(GetLastError())); + CloseHandle(writeInChild); + CloseHandle(readFromChild); + THROW_EXCEPTION(ServerException, ServerForkError) + } + + CloseHandle(procInfo.hProcess); + CloseHandle(procInfo.hThread); + CloseHandle(writeInChild); + + rPidOut = (int)(procInfo.dwProcessId); + + std::auto_ptr<IOStream> stream(new FileStream(readFromChild)); return stream; + #endif // ! WIN32 } diff --git a/lib/server/LocalProcessStream.h b/lib/server/LocalProcessStream.h index 386ae21f..5547c79c 100644 --- a/lib/server/LocalProcessStream.h +++ b/lib/server/LocalProcessStream.h @@ -1,4 +1,4 @@ -// distribution boxbackup-0.10 (svn version: 494) +// distribution boxbackup-0.11rc1 (svn version: 2023_2024) // // Copyright (c) 2003 - 2006 // Ben Summers and contributors. All rights reserved. diff --git a/lib/server/Makefile.extra b/lib/server/Makefile.extra index 6cc0de2e..33fac0a1 100644 --- a/lib/server/Makefile.extra +++ b/lib/server/Makefile.extra @@ -3,9 +3,9 @@ MAKEEXCEPTION = ../../lib/common/makeexception.pl # AUTOGEN SEEDING autogen_ServerException.h autogen_ServerException.cpp: $(MAKEEXCEPTION) ServerException.txt - perl $(MAKEEXCEPTION) ServerException.txt + $(PERL) $(MAKEEXCEPTION) ServerException.txt # AUTOGEN SEEDING autogen_ConnectionException.h autogen_ConnectionException.cpp: $(MAKEEXCEPTION) ConnectionException.txt - perl $(MAKEEXCEPTION) ConnectionException.txt + $(PERL) $(MAKEEXCEPTION) ConnectionException.txt diff --git a/lib/server/Protocol.cpp b/lib/server/Protocol.cpp index d459bdcb..0f15beeb 100644 --- a/lib/server/Protocol.cpp +++ b/lib/server/Protocol.cpp @@ -1,4 +1,4 @@ -// distribution boxbackup-0.10 (svn version: 494) +// distribution boxbackup-0.11rc1 (svn version: 2023_2024) // // Copyright (c) 2003 - 2006 // Ben Summers and contributors. All rights reserved. @@ -60,6 +60,7 @@ #include "ServerException.h" #include "PartialReadStream.h" #include "ProtocolUncertainStream.h" +#include "Logging.h" #include "MemLeakFindOn.h" @@ -93,7 +94,8 @@ Protocol::Protocol(IOStream &rStream) mLastErrorType(NoError), mLastErrorSubType(NoError) { - TRACE1("Send block allocation size is %d\n", PROTOCOL_ALLOCATE_SEND_BLOCK_CHUNK); + BOX_TRACE("Send block allocation size is " << + PROTOCOL_ALLOCATE_SEND_BLOCK_CHUNK); } // -------------------------------------------------------------------------- diff --git a/lib/server/Protocol.h b/lib/server/Protocol.h index 7781ac7c..da665219 100644 --- a/lib/server/Protocol.h +++ b/lib/server/Protocol.h @@ -1,4 +1,4 @@ -// distribution boxbackup-0.10 (svn version: 494) +// distribution boxbackup-0.11rc1 (svn version: 2023_2024) // // Copyright (c) 2003 - 2006 // Ben Summers and contributors. All rights reserved. diff --git a/lib/server/ProtocolObject.cpp b/lib/server/ProtocolObject.cpp index 44466392..6b426141 100644 --- a/lib/server/ProtocolObject.cpp +++ b/lib/server/ProtocolObject.cpp @@ -1,4 +1,4 @@ -// distribution boxbackup-0.10 (svn version: 494) +// distribution boxbackup-0.11rc1 (svn version: 2023_2024) // // Copyright (c) 2003 - 2006 // Ben Summers and contributors. All rights reserved. diff --git a/lib/server/ProtocolObject.h b/lib/server/ProtocolObject.h index 0dbb2817..9b6736fa 100644 --- a/lib/server/ProtocolObject.h +++ b/lib/server/ProtocolObject.h @@ -1,4 +1,4 @@ -// distribution boxbackup-0.10 (svn version: 494) +// distribution boxbackup-0.11rc1 (svn version: 2023_2024) // // Copyright (c) 2003 - 2006 // Ben Summers and contributors. All rights reserved. diff --git a/lib/server/ProtocolUncertainStream.cpp b/lib/server/ProtocolUncertainStream.cpp index 5d22dc89..84ef6e38 100644 --- a/lib/server/ProtocolUncertainStream.cpp +++ b/lib/server/ProtocolUncertainStream.cpp @@ -1,4 +1,4 @@ -// distribution boxbackup-0.10 (svn version: 494) +// distribution boxbackup-0.11rc1 (svn version: 2023_2024) // // Copyright (c) 2003 - 2006 // Ben Summers and contributors. All rights reserved. diff --git a/lib/server/ProtocolUncertainStream.h b/lib/server/ProtocolUncertainStream.h index ea5346e7..6c4e88f4 100644 --- a/lib/server/ProtocolUncertainStream.h +++ b/lib/server/ProtocolUncertainStream.h @@ -1,4 +1,4 @@ -// distribution boxbackup-0.10 (svn version: 494) +// distribution boxbackup-0.11rc1 (svn version: 2023_2024) // // Copyright (c) 2003 - 2006 // Ben Summers and contributors. All rights reserved. @@ -39,7 +39,7 @@ // -------------------------------------------------------------------------- // // File -// Name: PartialReadStream.h +// Name: ProtocolUncertainStream.h // Purpose: Read part of another stream // Created: 2003/12/05 // @@ -53,7 +53,7 @@ // -------------------------------------------------------------------------- // // Class -// Name: PartialReadStream +// Name: ProtocolUncertainStream // Purpose: Read part of another stream // Created: 2003/12/05 // diff --git a/lib/server/ProtocolWire.h b/lib/server/ProtocolWire.h index 11add21e..64326f31 100644 --- a/lib/server/ProtocolWire.h +++ b/lib/server/ProtocolWire.h @@ -1,4 +1,4 @@ -// distribution boxbackup-0.10 (svn version: 494) +// distribution boxbackup-0.11rc1 (svn version: 2023_2024) // // Copyright (c) 2003 - 2006 // Ben Summers and contributors. All rights reserved. diff --git a/lib/server/SSLLib.cpp b/lib/server/SSLLib.cpp index b8316bc0..40b51da6 100644 --- a/lib/server/SSLLib.cpp +++ b/lib/server/SSLLib.cpp @@ -1,4 +1,4 @@ -// distribution boxbackup-0.10 (svn version: 494) +// distribution boxbackup-0.11rc1 (svn version: 2023_2024) // // Copyright (c) 2003 - 2006 // Ben Summers and contributors. All rights reserved. @@ -52,10 +52,6 @@ #include <openssl/err.h> #include <openssl/rand.h> -#ifndef WIN32 -#include <syslog.h> -#endif - #include "SSLLib.h" #include "ServerException.h" @@ -91,7 +87,8 @@ void SSLLib::Initialise() THROW_EXCEPTION(ServerException, SSLRandomInitFailed) } #else - ::fprintf(stderr, "No random device -- additional seeding of random number generator not performed.\n"); + BOX_WARNING("No random device -- additional seeding of " + "random number generator not performed."); #endif } @@ -111,13 +108,8 @@ void SSLLib::LogError(const char *ErrorDuringAction) while((errcode = ERR_get_error()) != 0) { ::ERR_error_string_n(errcode, errname, sizeof(errname)); - #ifndef NDEBUG - if(SSLLib__TraceErrors) - { - TRACE2("SSL err during %s: %s\n", ErrorDuringAction, errname); - } - #endif - ::syslog(LOG_ERR, "SSL err during %s: %s", ErrorDuringAction, errname); + BOX_ERROR("SSL error during " << ErrorDuringAction << ": " << + errname); } } diff --git a/lib/server/SSLLib.h b/lib/server/SSLLib.h index b6fa9aad..7f0d5a2b 100644 --- a/lib/server/SSLLib.h +++ b/lib/server/SSLLib.h @@ -1,4 +1,4 @@ -// distribution boxbackup-0.10 (svn version: 494) +// distribution boxbackup-0.11rc1 (svn version: 2023_2024) // // Copyright (c) 2003 - 2006 // Ben Summers and contributors. All rights reserved. diff --git a/lib/server/ServerControl.h b/lib/server/ServerControl.h new file mode 100644 index 00000000..c8c746e4 --- /dev/null +++ b/lib/server/ServerControl.h @@ -0,0 +1,228 @@ +// distribution boxbackup-0.11rc1 (svn version: 2023_2024) +// +// Copyright (c) 2003 - 2006 +// Ben Summers and contributors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. All use of this software and associated advertising materials must +// display the following acknowledgment: +// This product includes software developed by Ben Summers. +// 4. The names of the Authors may not be used to endorse or promote +// products derived from this software without specific prior written +// permission. +// +// [Where legally impermissible the Authors do not disclaim liability for +// direct physical injury or death caused solely by defects in the software +// unless it is modified by a third party.] +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// +// +#ifndef SERVER_CONTROL_H +#define SERVER_CONTROL_H + +#include "Test.h" + +#ifdef WIN32 + +#include "WinNamedPipeStream.h" +#include "IOStreamGetLine.h" +#include "BoxPortsAndFiles.h" + +static std::string sPipeName; + +static void SetNamedPipeName(const std::string& rPipeName) +{ + sPipeName = rPipeName; +} + +static bool SendCommands(const std::string& rCmd) +{ + WinNamedPipeStream connection; + + try + { + connection.Connect(sPipeName); + } + catch(...) + { + BOX_ERROR("Failed to connect to daemon control socket"); + return false; + } + + // For receiving data + IOStreamGetLine getLine(connection); + + // Wait for the configuration summary + std::string configSummary; + if(!getLine.GetLine(configSummary)) + { + BOX_ERROR("Failed to receive configuration summary from daemon"); + return false; + } + + // Was the connection rejected by the server? + if(getLine.IsEOF()) + { + BOX_ERROR("Server rejected the connection"); + return false; + } + + // Decode it + int autoBackup, updateStoreInterval, minimumFileAge, maxUploadWait; + if(::sscanf(configSummary.c_str(), "bbackupd: %d %d %d %d", + &autoBackup, &updateStoreInterval, + &minimumFileAge, &maxUploadWait) != 4) + { + BOX_ERROR("Config summary didn't decode"); + return false; + } + + std::string cmds; + bool expectResponse; + + if (rCmd != "") + { + cmds = rCmd; + cmds += "\nquit\n"; + expectResponse = true; + } + else + { + cmds = "quit\n"; + expectResponse = false; + } + + connection.Write(cmds.c_str(), cmds.size()); + + // Read the response + std::string line; + bool statusOk = !expectResponse; + + while (expectResponse && !getLine.IsEOF() && getLine.GetLine(line)) + { + // Is this an OK or error line? + if (line == "ok") + { + statusOk = true; + } + else if (line == "error") + { + BOX_ERROR(rCmd); + break; + } + else + { + BOX_WARNING("Unexpected response to command '" << + rCmd << "': " << line) + } + } + + return statusOk; +} + +inline bool HUPServer(int pid) +{ + return SendCommands("reload"); +} + +inline bool KillServerInternal(int pid) +{ + HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, false, pid); + if (hProcess == NULL) + { + BOX_ERROR("Failed to open process " << pid << ": " << + GetErrorMessage(GetLastError())); + return false; + } + + if (!TerminateProcess(hProcess, 1)) + { + BOX_ERROR("Failed to terminate process " << pid << ": " << + GetErrorMessage(GetLastError())); + CloseHandle(hProcess); + return false; + } + + CloseHandle(hProcess); + return true; +} + +#else // !WIN32 + +inline bool HUPServer(int pid) +{ + if(pid == 0) return false; + return ::kill(pid, SIGHUP) == 0; +} + +inline bool KillServerInternal(int pid) +{ + if(pid == 0 || pid == -1) return false; + bool killed = (::kill(pid, SIGTERM) == 0); + if (!killed) + { + BOX_ERROR("Failed to kill process " << pid << ": " << + strerror(errno)); + } + TEST_THAT(killed); + return killed; +} + +#endif // WIN32 + +inline bool KillServer(int pid) +{ + if (!KillServerInternal(pid)) + { + return false; + } + + for (int i = 0; i < 30; i++) + { + if (i == 0) + { + printf("Waiting for server to die: "); + } + + printf("."); + fflush(stdout); + + if (!ServerIsAlive(pid)) break; + ::sleep(1); + if (!ServerIsAlive(pid)) break; + } + + if (!ServerIsAlive(pid)) + { + printf(" done.\n"); + } + else + { + printf(" failed!\n"); + } + + fflush(stdout); + + return !ServerIsAlive(pid); +} + +#endif // SERVER_CONTROL_H diff --git a/lib/server/ServerException.h b/lib/server/ServerException.h index f6e59371..2d44271f 100644 --- a/lib/server/ServerException.h +++ b/lib/server/ServerException.h @@ -1,4 +1,4 @@ -// distribution boxbackup-0.10 (svn version: 494) +// distribution boxbackup-0.11rc1 (svn version: 2023_2024) // // Copyright (c) 2003 - 2006 // Ben Summers and contributors. All rights reserved. diff --git a/lib/server/ServerStream.h b/lib/server/ServerStream.h index e1136c72..0e81d05e 100644 --- a/lib/server/ServerStream.h +++ b/lib/server/ServerStream.h @@ -1,4 +1,4 @@ -// distribution boxbackup-0.10 (svn version: 494) +// distribution boxbackup-0.11rc1 (svn version: 2023_2024) // // Copyright (c) 2003 - 2006 // Ben Summers and contributors. All rights reserved. @@ -52,7 +52,6 @@ #include <errno.h> #ifndef WIN32 - #include <syslog.h> #include <sys/wait.h> #endif @@ -94,9 +93,11 @@ public: return "generic-stream-server"; } + virtual void OnIdle() { } + virtual void Run() { - // Set process title as appropraite + // Set process title as appropriate SetProcessTitle(ForkToHandleRequests?"server":"idle"); // Handle exceptions and child task quitting gracefully. @@ -109,7 +110,10 @@ public: { if(childExit) { - ::syslog(LOG_ERR, "in server child, exception %s (%d/%d) -- terminating child", e.what(), e.GetType(), e.GetSubType()); + BOX_ERROR("Error in child process, " + "terminating connection: exception " << + e.what() << "(" << e.GetType() << + "/" << e.GetSubType() << ")"); _exit(1); } else throw; @@ -118,7 +122,9 @@ public: { if(childExit) { - ::syslog(LOG_ERR, "in server child, exception %s -- terminating child", e.what()); + BOX_ERROR("Error in child process, " + "terminating connection: exception " << + e.what()); _exit(1); } else throw; @@ -127,7 +133,9 @@ public: { if(childExit) { - ::syslog(LOG_ERR, "in server child, unknown exception -- terminating child"); + BOX_ERROR("Error in child process, " + "terminating connection: " + "unknown exception"); _exit(1); } else throw; @@ -206,16 +214,22 @@ public: } else if(c[0] == "unix") { - // Check arguments size - if(c.size() != 2) - { - THROW_EXCEPTION(ServerException, ServerStreamBadListenAddrs) - } + #ifdef WIN32 + BOX_WARNING("Ignoring request to listen on a Unix socket on Windows: " << addrlist[a]); + delete psocket; + psocket = NULL; + #else + // Check arguments size + if(c.size() != 2) + { + THROW_EXCEPTION(ServerException, ServerStreamBadListenAddrs) + } - // unlink anything there - ::unlink(c[1].c_str()); - - psocket->Listen(Socket::TypeUNIX, c[1].c_str()); + // unlink anything there + ::unlink(c[1].c_str()); + + psocket->Listen(Socket::TypeUNIX, c[1].c_str()); + #endif // WIN32 } else { @@ -223,8 +237,11 @@ public: THROW_EXCEPTION(ServerException, ServerStreamBadListenAddrs) } - // Add to list of sockets - mSockets.push_back(psocket); + if (psocket != NULL) + { + // Add to list of sockets + mSockets.push_back(psocket); + } } catch(...) { @@ -232,8 +249,11 @@ public: throw; } - // Add to the list of things to wait on - connectionWait.Add(psocket); + if (psocket != NULL) + { + // Add to the list of things to wait on + connectionWait.Add(psocket); + } } } @@ -245,7 +265,8 @@ public: if(psocket) { - // Get the incomming connection (with zero wait time) + // Get the incoming connection + // (with zero wait time) std::string logMessage; std::auto_ptr<StreamType> connection(psocket->Accept(0, &logMessage)); @@ -253,7 +274,8 @@ public: if(connection.get()) { // Since this is a template parameter, the if() will be optimised out by the compiler - if(ForkToHandleRequests) + #ifndef WIN32 // no fork on Win32 + if(ForkToHandleRequests && !IsSingleProcess()) { pid_t pid = ::fork(); switch(pid) @@ -289,20 +311,26 @@ public: } // Log it - ::syslog(LOG_INFO, "%s (handling in child %d)", logMessage.c_str(), pid); + BOX_WARNING("Message from child process " << pid << ": " << logMessage); } else { - // Just handle in this connection + #endif // !WIN32 + // Just handle in this process SetProcessTitle("handling"); HandleConnection(*connection); SetProcessTitle("idle"); + #ifndef WIN32 } + #endif // !WIN32 } } - + + OnIdle(); + + #ifndef WIN32 // Clean up child processes (if forking daemon) - if(ForkToHandleRequests) + if(ForkToHandleRequests && !IsSingleProcess()) { int status = 0; int p = 0; @@ -315,6 +343,7 @@ public: } } while(p > 0); } + #endif // !WIN32 } } catch(...) @@ -335,11 +364,15 @@ public: virtual void Connection(StreamType &rStream) = 0; protected: - // For checking code in dervied classes -- use if you have an algorithm which + // For checking code in derived classes -- use if you have an algorithm which // depends on the forking model in case someone changes it later. bool WillForkToHandleRequests() { - return ForkToHandleRequests; + #ifdef WIN32 + return false; + #else + return ForkToHandleRequests && !IsSingleProcess(); + #endif // WIN32 } private: diff --git a/lib/server/ServerTLS.h b/lib/server/ServerTLS.h index be53af4c..a74f0087 100644 --- a/lib/server/ServerTLS.h +++ b/lib/server/ServerTLS.h @@ -1,4 +1,4 @@ -// distribution boxbackup-0.10 (svn version: 494) +// distribution boxbackup-0.11rc1 (svn version: 2023_2024) // // Copyright (c) 2003 - 2006 // Ben Summers and contributors. All rights reserved. diff --git a/lib/server/Socket.cpp b/lib/server/Socket.cpp index 0939343d..95347af6 100644 --- a/lib/server/Socket.cpp +++ b/lib/server/Socket.cpp @@ -1,4 +1,4 @@ -// distribution boxbackup-0.10 (svn version: 494) +// distribution boxbackup-0.11rc1 (svn version: 2023_2024) // // Copyright (c) 2003 - 2006 // Ben Summers and contributors. All rights reserved. @@ -55,7 +55,6 @@ #ifndef WIN32 #include <sys/socket.h> #include <netdb.h> -#include <syslog.h> #include <netinet/in.h> #include <arpa/inet.h> #endif @@ -162,18 +161,20 @@ void Socket::LogIncomingConnection(const struct sockaddr *addr, socklen_t addrle switch(addr->sa_family) { case AF_UNIX: - ::syslog(LOG_INFO, "Incoming connection from local (UNIX socket)"); + BOX_INFO("Incoming connection from local (UNIX socket)"); break; case AF_INET: { sockaddr_in *a = (sockaddr_in*)addr; - ::syslog(LOG_INFO, "Incoming connection from %s port %d", inet_ntoa(a->sin_addr), ntohs(a->sin_port)); + BOX_INFO("Incoming connection from " << + inet_ntoa(a->sin_addr) << " port " << + ntohs(a->sin_port)); } break; default: - ::syslog(LOG_INFO, "Incoming connection of unknown type"); + BOX_WARNING("Incoming connection of unknown type"); break; } } diff --git a/lib/server/Socket.h b/lib/server/Socket.h index fc3a3a0c..f262bc3a 100644 --- a/lib/server/Socket.h +++ b/lib/server/Socket.h @@ -1,4 +1,4 @@ -// distribution boxbackup-0.10 (svn version: 494) +// distribution boxbackup-0.11rc1 (svn version: 2023_2024) // // Copyright (c) 2003 - 2006 // Ben Summers and contributors. All rights reserved. diff --git a/lib/server/SocketListen.h b/lib/server/SocketListen.h index 41a9fc3f..7b99631b 100644 --- a/lib/server/SocketListen.h +++ b/lib/server/SocketListen.h @@ -1,4 +1,4 @@ -// distribution boxbackup-0.10 (svn version: 494) +// distribution boxbackup-0.11rc1 (svn version: 2023_2024) // // Copyright (c) 2003 - 2006 // Ben Summers and contributors. All rights reserved. diff --git a/lib/server/SocketStream.cpp b/lib/server/SocketStream.cpp index 49bf18b2..38370598 100644 --- a/lib/server/SocketStream.cpp +++ b/lib/server/SocketStream.cpp @@ -1,4 +1,4 @@ -// distribution boxbackup-0.10 (svn version: 494) +// distribution boxbackup-0.11rc1 (svn version: 2023_2024) // // Copyright (c) 2003 - 2006 // Ben Summers and contributors. All rights reserved. @@ -53,6 +53,7 @@ #include <sys/types.h> #include <errno.h> +#include <string.h> #ifndef WIN32 #include <poll.h> @@ -74,7 +75,7 @@ // // -------------------------------------------------------------------------- SocketStream::SocketStream() - : mSocketHandle(-1), + : mSocketHandle(INVALID_SOCKET_VALUE), mReadClosed(false), mWriteClosed(false), mBytesRead(0), @@ -123,7 +124,7 @@ SocketStream::SocketStream(const SocketStream &rToCopy) { THROW_EXCEPTION(ServerException, BadSocketHandle); } - if(mSocketHandle == -1) + if(mSocketHandle == INVALID_SOCKET_VALUE) { THROW_EXCEPTION(ServerException, DupError); } @@ -139,7 +140,7 @@ SocketStream::SocketStream(const SocketStream &rToCopy) // -------------------------------------------------------------------------- SocketStream::~SocketStream() { - if(mSocketHandle != -1) + if(mSocketHandle != INVALID_SOCKET_VALUE) { Close(); } @@ -155,7 +156,10 @@ SocketStream::~SocketStream() // -------------------------------------------------------------------------- void SocketStream::Attach(int socket) { - if(mSocketHandle != -1) {THROW_EXCEPTION(ServerException, SocketAlreadyOpen)} + if(mSocketHandle != INVALID_SOCKET_VALUE) + { + THROW_EXCEPTION(ServerException, SocketAlreadyOpen) + } mSocketHandle = socket; ResetCounters(); @@ -172,7 +176,10 @@ void SocketStream::Attach(int socket) // -------------------------------------------------------------------------- void SocketStream::Open(int Type, const char *Name, int Port) { - if(mSocketHandle != -1) {THROW_EXCEPTION(ServerException, SocketAlreadyOpen)} + if(mSocketHandle != INVALID_SOCKET_VALUE) + { + THROW_EXCEPTION(ServerException, SocketAlreadyOpen) + } // Setup parameters based on type, looking up names if required int sockDomain = 0; @@ -182,7 +189,7 @@ void SocketStream::Open(int Type, const char *Name, int Port) // Create the socket mSocketHandle = ::socket(sockDomain, SOCK_STREAM, 0 /* let OS choose protocol */); - if(mSocketHandle == -1) + if(mSocketHandle == INVALID_SOCKET_VALUE) { THROW_EXCEPTION(ServerException, SocketOpenError) } @@ -192,13 +199,29 @@ void SocketStream::Open(int Type, const char *Name, int Port) { // Dispose of the socket #ifdef WIN32 + DWORD err = WSAGetLastError(); ::closesocket(mSocketHandle); #else + int err = errno; ::close(mSocketHandle); #endif - mSocketHandle = -1; + +#ifdef WIN32 + BOX_ERROR("Failed to connect to socket (type " << Type << + ", name " << Name << ", port " << Port << "): " << + GetErrorMessage(err) + ); +#else + BOX_ERROR("Failed to connect to socket (type " << Type << + ", name " << Name << ", port " << Port << "): " << + strerror(err) << " (" << err << ")" + ); +#endif + + mSocketHandle = INVALID_SOCKET_VALUE; THROW_EXCEPTION(ConnectionException, Conn_SocketConnectError) } + ResetCounters(); } @@ -212,7 +235,10 @@ void SocketStream::Open(int Type, const char *Name, int Port) // -------------------------------------------------------------------------- int SocketStream::Read(void *pBuffer, int NBytes, int Timeout) { - if(mSocketHandle == -1) {THROW_EXCEPTION(ServerException, BadSocketHandle)} + if(mSocketHandle == INVALID_SOCKET_VALUE) + { + THROW_EXCEPTION(ServerException, BadSocketHandle) + } if(Timeout != IOStream::TimeOutInfinite) { @@ -285,7 +311,10 @@ int SocketStream::Read(void *pBuffer, int NBytes, int Timeout) // -------------------------------------------------------------------------- void SocketStream::Write(const void *pBuffer, int NBytes) { - if(mSocketHandle == -1) {THROW_EXCEPTION(ServerException, BadSocketHandle)} + if(mSocketHandle == INVALID_SOCKET_VALUE) + { + THROW_EXCEPTION(ServerException, BadSocketHandle) + } // Buffer in byte sized type. ASSERT(sizeof(char) == 1); @@ -349,7 +378,10 @@ void SocketStream::Write(const void *pBuffer, int NBytes) // -------------------------------------------------------------------------- void SocketStream::Close() { - if(mSocketHandle == -1) {THROW_EXCEPTION(ServerException, BadSocketHandle)} + if(mSocketHandle == INVALID_SOCKET_VALUE) + { + THROW_EXCEPTION(ServerException, BadSocketHandle) + } #ifdef WIN32 if(::closesocket(mSocketHandle) == -1) #else @@ -358,7 +390,7 @@ void SocketStream::Close() { THROW_EXCEPTION(ServerException, SocketCloseError) } - mSocketHandle = -1; + mSocketHandle = INVALID_SOCKET_VALUE; } // -------------------------------------------------------------------------- @@ -371,7 +403,10 @@ void SocketStream::Close() // -------------------------------------------------------------------------- void SocketStream::Shutdown(bool Read, bool Write) { - if(mSocketHandle == -1) {THROW_EXCEPTION(ServerException, BadSocketHandle)} + if(mSocketHandle == INVALID_SOCKET_VALUE) + { + THROW_EXCEPTION(ServerException, BadSocketHandle) + } // Do anything? if(!Read && !Write) return; @@ -426,7 +461,10 @@ bool SocketStream::StreamClosed() // -------------------------------------------------------------------------- tOSSocketHandle SocketStream::GetSocketHandle() { - if(mSocketHandle == -1) {THROW_EXCEPTION(ServerException, BadSocketHandle)} + if(mSocketHandle == INVALID_SOCKET_VALUE) + { + THROW_EXCEPTION(ServerException, BadSocketHandle) + } return mSocketHandle; } diff --git a/lib/server/SocketStream.h b/lib/server/SocketStream.h index 17a2e4d7..efe3f9ce 100644 --- a/lib/server/SocketStream.h +++ b/lib/server/SocketStream.h @@ -1,4 +1,4 @@ -// distribution boxbackup-0.10 (svn version: 494) +// distribution boxbackup-0.11rc1 (svn version: 2023_2024) // // Copyright (c) 2003 - 2006 // Ben Summers and contributors. All rights reserved. @@ -52,8 +52,10 @@ #ifdef WIN32 typedef SOCKET tOSSocketHandle; + #define INVALID_SOCKET_VALUE (tOSSocketHandle)(-1) #else typedef int tOSSocketHandle; + #define INVALID_SOCKET_VALUE -1 #endif // -------------------------------------------------------------------------- @@ -103,6 +105,7 @@ public: off_t GetBytesRead() const {return mBytesRead;} off_t GetBytesWritten() const {return mBytesWritten;} void ResetCounters() {mBytesRead = mBytesWritten = 0;} + bool IsOpened() { return mSocketHandle != INVALID_SOCKET_VALUE; } }; #endif // SOCKETSTREAM__H diff --git a/lib/server/SocketStreamTLS.cpp b/lib/server/SocketStreamTLS.cpp index 0845f868..e7126120 100644 --- a/lib/server/SocketStreamTLS.cpp +++ b/lib/server/SocketStreamTLS.cpp @@ -1,4 +1,4 @@ -// distribution boxbackup-0.10 (svn version: 494) +// distribution boxbackup-0.11rc1 (svn version: 2023_2024) // // Copyright (c) 2003 - 2006 // Ben Summers and contributors. All rights reserved. @@ -61,6 +61,7 @@ #include "SSLLib.h" #include "ServerException.h" #include "TLSContext.h" +#include "BoxTime.h" #include "MemLeakFindOn.h" @@ -175,8 +176,12 @@ void SocketStreamTLS::Handshake(const TLSContext &rContext, bool IsServer) THROW_EXCEPTION(ServerException, TLSAllocationFailed) } -#ifndef WIN32 // Make the socket non-blocking so timeouts on Read work + +#ifdef WIN32 + u_long nonblocking = 1; + ioctlsocket(socket, FIONBIO, &nonblocking); +#else // !WIN32 // This is more portable than using ioctl with FIONBIO int statusFlags = 0; if(::fcntl(socket, F_GETFL, &statusFlags) < 0 @@ -278,20 +283,30 @@ bool SocketStreamTLS::WaitWhenRetryRequired(int SSLErrorCode, int Timeout) break; } p.revents = 0; - switch(::poll(&p, 1, (Timeout == IOStream::TimeOutInfinite)?INFTIM:Timeout)) + + int64_t start, end; + start = BoxTimeToMilliSeconds(GetCurrentBoxTime()); + end = start + Timeout; + int result; + + do { - case -1: - // error - if(errno == EINTR) + int64_t now = BoxTimeToMilliSeconds(GetCurrentBoxTime()); + int poll_timeout = (int)(end - now); + if (poll_timeout < 0) poll_timeout = 0; + if (Timeout == IOStream::TimeOutInfinite) { - // Signal. Do "time out" - return false; - } - else - { - // Bad! - THROW_EXCEPTION(ServerException, SocketPollError) + poll_timeout = INFTIM; } + result = ::poll(&p, 1, poll_timeout); + } + while(result == -1 && errno == EINTR); + + switch(result) + { + case -1: + // error - Bad! + THROW_EXCEPTION(ServerException, SocketPollError) break; case 0: @@ -347,7 +362,7 @@ int SocketStreamTLS::Read(void *pBuffer, int NBytes, int Timeout) case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_WRITE: - // wait for the requried data + // wait for the required data // Will only get once around this loop, so don't need to calculate timeout values if(WaitWhenRetryRequired(se, Timeout) == false) { diff --git a/lib/server/SocketStreamTLS.h b/lib/server/SocketStreamTLS.h index dc1da5bc..41f78d76 100644 --- a/lib/server/SocketStreamTLS.h +++ b/lib/server/SocketStreamTLS.h @@ -1,4 +1,4 @@ -// distribution boxbackup-0.10 (svn version: 494) +// distribution boxbackup-0.11rc1 (svn version: 2023_2024) // // Copyright (c) 2003 - 2006 // Ben Summers and contributors. All rights reserved. diff --git a/lib/server/TLSContext.cpp b/lib/server/TLSContext.cpp index 87df562c..da0b320c 100644 --- a/lib/server/TLSContext.cpp +++ b/lib/server/TLSContext.cpp @@ -1,4 +1,4 @@ -// distribution boxbackup-0.10 (svn version: 494) +// distribution boxbackup-0.11rc1 (svn version: 2023_2024) // // Copyright (c) 2003 - 2006 // Ben Summers and contributors. All rights reserved. @@ -99,6 +99,11 @@ TLSContext::~TLSContext() // -------------------------------------------------------------------------- void TLSContext::Initialise(bool AsServer, const char *CertificatesFile, const char *PrivateKeyFile, const char *TrustedCAsFile) { + if(mpContext != 0) + { + ::SSL_CTX_free(mpContext); + } + mpContext = ::SSL_CTX_new(AsServer?TLSv1_server_method():TLSv1_client_method()); if(mpContext == NULL) { diff --git a/lib/server/TLSContext.h b/lib/server/TLSContext.h index 408b0039..88013396 100644 --- a/lib/server/TLSContext.h +++ b/lib/server/TLSContext.h @@ -1,4 +1,4 @@ -// distribution boxbackup-0.10 (svn version: 494) +// distribution boxbackup-0.11rc1 (svn version: 2023_2024) // // Copyright (c) 2003 - 2006 // Ben Summers and contributors. All rights reserved. diff --git a/lib/server/WinNamedPipeStream.cpp b/lib/server/WinNamedPipeStream.cpp new file mode 100644 index 00000000..bb264da7 --- /dev/null +++ b/lib/server/WinNamedPipeStream.cpp @@ -0,0 +1,629 @@ +// distribution boxbackup-0.11rc1 (svn version: 2023_2024) +// +// Copyright (c) 2003 - 2006 +// Ben Summers and contributors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. All use of this software and associated advertising materials must +// display the following acknowledgment: +// This product includes software developed by Ben Summers. +// 4. The names of the Authors may not be used to endorse or promote +// products derived from this software without specific prior written +// permission. +// +// [Where legally impermissible the Authors do not disclaim liability for +// direct physical injury or death caused solely by defects in the software +// unless it is modified by a third party.] +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// +// +// -------------------------------------------------------------------------- +// +// File +// Name: WinNamedPipeStream.cpp +// Purpose: I/O stream interface for Win32 named pipes +// Created: 2005/12/07 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#ifdef WIN32 + +#ifdef HAVE_UNISTD_H + #include <unistd.h> +#endif + +#include <sys/types.h> +#include <errno.h> +#include <windows.h> + +#include "WinNamedPipeStream.h" +#include "ServerException.h" +#include "CommonException.h" +#include "Socket.h" + +#include "MemLeakFindOn.h" + +std::string WinNamedPipeStream::sPipeNamePrefix = "\\\\.\\pipe\\"; + +// -------------------------------------------------------------------------- +// +// Function +// Name: WinNamedPipeStream::WinNamedPipeStream() +// Purpose: Constructor (create stream ready for Open() call) +// Created: 2005/12/07 +// +// -------------------------------------------------------------------------- +WinNamedPipeStream::WinNamedPipeStream() + : mSocketHandle(INVALID_HANDLE_VALUE), + mReadableEvent(INVALID_HANDLE_VALUE), + mBytesInBuffer(0), + mReadClosed(false), + mWriteClosed(false), + mIsServer(false), + mIsConnected(false) +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: WinNamedPipeStream::~WinNamedPipeStream() +// Purpose: Destructor, closes stream if open +// Created: 2005/12/07 +// +// -------------------------------------------------------------------------- +WinNamedPipeStream::~WinNamedPipeStream() +{ + if (mSocketHandle != INVALID_HANDLE_VALUE) + { + try + { + Close(); + } + catch (std::exception &e) + { + BOX_ERROR("Caught exception while destroying " + "named pipe, ignored: " << e.what()); + } + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: WinNamedPipeStream::Accept(const std::string& rName) +// Purpose: Creates a new named pipe with the given name, +// and wait for a connection on it +// Created: 2005/12/07 +// +// -------------------------------------------------------------------------- +void WinNamedPipeStream::Accept(const std::string& rName) +{ + if (mSocketHandle != INVALID_HANDLE_VALUE || mIsConnected) + { + THROW_EXCEPTION(ServerException, SocketAlreadyOpen) + } + + std::string socket = sPipeNamePrefix + rName; + + mSocketHandle = CreateNamedPipeA( + socket.c_str(), // pipe name + PIPE_ACCESS_DUPLEX | // read/write access + FILE_FLAG_OVERLAPPED, // enabled overlapped I/O + PIPE_TYPE_BYTE | // message type pipe + PIPE_READMODE_BYTE | // message-read mode + PIPE_WAIT, // blocking mode + 1, // max. instances + 4096, // output buffer size + 4096, // input buffer size + NMPWAIT_USE_DEFAULT_WAIT, // client time-out + NULL); // default security attribute + + if (mSocketHandle == INVALID_HANDLE_VALUE) + { + BOX_ERROR("Failed to CreateNamedPipeA(" << socket << "): " << + GetErrorMessage(GetLastError())); + THROW_EXCEPTION(ServerException, SocketOpenError) + } + + bool connected = ConnectNamedPipe(mSocketHandle, (LPOVERLAPPED) NULL); + + if (!connected) + { + BOX_ERROR("Failed to ConnectNamedPipe(" << socket << "): " << + GetErrorMessage(GetLastError())); + Close(); + THROW_EXCEPTION(ServerException, SocketOpenError) + } + + mBytesInBuffer = 0; + mReadClosed = false; + mWriteClosed = false; + mIsServer = true; // must flush and disconnect before closing + mIsConnected = true; + + // create the Readable event + mReadableEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + + if (mReadableEvent == INVALID_HANDLE_VALUE) + { + BOX_ERROR("Failed to create the Readable event: " << + GetErrorMessage(GetLastError())); + Close(); + THROW_EXCEPTION(CommonException, Internal) + } + + // initialise the OVERLAPPED structure + memset(&mReadOverlap, 0, sizeof(mReadOverlap)); + mReadOverlap.hEvent = mReadableEvent; + + // start the first overlapped read + if (!ReadFile(mSocketHandle, mReadBuffer, sizeof(mReadBuffer), + NULL, &mReadOverlap)) + { + DWORD err = GetLastError(); + + if (err != ERROR_IO_PENDING) + { + BOX_ERROR("Failed to start overlapped read: " << + GetErrorMessage(err)); + Close(); + THROW_EXCEPTION(ConnectionException, + Conn_SocketReadError) + } + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: WinNamedPipeStream::Connect(const std::string& rName) +// Purpose: Opens a connection to a listening named pipe +// Created: 2005/12/07 +// +// -------------------------------------------------------------------------- +void WinNamedPipeStream::Connect(const std::string& rName) +{ + if (mSocketHandle != INVALID_HANDLE_VALUE || mIsConnected) + { + THROW_EXCEPTION(ServerException, SocketAlreadyOpen) + } + + std::string socket = sPipeNamePrefix + rName; + + mSocketHandle = CreateFileA( + socket.c_str(), // pipe name + GENERIC_READ | // read and write access + GENERIC_WRITE, + 0, // no sharing + NULL, // default security attributes + OPEN_EXISTING, + 0, // default attributes + NULL); // no template file + + if (mSocketHandle == INVALID_HANDLE_VALUE) + { + DWORD err = GetLastError(); + if (err == ERROR_PIPE_BUSY) + { + BOX_ERROR("Failed to connect to backup daemon: " + "it is busy with another connection"); + } + else + { + BOX_ERROR("Failed to connect to backup daemon: " << + GetErrorMessage(err)); + } + THROW_EXCEPTION(ServerException, SocketOpenError) + } + + mReadClosed = false; + mWriteClosed = false; + mIsServer = false; // just close the socket + mIsConnected = true; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: WinNamedPipeStream::Read(void *pBuffer, int NBytes) +// Purpose: Reads data from stream. Maybe returns less than asked for. +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +int WinNamedPipeStream::Read(void *pBuffer, int NBytes, int Timeout) +{ + // TODO no support for timeouts yet + if (Timeout != IOStream::TimeOutInfinite) + { + THROW_EXCEPTION(CommonException, AssertFailed) + } + + if (mSocketHandle == INVALID_HANDLE_VALUE || !mIsConnected) + { + THROW_EXCEPTION(ServerException, BadSocketHandle) + } + + if (mReadClosed) + { + THROW_EXCEPTION(ConnectionException, SocketShutdownError) + } + + // ensure safe to cast NBytes to unsigned + if (NBytes < 0) + { + THROW_EXCEPTION(CommonException, AssertFailed) + } + + DWORD NumBytesRead; + + if (mIsServer) + { + // satisfy from buffer if possible, to avoid + // blocking on read. + bool needAnotherRead = false; + if (mBytesInBuffer == 0) + { + // overlapped I/O completed successfully? + // (wait if needed) + + if (GetOverlappedResult(mSocketHandle, + &mReadOverlap, &NumBytesRead, TRUE)) + { + needAnotherRead = true; + } + else + { + DWORD err = GetLastError(); + + if (err == ERROR_HANDLE_EOF) + { + mReadClosed = true; + } + else + { + if (err == ERROR_BROKEN_PIPE) + { + BOX_ERROR("Control client " + "disconnected"); + } + else + { + BOX_ERROR("Failed to wait for " + "ReadFile to complete: " + << GetErrorMessage(err)); + } + + Close(); + THROW_EXCEPTION(ConnectionException, + Conn_SocketReadError) + } + } + } + else + { + NumBytesRead = 0; + } + + size_t BytesToCopy = NumBytesRead + mBytesInBuffer; + size_t BytesRemaining = 0; + + if (BytesToCopy > (size_t)NBytes) + { + BytesRemaining = BytesToCopy - NBytes; + BytesToCopy = NBytes; + } + + memcpy(pBuffer, mReadBuffer, BytesToCopy); + memmove(mReadBuffer, mReadBuffer + BytesToCopy, BytesRemaining); + + mBytesInBuffer = BytesRemaining; + NumBytesRead = BytesToCopy; + + if (needAnotherRead) + { + // reinitialise the OVERLAPPED structure + memset(&mReadOverlap, 0, sizeof(mReadOverlap)); + mReadOverlap.hEvent = mReadableEvent; + } + + // start the next overlapped read + if (needAnotherRead && !ReadFile(mSocketHandle, + mReadBuffer + mBytesInBuffer, + sizeof(mReadBuffer) - mBytesInBuffer, + NULL, &mReadOverlap)) + { + DWORD err = GetLastError(); + if (err == ERROR_IO_PENDING) + { + // Don't reset yet, there might be data + // in the buffer waiting to be read, + // will check below. + // ResetEvent(mReadableEvent); + } + else if (err == ERROR_HANDLE_EOF) + { + mReadClosed = true; + } + else if (err == ERROR_BROKEN_PIPE) + { + BOX_ERROR("Control client disconnected"); + mReadClosed = true; + } + else + { + BOX_ERROR("Failed to start overlapped read: " + << GetErrorMessage(err)); + Close(); + THROW_EXCEPTION(ConnectionException, + Conn_SocketReadError) + } + } + + // If the read succeeded immediately, leave the event + // signaled, so that we will be called again to process + // the newly read data and start another overlapped read. + if (needAnotherRead && !mReadClosed) + { + // leave signalled + } + else if (!needAnotherRead && mBytesInBuffer > 0) + { + // leave signalled + } + else + { + // nothing left to read, reset the event + ResetEvent(mReadableEvent); + // FIXME: a pending read could have signalled + // the event (again) while we were busy reading. + // that signal would be lost, and the reading + // thread would block. Should be pretty obvious + // if this happens in practice: control client + // hangs. + } + } + else + { + if (!ReadFile( + mSocketHandle, // pipe handle + pBuffer, // buffer to receive reply + NBytes, // size of buffer + &NumBytesRead, // number of bytes read + NULL)) // not overlapped + { + DWORD err = GetLastError(); + + Close(); + + // ERROR_NO_DATA is a strange name for + // "The pipe is being closed". No exception wanted. + + if (err == ERROR_NO_DATA || + err == ERROR_PIPE_NOT_CONNECTED) + { + NumBytesRead = 0; + } + else + { + BOX_ERROR("Failed to read from control socket: " + << GetErrorMessage(err)); + THROW_EXCEPTION(ConnectionException, + Conn_SocketReadError) + } + } + + // Closed for reading at EOF? + if (NumBytesRead == 0) + { + mReadClosed = true; + } + } + + return NumBytesRead; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: WinNamedPipeStream::Write(void *pBuffer, int NBytes) +// Purpose: Writes data, blocking until it's all done. +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void WinNamedPipeStream::Write(const void *pBuffer, int NBytes) +{ + if (mSocketHandle == INVALID_HANDLE_VALUE || !mIsConnected) + { + THROW_EXCEPTION(ServerException, BadSocketHandle) + } + + // Buffer in byte sized type. + ASSERT(sizeof(char) == 1); + const char *pByteBuffer = (char *)pBuffer; + + int NumBytesWrittenTotal = 0; + + while (NumBytesWrittenTotal < NBytes) + { + DWORD NumBytesWrittenThisTime = 0; + + bool Success = WriteFile( + mSocketHandle, // pipe handle + pByteBuffer + NumBytesWrittenTotal, // message + NBytes - NumBytesWrittenTotal, // message length + &NumBytesWrittenThisTime, // bytes written this time + NULL); // not overlapped + + if (!Success) + { + DWORD err = GetLastError(); + BOX_ERROR("Failed to write to control socket: " << + GetErrorMessage(err)); + Close(); + + // ERROR_NO_DATA is a strange name for + // "The pipe is being closed". No exception wanted. + + if (err == ERROR_NO_DATA) + { + return; + } + else + { + THROW_EXCEPTION(ConnectionException, + Conn_SocketWriteError) + } + } + + NumBytesWrittenTotal += NumBytesWrittenThisTime; + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: WinNamedPipeStream::Close() +// Purpose: Closes connection to remote socket +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void WinNamedPipeStream::Close() +{ + if (mSocketHandle == INVALID_HANDLE_VALUE && mIsConnected) + { + BOX_ERROR("Named pipe: inconsistent connected state"); + mIsConnected = false; + } + + if (mSocketHandle == INVALID_HANDLE_VALUE) + { + THROW_EXCEPTION(ServerException, BadSocketHandle) + } + + if (mIsServer) + { + if (!CancelIo(mSocketHandle)) + { + BOX_ERROR("Failed to cancel outstanding I/O: " << + GetErrorMessage(GetLastError())); + } + + if (mReadableEvent == INVALID_HANDLE_VALUE) + { + BOX_ERROR("Failed to destroy Readable event: " + "invalid handle"); + } + else if (!CloseHandle(mReadableEvent)) + { + BOX_ERROR("Failed to destroy Readable event: " << + GetErrorMessage(GetLastError())); + } + + mReadableEvent = INVALID_HANDLE_VALUE; + + if (!FlushFileBuffers(mSocketHandle)) + { + BOX_ERROR("Failed to FlushFileBuffers: " << + GetErrorMessage(GetLastError())); + } + + if (!DisconnectNamedPipe(mSocketHandle)) + { + DWORD err = GetLastError(); + if (err != ERROR_PIPE_NOT_CONNECTED) + { + BOX_ERROR("Failed to DisconnectNamedPipe: " << + GetErrorMessage(err)); + } + } + + mIsServer = false; + } + + bool result = CloseHandle(mSocketHandle); + + mSocketHandle = INVALID_HANDLE_VALUE; + mIsConnected = false; + mReadClosed = true; + mWriteClosed = true; + + if (!result) + { + BOX_ERROR("Failed to CloseHandle: " << + GetErrorMessage(GetLastError())); + THROW_EXCEPTION(ServerException, SocketCloseError) + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: WinNamedPipeStream::StreamDataLeft() +// Purpose: Still capable of reading data? +// Created: 2003/08/02 +// +// -------------------------------------------------------------------------- +bool WinNamedPipeStream::StreamDataLeft() +{ + return !mReadClosed; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: WinNamedPipeStream::StreamClosed() +// Purpose: Connection been closed? +// Created: 2003/08/02 +// +// -------------------------------------------------------------------------- +bool WinNamedPipeStream::StreamClosed() +{ + return mWriteClosed; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: IOStream::WriteAllBuffered() +// Purpose: Ensures that any data which has been buffered is written to the stream +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +void WinNamedPipeStream::WriteAllBuffered() +{ + if (mSocketHandle == INVALID_HANDLE_VALUE || !mIsConnected) + { + THROW_EXCEPTION(ServerException, BadSocketHandle) + } + + if (!FlushFileBuffers(mSocketHandle)) + { + BOX_ERROR("Failed to FlushFileBuffers: " << + GetErrorMessage(GetLastError())); + } +} + + +#endif // WIN32 diff --git a/lib/server/WinNamedPipeStream.h b/lib/server/WinNamedPipeStream.h new file mode 100644 index 00000000..c4ec1f3f --- /dev/null +++ b/lib/server/WinNamedPipeStream.h @@ -0,0 +1,106 @@ +// distribution boxbackup-0.11rc1 (svn version: 2023_2024) +// +// Copyright (c) 2003 - 2006 +// Ben Summers and contributors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. All use of this software and associated advertising materials must +// display the following acknowledgment: +// This product includes software developed by Ben Summers. +// 4. The names of the Authors may not be used to endorse or promote +// products derived from this software without specific prior written +// permission. +// +// [Where legally impermissible the Authors do not disclaim liability for +// direct physical injury or death caused solely by defects in the software +// unless it is modified by a third party.] +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// +// +// -------------------------------------------------------------------------- +// +// File +// Name: WinNamedPipeStream.h +// Purpose: I/O stream interface for Win32 named pipes +// Created: 2005/12/07 +// +// -------------------------------------------------------------------------- + +#if ! defined WINNAMEDPIPESTREAM__H && defined WIN32 +#define WINNAMEDPIPESTREAM__H + +#include "IOStream.h" + +// -------------------------------------------------------------------------- +// +// Class +// Name: WinNamedPipeStream +// Purpose: I/O stream interface for Win32 named pipes +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +class WinNamedPipeStream : public IOStream +{ +public: + WinNamedPipeStream(); + ~WinNamedPipeStream(); + + // server side - create the named pipe and listen for connections + void Accept(const std::string& rName); + + // client side - connect to a waiting server + void Connect(const std::string& rName); + + // both sides + virtual int Read(void *pBuffer, int NBytes, + int Timeout = IOStream::TimeOutInfinite); + virtual void Write(const void *pBuffer, int NBytes); + virtual void WriteAllBuffered(); + virtual void Close(); + virtual bool StreamDataLeft(); + virtual bool StreamClosed(); + bool IsConnected() { return mIsConnected; } + HANDLE GetSocketHandle() { return mSocketHandle; } + HANDLE GetReadableEvent() { return mReadableEvent; } + +protected: + void MarkAsReadClosed() {mReadClosed = true;} + void MarkAsWriteClosed() {mWriteClosed = true;} + +private: + WinNamedPipeStream(const WinNamedPipeStream &rToCopy) + { /* do not call */ } + + HANDLE mSocketHandle; + HANDLE mReadableEvent; + OVERLAPPED mReadOverlap; + uint8_t mReadBuffer[4096]; + size_t mBytesInBuffer; + bool mReadClosed; + bool mWriteClosed; + bool mIsServer; + bool mIsConnected; + + static std::string sPipeNamePrefix; +}; + +#endif // WINNAMEDPIPESTREAM__H diff --git a/lib/server/makeprotocol.pl b/lib/server/makeprotocol.pl index 71f2b46a..35fb3ad0 100755 --- a/lib/server/makeprotocol.pl +++ b/lib/server/makeprotocol.pl @@ -1,5 +1,5 @@ #!/usr/bin/perl -# distribution boxbackup-0.10 (svn version: 494) +# distribution boxbackup-0.11rc1 (svn version: 2023_2024) # # Copyright (c) 2003 - 2006 # Ben Summers and contributors. All rights reserved. @@ -39,6 +39,9 @@ # use strict; +use lib "../../infrastructure"; +use BoxPlatform; + # Make protocol C++ classes from a protocol description file # built in type info (values are is basic type, C++ typename) @@ -538,11 +541,11 @@ __E if($implement_syslog) { - my ($format,$args) = make_log_strings($cmd); + my ($log) = make_log_strings_framework($cmd); print CPP <<__E; void ${class}LogSysLog(const char *Action) const { - ::syslog(LOG_INFO,"%s $format",Action$args); + BOX_TRACE($log); } __E } @@ -864,8 +867,8 @@ if($implement_filelog || $implement_syslog) } if($implement_filelog) { - $fR .= qq~\tif(mLogToFile) { ::fprintf(mLogToFile, (Size==Protocol::ProtocolStream_SizeUncertain)?"Receiving stream, size uncertain":"Receiving stream, size %d\\n", Size); ::fflush(mLogToFile); }\n~; - $fS .= qq~\tif(mLogToFile) { ::fprintf(mLogToFile, (Size==Protocol::ProtocolStream_SizeUncertain)?"Sending stream, size uncertain":"Sending stream, size %d\\n", Size); ::fflush(mLogToFile); }\n~; + $fR .= qq~\tif(mLogToFile) { ::fprintf(mLogToFile, (Size==Protocol::ProtocolStream_SizeUncertain)?"Receiving stream, size uncertain\\n":"Receiving stream, size %d\\n", Size); ::fflush(mLogToFile); }\n~; + $fS .= qq~\tif(mLogToFile) { ::fprintf(mLogToFile, (Size==Protocol::ProtocolStream_SizeUncertain)?"Sending stream, size uncertain\\n":"Sending stream, size %d\\n", Size); ::fflush(mLogToFile); }\n~; } print CPP <<__E; @@ -1019,8 +1022,15 @@ sub make_log_strings { # need to translate it my ($format,$arg) = @{$log_display_types{$ty}}; - push @str,$format; $arg =~ s/VAR/m$nm/g; + + if ($format eq "0x%llx" and $target_windows) + { + $format = "0x%I64x"; + $arg = "(uint64_t)$arg"; + } + + push @str,$format; push @arg,$arg; } else @@ -1032,4 +1042,45 @@ sub make_log_strings return ($cmd.'('.join(',',@str).')', join(',','',@arg)); } +sub make_log_strings_framework +{ + my ($cmd) = @_; + + my @args; + + for(my $x = 0; $x < $#{$cmd_contents{$cmd}}; $x+=2) + { + my ($ty,$nm) = (${$cmd_contents{$cmd}}[$x], ${$cmd_contents{$cmd}}[$x+1]); + + if(exists $log_display_types{$ty}) + { + # need to translate it + my ($format,$arg) = @{$log_display_types{$ty}}; + $arg =~ s/VAR/m$nm/g; + + if ($format =~ m'x$') + { + $arg = "std::hex << std::showbase " . + "<< $arg << std::dec"; + } + + push @args, $arg; + } + else + { + # is opaque + push @args, '"OPAQUE"'; + } + } + + my $log_cmd = "Action << \" $cmd(\" "; + foreach my $arg (@args) + { + $arg = "<< $arg "; + } + $log_cmd .= join('<< "," ',@args); + $log_cmd .= '<< ")"'; + return $log_cmd; +} + diff --git a/lib/server/makeprotocol.pl.in b/lib/server/makeprotocol.pl.in new file mode 100755 index 00000000..269ff3f5 --- /dev/null +++ b/lib/server/makeprotocol.pl.in @@ -0,0 +1,1048 @@ +#!@PERL@ +use strict; + +use lib "../../infrastructure"; +use BoxPlatform; + +# Make protocol C++ classes from a protocol description file + +# built in type info (values are is basic type, C++ typename) +# may get stuff added to it later if protocol uses extra types +my %translate_type_info = +( + 'int64' => [1, 'int64_t'], + 'int32' => [1, 'int32_t'], + 'int16' => [1, 'int16_t'], + 'int8' => [1, 'int8_t'], + 'bool' => [1, 'bool'], + 'string' => [0, 'std::string'] +); + +# built in instructions for logging various types +# may be added to +my %log_display_types = +( + 'int64' => ['0x%llx', 'VAR'], + 'int32' => ['0x%x', 'VAR'], + 'int16' => ['0x%x', 'VAR'], + 'int8' => ['0x%x', 'VAR'], + 'bool' => ['%s', '((VAR)?"true":"false")'], + 'string' => ['%s', 'VAR.c_str()'] +); + + + +my ($type, $file) = @ARGV; + +if($type ne 'Server' && $type ne 'Client') +{ + die "Neither Server or Client is specified on command line\n"; +} + +open IN, $file or die "Can't open input file $file\n"; + +print "Making $type protocol classes from $file...\n"; + +my @extra_header_files; + +my $implement_syslog = 0; +my $implement_filelog = 0; + +# read attributes +my %attr; +while(<IN>) +{ + # get and clean line + my $l = $_; $l =~ s/#.*\Z//; $l =~ s/\A\s+//; $l =~ s/\s+\Z//; next unless $l =~ m/\S/; + + last if $l eq 'BEGIN_OBJECTS'; + + my ($k,$v) = split /\s+/,$l,2; + + if($k eq 'ClientType') + { + add_type($v) if $type eq 'Client'; + } + elsif($k eq 'ServerType') + { + add_type($v) if $type eq 'Server'; + } + elsif($k eq 'ImplementLog') + { + my ($log_if_type,$log_type) = split /\s+/,$v; + if($type eq $log_if_type) + { + if($log_type eq 'syslog') + { + $implement_syslog = 1; + } + elsif($log_type eq 'file') + { + $implement_filelog = 1; + } + else + { + printf("ERROR: Unknown log type for implementation: $log_type\n"); + exit(1); + } + } + } + elsif($k eq 'LogTypeToText') + { + my ($log_if_type,$type_name,$printf_format,$arg_template) = split /\s+/,$v; + if($type eq $log_if_type) + { + $log_display_types{$type_name} = [$printf_format,$arg_template] + } + } + else + { + $attr{$k} = $v; + } +} + +sub add_type +{ + my ($protocol_name, $cpp_name, $header_file) = split /\s+/,$_[0]; + + $translate_type_info{$protocol_name} = [0, $cpp_name]; + push @extra_header_files, $header_file; +} + +# check attributes +for(qw/Name ServerContextClass IdentString/) +{ + if(!exists $attr{$_}) + { + die "Attribute $_ is required, but not specified\n"; + } +} + +my $protocol_name = $attr{'Name'}; +my ($context_class, $context_class_inc) = split /\s+/,$attr{'ServerContextClass'}; +my $ident_string = $attr{'IdentString'}; + +my $current_cmd = ''; +my %cmd_contents; +my %cmd_attributes; +my %cmd_constants; +my %cmd_id; +my @cmd_list; + +# read in the command definitions +while(<IN>) +{ + # get and clean line + my $l = $_; $l =~ s/#.*\Z//; $l =~ s/\s+\Z//; next unless $l =~ m/\S/; + + # definitions or new command thing? + if($l =~ m/\A\s+/) + { + die "No command defined yet" if $current_cmd eq ''; + + # definition of component + $l =~ s/\A\s+//; + + my ($type,$name,$value) = split /\s+/,$l; + if($type eq 'CONSTANT') + { + push @{$cmd_constants{$current_cmd}},"$name = $value" + } + else + { + push @{$cmd_contents{$current_cmd}},$type,$name; + } + } + else + { + # new command + my ($name,$id,@attributes) = split /\s+/,$l; + $cmd_attributes{$name} = [@attributes]; + $cmd_id{$name} = int($id); + $current_cmd = $name; + push @cmd_list,$name; + } +} + +close IN; + + + +# open files +my $h_filename = 'autogen_'.$protocol_name.'Protocol'.$type.'.h'; +open CPP,'>autogen_'.$protocol_name.'Protocol'.$type.'.cpp'; +open H,">$h_filename"; + +print CPP <<__E; + +// Auto-generated file -- do not edit + +#include "Box.h" +#include "$h_filename" +#include "IOStream.h" + +__E + +if($implement_syslog) +{ + print H <<EOF; +#ifndef WIN32 +#include <syslog.h> +#endif +EOF +} + + +my $guardname = uc 'AUTOGEN_'.$protocol_name.'Protocol'.$type.'_H'; +print H <<__E; + +// Auto-generated file -- do not edit + +#ifndef $guardname +#define $guardname + +#include "Protocol.h" +#include "ProtocolObject.h" +#include "ServerException.h" + +class IOStream; + +__E + +if($implement_filelog) +{ + print H qq~#include <stdio.h>\n~; +} + +# extra headers +for(@extra_header_files) +{ + print H qq~#include "$_"\n~ +} +print H "\n"; + +if($type eq 'Server') +{ + # need utils file for the server + print H '#include "Utils.h"',"\n\n" +} + + +my $derive_objects_from = 'ProtocolObject'; +my $objects_extra_h = ''; +my $objects_extra_cpp = ''; +if($type eq 'Server') +{ + # define the context + print H "class $context_class;\n\n"; + print CPP "#include \"$context_class_inc\"\n\n"; + + # change class we derive the objects from + $derive_objects_from = $protocol_name.'ProtocolObject'; + + $objects_extra_h = <<__E; + virtual std::auto_ptr<ProtocolObject> DoCommand(${protocol_name}ProtocolServer &rProtocol, $context_class &rContext); +__E + $objects_extra_cpp = <<__E; +std::auto_ptr<ProtocolObject> ${derive_objects_from}::DoCommand(${protocol_name}ProtocolServer &rProtocol, $context_class &rContext) +{ + THROW_EXCEPTION(ConnectionException, Conn_Protocol_TriedToExecuteReplyCommand) +} +__E +} + +print CPP qq~#include "MemLeakFindOn.h"\n~; + +if($type eq 'Client' && ($implement_syslog || $implement_filelog)) +{ + # change class we derive the objects from + $derive_objects_from = $protocol_name.'ProtocolObjectCl'; +} +if($implement_syslog) +{ + $objects_extra_h .= <<__E; + virtual void LogSysLog(const char *Action) const = 0; +__E +} +if($implement_filelog) +{ + $objects_extra_h .= <<__E; + virtual void LogFile(const char *Action, FILE *file) const = 0; +__E +} + +if($derive_objects_from ne 'ProtocolObject') +{ + # output a definition for the protocol object derviced class + print H <<__E; +class ${protocol_name}ProtocolServer; + +class $derive_objects_from : public ProtocolObject +{ +public: + $derive_objects_from(); + virtual ~$derive_objects_from(); + $derive_objects_from(const $derive_objects_from &rToCopy); + +$objects_extra_h +}; +__E + + # and some cpp definitions + print CPP <<__E; +${derive_objects_from}::${derive_objects_from}() +{ +} +${derive_objects_from}::~${derive_objects_from}() +{ +} +${derive_objects_from}::${derive_objects_from}(const $derive_objects_from &rToCopy) +{ +} +$objects_extra_cpp +__E +} + + + +my $classname_base = $protocol_name.'Protocol'.$type; + +# output the classes +for my $cmd (@cmd_list) +{ + print H <<__E; +class $classname_base$cmd : public $derive_objects_from +{ +public: + $classname_base$cmd(); + $classname_base$cmd(const $classname_base$cmd &rToCopy); + ~$classname_base$cmd(); + int GetType() const; + enum + { + TypeID = $cmd_id{$cmd} + }; +__E + # constants + if(exists $cmd_constants{$cmd}) + { + print H "\tenum\n\t{\n\t\t"; + print H join(",\n\t\t",@{$cmd_constants{$cmd}}); + print H "\n\t};\n"; + } + # flags + if(obj_is_type($cmd,'EndsConversation')) + { + print H "\tbool IsConversationEnd() const;\n"; + } + if(obj_is_type($cmd,'IsError')) + { + print H "\tbool IsError(int &rTypeOut, int &rSubTypeOut) const;\n"; + } + if($type eq 'Server' && obj_is_type($cmd, 'Command')) + { + print H "\tstd::auto_ptr<ProtocolObject> DoCommand(${protocol_name}ProtocolServer &rProtocol, $context_class &rContext); // IMPLEMENT THIS\n" + } + + # want to be able to read from streams? + my $read_from_streams = (obj_is_type($cmd,'Command') && $type eq 'Server') || (obj_is_type($cmd,'Reply') && $type eq 'Client'); + my $write_to_streams = (obj_is_type($cmd,'Command') && $type eq 'Client') || (obj_is_type($cmd,'Reply') && $type eq 'Server'); + + if($read_from_streams) + { + print H "\tvoid SetPropertiesFromStreamData(Protocol &rProtocol);\n"; + + # write Get functions + for(my $x = 0; $x < $#{$cmd_contents{$cmd}}; $x+=2) + { + my ($ty,$nm) = (${$cmd_contents{$cmd}}[$x], ${$cmd_contents{$cmd}}[$x+1]); + + print H "\t".translate_type_to_arg_type($ty)." Get$nm() {return m$nm;}\n"; + } + } + my $param_con_args = ''; + if($write_to_streams) + { + # extra constructor? + if($#{$cmd_contents{$cmd}} >= 0) + { + my @a; + for(my $x = 0; $x < $#{$cmd_contents{$cmd}}; $x+=2) + { + my ($ty,$nm) = (${$cmd_contents{$cmd}}[$x], ${$cmd_contents{$cmd}}[$x+1]); + + push @a,translate_type_to_arg_type($ty)." $nm"; + } + $param_con_args = join(', ',@a); + print H "\t$classname_base$cmd(".$param_con_args.");\n"; + } + print H "\tvoid WritePropertiesToStreamData(Protocol &rProtocol) const;\n"; + # set functions + for(my $x = 0; $x < $#{$cmd_contents{$cmd}}; $x+=2) + { + my ($ty,$nm) = (${$cmd_contents{$cmd}}[$x], ${$cmd_contents{$cmd}}[$x+1]); + + print H "\tvoid Set$nm(".translate_type_to_arg_type($ty)." $nm) {m$nm = $nm;}\n"; + } + } + + if($implement_syslog) + { + print H "\tvirtual void LogSysLog(const char *Action) const;\n"; + } + if($implement_filelog) + { + print H "\tvirtual void LogFile(const char *Action, FILE *file) const;\n"; + } + + + # write member variables and setup for cpp file + my @def_constructor_list; + my @copy_constructor_list; + my @param_constructor_list; + + print H "private:\n"; + for(my $x = 0; $x < $#{$cmd_contents{$cmd}}; $x+=2) + { + my ($ty,$nm) = (${$cmd_contents{$cmd}}[$x], ${$cmd_contents{$cmd}}[$x+1]); + + print H "\t".translate_type_to_member_type($ty)." m$nm;\n"; + + my ($basic,$typename) = translate_type($ty); + if($basic) + { + push @def_constructor_list, "m$nm(0)"; + } + push @copy_constructor_list, "m$nm(rToCopy.m$nm)"; + push @param_constructor_list, "m$nm($nm)"; + } + + # finish off + print H "};\n\n"; + + # now the cpp file... + my $def_con_vars = join(",\n\t ",@def_constructor_list); + $def_con_vars = "\n\t: ".$def_con_vars if $def_con_vars ne ''; + my $copy_con_vars = join(",\n\t ",@copy_constructor_list); + $copy_con_vars = "\n\t: ".$copy_con_vars if $copy_con_vars ne ''; + my $param_con_vars = join(",\n\t ",@param_constructor_list); + $param_con_vars = "\n\t: ".$param_con_vars if $param_con_vars ne ''; + + my $class = "$classname_base$cmd".'::'; + print CPP <<__E; +$class$classname_base$cmd()$def_con_vars +{ +} +$class$classname_base$cmd(const $classname_base$cmd &rToCopy)$copy_con_vars +{ +} +$class~$classname_base$cmd() +{ +} +int ${class}GetType() const +{ + return $cmd_id{$cmd}; +} +__E + if($read_from_streams) + { + print CPP "void ${class}SetPropertiesFromStreamData(Protocol &rProtocol)\n{\n"; + for(my $x = 0; $x < $#{$cmd_contents{$cmd}}; $x+=2) + { + my ($ty,$nm) = (${$cmd_contents{$cmd}}[$x], ${$cmd_contents{$cmd}}[$x+1]); + if($ty =~ m/\Avector/) + { + print CPP "\trProtocol.ReadVector(m$nm);\n"; + } + else + { + print CPP "\trProtocol.Read(m$nm);\n"; + } + } + print CPP "}\n"; + } + if($write_to_streams) + { + # implement extra constructor? + if($param_con_vars ne '') + { + print CPP "$class$classname_base$cmd($param_con_args)$param_con_vars\n{\n}\n"; + } + print CPP "void ${class}WritePropertiesToStreamData(Protocol &rProtocol) const\n{\n"; + for(my $x = 0; $x < $#{$cmd_contents{$cmd}}; $x+=2) + { + my ($ty,$nm) = (${$cmd_contents{$cmd}}[$x], ${$cmd_contents{$cmd}}[$x+1]); + if($ty =~ m/\Avector/) + { + print CPP "\trProtocol.WriteVector(m$nm);\n"; + } + else + { + print CPP "\trProtocol.Write(m$nm);\n"; + } + } + print CPP "}\n"; + } + if(obj_is_type($cmd,'EndsConversation')) + { + print CPP "bool ${class}IsConversationEnd() const\n{\n\treturn true;\n}\n"; + } + if(obj_is_type($cmd,'IsError')) + { + # get parameters + my ($mem_type,$mem_subtype) = split /,/,obj_get_type_params($cmd,'IsError'); + print CPP <<__E; +bool ${class}IsError(int &rTypeOut, int &rSubTypeOut) const +{ + rTypeOut = m$mem_type; + rSubTypeOut = m$mem_subtype; + return true; +} +__E + } + + if($implement_syslog) + { + my ($log) = make_log_strings_framework($cmd); + print CPP <<__E; +void ${class}LogSysLog(const char *Action) const +{ + BOX_TRACE($log); +} +__E + } + if($implement_filelog) + { + my ($format,$args) = make_log_strings($cmd); + print CPP <<__E; +void ${class}LogFile(const char *Action, FILE *File) const +{ + ::fprintf(File,"%s $format\\n",Action$args); + ::fflush(File); +} +__E + } +} + +# finally, the protocol object itself +print H <<__E; +class $classname_base : public Protocol +{ +public: + $classname_base(IOStream &rStream); + virtual ~$classname_base(); + + std::auto_ptr<$derive_objects_from> Receive(); + void Send(const ${derive_objects_from} &rObject); +__E +if($implement_syslog) +{ + print H "\tvoid SetLogToSysLog(bool Log = false) {mLogToSysLog = Log;}\n"; +} +if($implement_filelog) +{ + print H "\tvoid SetLogToFile(FILE *File = 0) {mLogToFile = File;}\n"; +} +if($type eq 'Server') +{ + # need to put in the conversation function + print H "\tvoid DoServer($context_class &rContext);\n\n"; + # and the send vector thing + print H "\tvoid SendStreamAfterCommand(IOStream *pStream);\n\n"; +} +if($type eq 'Client') +{ + # add plain object taking query functions + my $with_params; + for my $cmd (@cmd_list) + { + if(obj_is_type($cmd,'Command')) + { + my $has_stream = obj_is_type($cmd,'StreamWithCommand'); + my $argextra = $has_stream?', IOStream &rStream':''; + my $queryextra = $has_stream?', rStream':''; + my $reply = obj_get_type_params($cmd,'Command'); + print H "\tstd::auto_ptr<$classname_base$reply> Query(const $classname_base$cmd &rQuery$argextra);\n"; + my @a; + my @na; + for(my $x = 0; $x < $#{$cmd_contents{$cmd}}; $x+=2) + { + my ($ty,$nm) = (${$cmd_contents{$cmd}}[$x], ${$cmd_contents{$cmd}}[$x+1]); + push @a,translate_type_to_arg_type($ty)." $nm"; + push @na,"$nm"; + } + my $ar = join(', ',@a); + my $nar = join(', ',@na); + $nar = "($nar)" if $nar ne ''; + + $with_params .= "\tinline std::auto_ptr<$classname_base$reply> Query$cmd($ar$argextra)\n\t{\n"; + $with_params .= "\t\t$classname_base$cmd send$nar;\n"; + $with_params .= "\t\treturn Query(send$queryextra);\n"; + $with_params .= "\t}\n"; + } + } + # quick hack to correct bad argument lists for commands with zero paramters but with streams + $with_params =~ s/\(, /(/g; + print H "\n",$with_params,"\n"; +} +print H <<__E; +private: + $classname_base(const $classname_base &rToCopy); +__E +if($type eq 'Server') +{ + # need to put the streams to send vector + print H "\tstd::vector<IOStream*> mStreamsToSend;\n\tvoid DeleteStreamsToSend();\n"; +} + +if($implement_filelog || $implement_syslog) +{ + print H <<__E; + virtual void InformStreamReceiving(u_int32_t Size); + virtual void InformStreamSending(u_int32_t Size); +__E +} + +if($implement_syslog) +{ + print H "private:\n\tbool mLogToSysLog;\n"; +} +if($implement_filelog) +{ + print H "private:\n\tFILE *mLogToFile;\n"; +} +print H <<__E; + +protected: + virtual std::auto_ptr<ProtocolObject> MakeProtocolObject(int ObjType); + virtual const char *GetIdentString(); +}; + +__E + +my $construtor_extra = ''; +$construtor_extra .= ', mLogToSysLog(false)' if $implement_syslog; +$construtor_extra .= ', mLogToFile(0)' if $implement_filelog; + +my $destructor_extra = ($type eq 'Server')?"\n\tDeleteStreamsToSend();":''; + +my $prefix = $classname_base.'::'; +print CPP <<__E; +$prefix$classname_base(IOStream &rStream) + : Protocol(rStream)$construtor_extra +{ +} +$prefix~$classname_base() +{$destructor_extra +} +const char *${prefix}GetIdentString() +{ + return "$ident_string"; +} +std::auto_ptr<ProtocolObject> ${prefix}MakeProtocolObject(int ObjType) +{ + switch(ObjType) + { +__E + +# do objects within this +for my $cmd (@cmd_list) +{ + print CPP <<__E; + case $cmd_id{$cmd}: + return std::auto_ptr<ProtocolObject>(new $classname_base$cmd); + break; +__E +} + +print CPP <<__E; + default: + THROW_EXCEPTION(ConnectionException, Conn_Protocol_UnknownCommandRecieved) + } +} +__E +# write receieve and send functions +print CPP <<__E; +std::auto_ptr<$derive_objects_from> ${prefix}Receive() +{ + std::auto_ptr<${derive_objects_from}> preply((${derive_objects_from}*)(Protocol::Receive().release())); + +__E + if($implement_syslog) + { + print CPP <<__E; + if(mLogToSysLog) + { + preply->LogSysLog("Receive"); + } +__E + } + if($implement_filelog) + { + print CPP <<__E; + if(mLogToFile != 0) + { + preply->LogFile("Receive", mLogToFile); + } +__E + } +print CPP <<__E; + + return preply; +} + +void ${prefix}Send(const ${derive_objects_from} &rObject) +{ +__E + if($implement_syslog) + { + print CPP <<__E; + if(mLogToSysLog) + { + rObject.LogSysLog("Send"); + } +__E + } + if($implement_filelog) + { + print CPP <<__E; + if(mLogToFile != 0) + { + rObject.LogFile("Send", mLogToFile); + } +__E + } + +print CPP <<__E; + Protocol::Send(rObject); +} + +__E +# write server function? +if($type eq 'Server') +{ + print CPP <<__E; +void ${prefix}DoServer($context_class &rContext) +{ + // Handshake with client + Handshake(); + + // Command processing loop + bool inProgress = true; + while(inProgress) + { + // Get an object from the conversation + std::auto_ptr<${derive_objects_from}> pobj(Receive()); + +__E + if($implement_syslog) + { + print CPP <<__E; + if(mLogToSysLog) + { + pobj->LogSysLog("Receive"); + } +__E + } + if($implement_filelog) + { + print CPP <<__E; + if(mLogToFile != 0) + { + pobj->LogFile("Receive", mLogToFile); + } +__E + } + print CPP <<__E; + + // Run the command + std::auto_ptr<${derive_objects_from}> preply((${derive_objects_from}*)(pobj->DoCommand(*this, rContext).release())); + +__E + if($implement_syslog) + { + print CPP <<__E; + if(mLogToSysLog) + { + preply->LogSysLog("Send"); + } +__E + } + if($implement_filelog) + { + print CPP <<__E; + if(mLogToFile != 0) + { + preply->LogFile("Send", mLogToFile); + } +__E + } + print CPP <<__E; + + // Send the reply + Send(*(preply.get())); + + // Send any streams + for(unsigned int s = 0; s < mStreamsToSend.size(); s++) + { + // Send the streams + SendStream(*mStreamsToSend[s]); + } + // Delete these streams + DeleteStreamsToSend(); + + // Does this end the conversation? + if(pobj->IsConversationEnd()) + { + inProgress = false; + } + } +} + +void ${prefix}SendStreamAfterCommand(IOStream *pStream) +{ + ASSERT(pStream != NULL); + mStreamsToSend.push_back(pStream); +} + +void ${prefix}DeleteStreamsToSend() +{ + for(std::vector<IOStream*>::iterator i(mStreamsToSend.begin()); i != mStreamsToSend.end(); ++i) + { + delete (*i); + } + mStreamsToSend.clear(); +} + +__E +} + +# write logging functions? +if($implement_filelog || $implement_syslog) +{ + my ($fR,$fS); + + if($implement_syslog) + { + $fR .= qq~\tif(mLogToSysLog) { ::syslog(LOG_INFO, (Size==Protocol::ProtocolStream_SizeUncertain)?"Receiving stream, size uncertain":"Receiving stream, size %d", Size); }\n~; + $fS .= qq~\tif(mLogToSysLog) { ::syslog(LOG_INFO, (Size==Protocol::ProtocolStream_SizeUncertain)?"Sending stream, size uncertain":"Sending stream, size %d", Size); }\n~; + } + if($implement_filelog) + { + $fR .= qq~\tif(mLogToFile) { ::fprintf(mLogToFile, (Size==Protocol::ProtocolStream_SizeUncertain)?"Receiving stream, size uncertain\\n":"Receiving stream, size %d\\n", Size); ::fflush(mLogToFile); }\n~; + $fS .= qq~\tif(mLogToFile) { ::fprintf(mLogToFile, (Size==Protocol::ProtocolStream_SizeUncertain)?"Sending stream, size uncertain\\n":"Sending stream, size %d\\n", Size); ::fflush(mLogToFile); }\n~; + } + + print CPP <<__E; + +void ${prefix}InformStreamReceiving(u_int32_t Size) +{ +$fR} + +void ${prefix}InformStreamSending(u_int32_t Size) +{ +$fS} + +__E +} + + +# write client Query functions? +if($type eq 'Client') +{ + for my $cmd (@cmd_list) + { + if(obj_is_type($cmd,'Command')) + { + my $reply = obj_get_type_params($cmd,'Command'); + my $reply_id = $cmd_id{$reply}; + my $has_stream = obj_is_type($cmd,'StreamWithCommand'); + my $argextra = $has_stream?', IOStream &rStream':''; + my $send_stream_extra = ''; + if($has_stream) + { + $send_stream_extra = <<__E; + + // Send stream after the command + SendStream(rStream); +__E + } + print CPP <<__E; +std::auto_ptr<$classname_base$reply> ${classname_base}::Query(const $classname_base$cmd &rQuery$argextra) +{ + // Send query + Send(rQuery); + $send_stream_extra + // Wait for the reply + std::auto_ptr<${derive_objects_from}> preply(Receive().release()); + + if(preply->GetType() == $reply_id) + { + // Correct response + return std::auto_ptr<$classname_base$reply>(($classname_base$reply*)preply.release()); + } + else + { + // Set protocol error + int type, subType; + if(preply->IsError(type, subType)) + { + SetError(type, subType); + TRACE2("Protocol: Error received %d/%d\\n", type, subType); + } + else + { + SetError(Protocol::UnknownError, Protocol::UnknownError); + } + + // Throw an exception + THROW_EXCEPTION(ConnectionException, Conn_Protocol_UnexpectedReply) + } +} +__E + } + } +} + + + +print H <<__E; +#endif // $guardname + +__E + +# close files +close H; +close CPP; + + +sub obj_is_type +{ + my ($c,$ty) = @_; + for(@{$cmd_attributes{$c}}) + { + return 1 if $_ =~ m/\A$ty/; + } + + return 0; +} + +sub obj_get_type_params +{ + my ($c,$ty) = @_; + for(@{$cmd_attributes{$c}}) + { + return $1 if $_ =~ m/\A$ty\((.+?)\)\Z/; + } + die "Can't find attribute $ty\n" +} + +# returns (is basic type, typename) +sub translate_type +{ + my $ty = $_[0]; + + if($ty =~ m/\Avector\<(.+?)\>\Z/) + { + my $v_type = $1; + my (undef,$v_ty) = translate_type($v_type); + return (0, 'std::vector<'.$v_ty.'>') + } + else + { + if(!exists $translate_type_info{$ty}) + { + die "Don't know about type name $ty\n"; + } + return @{$translate_type_info{$ty}} + } +} + +sub translate_type_to_arg_type +{ + my ($basic,$typename) = translate_type(@_); + return $basic?$typename:'const '.$typename.'&' +} + +sub translate_type_to_member_type +{ + my ($basic,$typename) = translate_type(@_); + return $typename +} + +sub make_log_strings +{ + my ($cmd) = @_; + + my @str; + my @arg; + for(my $x = 0; $x < $#{$cmd_contents{$cmd}}; $x+=2) + { + my ($ty,$nm) = (${$cmd_contents{$cmd}}[$x], ${$cmd_contents{$cmd}}[$x+1]); + + if(exists $log_display_types{$ty}) + { + # need to translate it + my ($format,$arg) = @{$log_display_types{$ty}}; + $arg =~ s/VAR/m$nm/g; + + if ($format eq "0x%llx" and $target_windows) + { + $format = "0x%I64x"; + $arg = "(uint64_t)$arg"; + } + + push @str,$format; + push @arg,$arg; + } + else + { + # is opaque + push @str,'OPAQUE'; + } + } + return ($cmd.'('.join(',',@str).')', join(',','',@arg)); +} + +sub make_log_strings_framework +{ + my ($cmd) = @_; + + my @args; + + for(my $x = 0; $x < $#{$cmd_contents{$cmd}}; $x+=2) + { + my ($ty,$nm) = (${$cmd_contents{$cmd}}[$x], ${$cmd_contents{$cmd}}[$x+1]); + + if(exists $log_display_types{$ty}) + { + # need to translate it + my ($format,$arg) = @{$log_display_types{$ty}}; + $arg =~ s/VAR/m$nm/g; + + if ($format =~ m'x$') + { + $arg = "std::hex << std::showbase " . + "<< $arg << std::dec"; + } + + push @args, $arg; + } + else + { + # is opaque + push @args, '"OPAQUE"'; + } + } + + my $log_cmd = "Action << \" $cmd(\" "; + foreach my $arg (@args) + { + $arg = "<< $arg "; + } + $log_cmd .= join('<< "," ',@args); + $log_cmd .= '<< ")"'; + return $log_cmd; +} + + |