[Box Backup-dev] COMMIT r233 - box/chris/boxi/bin/bbackupd

boxbackup-dev@fluffy.co.uk boxbackup-dev@fluffy.co.uk
Thu, 15 Dec 2005 00:00:08 +0000 (GMT)


Author: chris
Date: 2005-12-15 00:00:02 +0000 (Thu, 15 Dec 2005)
New Revision: 233

Added:
   box/chris/boxi/bin/bbackupd/CommandSocketManager.cpp
   box/chris/boxi/bin/bbackupd/CommandSocketManager.h
Log:
* CommandSocketManager.cpp
* CommandSocketManager.h
- Added new command socket manager class to Subversion


Added: box/chris/boxi/bin/bbackupd/CommandSocketManager.cpp
===================================================================
--- box/chris/boxi/bin/bbackupd/CommandSocketManager.cpp	2005-12-14 23:54:01 UTC (rev 232)
+++ box/chris/boxi/bin/bbackupd/CommandSocketManager.cpp	2005-12-15 00:00:02 UTC (rev 233)
@@ -0,0 +1,392 @@
+// distribution boxbackup-0.09
+// 
+//  
+// Copyright (c) 2003, 2004
+//      Ben Summers.  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 acknowledgement:
+//        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:    CommandSocketManager.cpp
+//		Purpose: Implementation for command socket management interface
+//		Created: 2005/04/08
+//
+// --------------------------------------------------------------------------
+
+#include <syslog.h>
+
+#include "Box.h"
+#include "IOStreamGetLine.h"
+#include "CommandSocketManager.h"
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    CommandSocketManager::CommandSocketManager()
+//		Purpose: Constructor
+//		Created: 18/2/04
+//
+// --------------------------------------------------------------------------
+CommandSocketManager::CommandSocketManager(
+	const Configuration& rConf,
+	CommandListener* pListener, 
+	const char * pSocketName)
+	: mpGetLine(0),
+	  mConf(rConf),
+	  mState(State_Initialising)
+{
+	mpListener = pListener;
+	::unlink(pSocketName);
+	mListeningSocket.Listen(Socket::TypeUNIX, pSocketName);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    BackupDaemon::CommandSocketInfo::~CommandSocketInfo()
+//		Purpose: Destructor
+//		Created: 18/2/04
+//
+// --------------------------------------------------------------------------
+CommandSocketManager::~CommandSocketManager()
+{
+	if (mpConnectedSocket.get() != 0)
+	{
+		CloseConnection();
+	}
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    CommandSocketManager::Wait(box_time_t, bool &, bool &)
+//		Purpose: Waits on a the command socket for a time of UP TO the required time
+//				 but may be much less, and handles a command if necessary.
+//		Created: 18/2/04
+//
+// --------------------------------------------------------------------------
+
+void CommandSocketManager::Wait(box_time_t RequiredDelay)
+{
+#ifdef WIN32
+	// Another thread is listening on the command socket, 
+	// no need for us to do anything here.
+	DWORD timeout = BoxTimeToMilliSeconds(RequiredDelay);
+
+	if (timeout > 0)
+	{
+		Sleep(timeout);
+	}
+#else // !WIN32
+	TRACE1("Wait on command socket, delay = %lld\n", RequiredDelay);
+	
+	try
+	{
+		// Timeout value for connections and things
+		int timeout = ((int)BoxTimeToMilliSeconds(RequiredDelay));
+		// Handle bad boundary cases
+		if(timeout <= 0) timeout = 1;
+		if(timeout == INFTIM) timeout = 100000;
+
+		// Wait for socket connection, or handle a command?
+		if(mpConnectedSocket.get() == 0)
+		{
+			// No connection, listen for a new one
+			mpConnectedSocket.reset(mListeningSocket.Accept(timeout).release());
+			
+			if(mpConnectedSocket.get() == 0)
+			{
+				// If a connection didn't arrive, there was a timeout, 
+				// which means we've waited long enough and it's time to go.
+				return;
+			}
+			else
+			{
+#ifdef PLATFORM_CANNOT_FIND_PEER_UID_OF_UNIX_SOCKET
+				bool uidOK = true;
+				::syslog(LOG_ERR, "On this platform, no security check "
+					"can be made on the credientials of peers connecting "
+					"to the command socket. (bbackupctl)");
+#else
+				// Security check -- does the process connecting
+				// to this socket have the same UID as this process?
+				bool uidOK = false;
+				// BLOCK
+				{
+					uid_t remoteEUID = 0xffff;
+					gid_t remoteEGID = 0xffff;
+					if(mpConnectedSocket->GetPeerCredentials(remoteEUID, 
+						remoteEGID))
+					{
+						// Credentials are available -- check UID
+						if(remoteEUID == ::getuid())
+						{
+							// Acceptable
+							uidOK = true;
+						}
+					}
+				}
+#endif // PLATFORM_CANNOT_FIND_PEER_UID_OF_UNIX_SOCKET
+				
+				// Is this an acceptible connection?
+				if(!uidOK)
+				{
+					// Dump the connection
+					::syslog(LOG_ERR, "Incoming command connection from peer "
+						"had different user ID than this process, or security "
+						"check could not be completed.");
+					mpConnectedSocket.reset();
+					return;
+				}
+
+				// Log
+				::syslog(LOG_INFO, "Incoming connection to command socket");
+				TRACE0("Accepted new command connection\n");
+				
+				// Send a header line summarising the configuration and current state
+				char summary[256];
+				int summarySize = sprintf(summary, "bbackupd: %d %d %d %d\n"
+					"state %d\n",
+					mConf.GetKeyValueBool("AutomaticBackup"),
+					mConf.GetKeyValueInt("UpdateStoreInterval"),
+					mConf.GetKeyValueInt("MinimumFileAge"),
+					mConf.GetKeyValueInt("MaxUploadWait"),
+					mState);
+				mpConnectedSocket->Write(summary, summarySize);
+				
+				// Set the timeout to something very small, so we don't
+				// spend too long on waiting for any incoming data
+				timeout = 10; // milliseconds
+			}
+		}
+
+		// So there must be a connection now.
+		ASSERT(mpConnectedSocket.get() != 0);
+		
+		// Is there a getline object ready?
+		if(mpGetLine == 0)
+		{
+			// Create a new one
+			mpGetLine = new IOStreamGetLine(*(mpConnectedSocket.get()));
+		}
+		
+		// Ping the remote side, to provide errors which will mean the socket gets closed
+		// Don't do this if the timeout requested was zero, as we don't want
+		// to flood the connection during background polling
+		if (RequiredDelay > 0)
+			mpConnectedSocket->Write("ping\n", 5);
+		
+		// Wait for a command or something on the socket
+		std::string command;
+		while(mpGetLine != 0 && !mpGetLine->IsEOF()
+			&& mpGetLine->GetLine(command, false /* no preprocessing */, timeout))
+		{
+			TRACE1("Receiving command '%s' over command socket\n", command.c_str());
+			
+			bool sendOK = false;
+			bool sendResponse = true;
+		
+			// Command to process!
+			if(command == "quit" || command == "")
+			{
+				// Close the socket.
+				CloseConnection();
+				sendResponse = false;
+			}
+			else if(command == "sync")
+			{
+				// Sync now!
+				mpListener->SetSyncRequested();
+				sendOK = true;
+			}
+			else if(command == "force-sync")
+			{
+				// Sync now (forced -- overrides any SyncAllowScript)
+				mpListener->SetSyncForced();
+				sendOK = true;
+			}
+			else if(command == "reload")
+			{
+				// Reload the configuration
+				mpListener->SetReloadConfigWanted();
+				sendOK = true;
+			}
+			else if(command == "terminate")
+			{
+				// Terminate the daemon cleanly
+				mpListener->SetTerminateWanted();
+				sendOK = true;
+			}
+			
+			// Send a response back?
+			if(sendResponse)
+			{
+				mpConnectedSocket->Write(sendOK?"ok\n":"error\n", sendOK?3:6);
+			}
+			
+			// Set timeout to something very small, 
+			// so this just checks for data which is waiting
+			timeout = 1;
+		}
+		
+		// Close on EOF?
+		if(mpGetLine != 0 && mpGetLine->IsEOF())
+		{
+			CloseConnection();
+		}
+	}
+	catch(...)
+	{
+		// If an error occurs, and there is a connection active, just close that
+		// connection and continue. Otherwise, let the error propagate.
+		if(mpConnectedSocket.get() == 0)
+		{
+			throw;
+		}
+		else
+		{
+			// Close socket and ignore error
+			CloseConnection();
+		}
+	}
+#endif // WIN32
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    CommandSocketManager::CloseConnection()
+//		Purpose: Close the command connection, ignoring any errors
+//		Created: 18/2/04
+//
+// --------------------------------------------------------------------------
+void CommandSocketManager::CloseConnection()
+{
+	try
+	{
+		TRACE0("Closing command connection\n");
+	
+#ifdef WIN32
+		mListeningSocket.Close();
+#else
+		if(mpGetLine)
+		{
+			delete mpGetLine;
+			mpGetLine = 0;
+		}
+		mpConnectedSocket.reset();
+#endif
+	}
+	catch(...)
+	{
+		// Ignore any errors
+	}
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    CommandSocketManager::SendSyncStartOrFinish(bool sendStart)
+//		Purpose: Send a message to any connected client when a sync starts or
+//			finishes.
+//		Created: 18/2/04
+//
+// --------------------------------------------------------------------------
+void CommandSocketManager::SendSyncStartOrFinish(bool SendStart)
+{
+	// The bbackupctl program can't rely on a state change, because it may never
+	// change if the server doesn't need to be contacted.
+
+#ifdef WIN32
+    if (mListeningSocket.IsConnected())
+#else
+	if (mpConnectedSocket.get() != 0)
+#endif
+	{
+		const char* message = SendStart ? "start-sync\n" : "finish-sync\n";
+		
+		try
+		{
+#ifdef WIN32
+			mListeningSocket.Write(message, strlen(message));
+#else
+			mpConnectedSocket->Write(message, strlen(message));
+#endif
+		}
+		catch(...)
+		{
+			CloseConnection();
+		}
+	}
+}
+
+void CommandSocketManager::SendStateUpdate(state_t State)
+{
+	mState = State;
+	
+	// If there's a command socket connected, then inform it -- 
+	// disconnecting from the command socket if there's an error
+
+#ifdef WIN32
+	#warning FIX ME: race condition
+	// what happens if the socket is closed by the other thread before
+	// we can write to it? Null pointer deref at best.
+	if (mListeningSocket.IsConnected())
+		return;
+#else	
+	if(mpConnectedSocket.get() == 0)
+		return;
+#endif
+	
+	// Something connected to the command socket, tell it about the new state
+	char newState[64];
+	char newStateSize = sprintf(newState, "state %d\n", State);
+	try
+	{
+#ifdef WIN32
+		mListeningSocket.Write(newState, newStateSize);
+#else
+		mpConnectedSocket->Write(newState, newStateSize);
+#endif
+	}
+	catch(...)
+	{
+		CloseConnection();
+	}
+}

Added: box/chris/boxi/bin/bbackupd/CommandSocketManager.h
===================================================================
--- box/chris/boxi/bin/bbackupd/CommandSocketManager.h	2005-12-14 23:54:01 UTC (rev 232)
+++ box/chris/boxi/bin/bbackupd/CommandSocketManager.h	2005-12-15 00:00:02 UTC (rev 233)
@@ -0,0 +1,74 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    CommandSocketManager.h
+//		Purpose: Interface for managing command socket and processing
+//			 client commands
+//		Created: 2003/10/08
+//
+// --------------------------------------------------------------------------
+
+#ifndef COMMANDSOCKETMANAGER__H
+#define COMMANDSOCKETMANAGER__H
+
+#include "BoxTime.h"
+#include "Configuration.h"
+#include "Socket.h"
+#include "SocketListen.h"
+#include "SocketStream.h"
+#include "WinNamedPipeStream.h"
+
+typedef enum
+{
+	// Add stuff to this, make sure the textual equivalents 
+	// in BackupDaemon::SetState() are changed too.
+	State_Initialising = -1,
+	State_Idle = 0,
+	State_Connected = 1,
+	State_Error = 2,
+	State_StorageLimitExceeded = 3
+} state_t;
+
+class IOStreamGetLine;
+
+class CommandListener
+{
+	public:
+	virtual ~CommandListener() { }
+	virtual void SetReloadConfigWanted() = 0;
+	virtual void SetTerminateWanted() = 0;
+	virtual void SetSyncRequested() = 0;
+	virtual void SetSyncForced() = 0;
+};
+
+class CommandSocketManager
+{
+public:
+	CommandSocketManager(
+		const Configuration& rConf, 
+		CommandListener* pListener,
+		const char * pSocketName);
+	~CommandSocketManager();
+	void Wait(box_time_t RequiredDelay);
+	void CloseConnection();
+	void SendSyncStartOrFinish(bool SendStart);
+	void SendStateUpdate(state_t newState);
+
+private:
+	CommandSocketManager(const CommandSocketManager &);	// no copying
+	CommandSocketManager &operator=(const CommandSocketManager &);
+
+#ifdef WIN32
+	WinNamedPipeStream mListeningSocket;
+#else
+	SocketListen<SocketStream, 1 /* listen backlog */> mListeningSocket;
+	std::auto_ptr<SocketStream> mpConnectedSocket;
+#endif
+
+	IOStreamGetLine *mpGetLine;
+	CommandListener* mpListener;
+	Configuration mConf;
+	state_t mState;
+};
+
+#endif // COMMANDSOCKETMANAGER__H