summaryrefslogtreecommitdiff
path: root/bin/bbackupquery/BackupQueries.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'bin/bbackupquery/BackupQueries.cpp')
-rw-r--r--bin/bbackupquery/BackupQueries.cpp1906
1 files changed, 1906 insertions, 0 deletions
diff --git a/bin/bbackupquery/BackupQueries.cpp b/bin/bbackupquery/BackupQueries.cpp
new file mode 100644
index 00000000..d254ba9c
--- /dev/null
+++ b/bin/bbackupquery/BackupQueries.cpp
@@ -0,0 +1,1906 @@
+// distribution boxbackup-0.10 (svn version: 494)
+//
+// 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: BackupQueries.cpp
+// Purpose: Perform various queries on the backup store server.
+// Created: 2003/10/10
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#ifdef HAVE_UNISTD_H
+ #include <unistd.h>
+#endif
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifdef HAVE_DIRENT_H
+ #include <dirent.h>
+#endif
+
+#include <set>
+
+#include "BackupQueries.h"
+#include "Utils.h"
+#include "Configuration.h"
+#include "autogen_BackupProtocolClient.h"
+#include "BackupStoreFilenameClear.h"
+#include "BackupStoreDirectory.h"
+#include "IOStream.h"
+#include "BoxTimeToText.h"
+#include "FileStream.h"
+#include "BackupStoreFile.h"
+#include "TemporaryDirectory.h"
+#include "FileModificationTime.h"
+#include "BackupClientFileAttributes.h"
+#include "CommonException.h"
+#include "BackupClientRestore.h"
+#include "BackupStoreException.h"
+#include "ExcludeList.h"
+#include "BackupClientMakeExcludeList.h"
+
+#include "MemLeakFindOn.h"
+
+#define COMPARE_RETURN_SAME 1
+#define COMPARE_RETURN_DIFFERENT 2
+#define COMPARE_RETURN_ERROR 3
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::BackupQueries()
+// Purpose: Constructor
+// Created: 2003/10/10
+//
+// --------------------------------------------------------------------------
+BackupQueries::BackupQueries(BackupProtocolClient &rConnection, const Configuration &rConfiguration)
+ : mrConnection(rConnection),
+ mrConfiguration(rConfiguration),
+ mQuitNow(false),
+ mRunningAsRoot(false),
+ mWarnedAboutOwnerAttributes(false),
+ mReturnCode(0) // default return code
+{
+ mRunningAsRoot = (::geteuid() == 0);
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::~BackupQueries()
+// Purpose: Destructor
+// Created: 2003/10/10
+//
+// --------------------------------------------------------------------------
+BackupQueries::~BackupQueries()
+{
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::DoCommand(const char *)
+// Purpose: Perform a command
+// Created: 2003/10/10
+//
+// --------------------------------------------------------------------------
+void BackupQueries::DoCommand(const char *Command)
+{
+ // is the command a shell command?
+ if(Command[0] == 's' && Command[1] == 'h' && Command[2] == ' ' && Command[3] != '\0')
+ {
+ // Yes, run shell command
+ ::system(Command + 3);
+ return;
+ }
+
+ // split command into components
+ std::vector<std::string> cmdElements;
+ std::string options;
+ {
+ const char *c = Command;
+ bool inQuoted = false;
+ bool inOptions = false;
+
+ std::string s;
+ while(*c != 0)
+ {
+ // Terminating char?
+ if(*c == ((inQuoted)?'"':' '))
+ {
+ if(!s.empty()) cmdElements.push_back(s);
+ s.resize(0);
+ inQuoted = false;
+ inOptions = false;
+ }
+ else
+ {
+ // No. Start of quoted parameter?
+ if(s.empty() && *c == '"')
+ {
+ inQuoted = true;
+ }
+ // Start of options?
+ else if(s.empty() && *c == '-')
+ {
+ inOptions = true;
+ }
+ else
+ {
+ if(inOptions)
+ {
+ // Option char
+ options += *c;
+ }
+ else
+ {
+ // Normal string char
+ s += *c;
+ }
+ }
+ }
+
+ ++c;
+ }
+ if(!s.empty()) cmdElements.push_back(s);
+ }
+
+ // Check...
+ if(cmdElements.size() < 1)
+ {
+ // blank command
+ return;
+ }
+
+ // Data about commands
+ static const char *commandNames[] = {"quit", "exit", "list", "pwd", "cd", "lcd", "sh", "getobject", "get", "compare", "restore", "help", "usage", "undelete", 0};
+ static const char *validOptions[] = {"", "", "rodIFtsh", "", "od", "", "", "", "i", "alcqE", "dri", "", "", "", 0};
+ #define COMMAND_Quit 0
+ #define COMMAND_Exit 1
+ #define COMMAND_List 2
+ #define COMMAND_pwd 3
+ #define COMMAND_cd 4
+ #define COMMAND_lcd 5
+ #define COMMAND_sh 6
+ #define COMMAND_GetObject 7
+ #define COMMAND_Get 8
+ #define COMMAND_Compare 9
+ #define COMMAND_Restore 10
+ #define COMMAND_Help 11
+ #define COMMAND_Usage 12
+ #define COMMAND_Undelete 13
+ static const char *alias[] = {"ls", 0};
+ static const int aliasIs[] = {COMMAND_List, 0};
+
+ // Work out which command it is...
+ int cmd = 0;
+ while(commandNames[cmd] != 0 && ::strcmp(cmdElements[0].c_str(), commandNames[cmd]) != 0)
+ {
+ cmd++;
+ }
+ if(commandNames[cmd] == 0)
+ {
+ // Check for aliases
+ int a;
+ for(a = 0; alias[a] != 0; ++a)
+ {
+ if(::strcmp(cmdElements[0].c_str(), alias[a]) == 0)
+ {
+ // Found an alias
+ cmd = aliasIs[a];
+ break;
+ }
+ }
+
+ // No such command
+ if(alias[a] == 0)
+ {
+ printf("Unrecognised command: %s\n", Command);
+ return;
+ }
+ }
+
+ // Arguments
+ std::vector<std::string> args(cmdElements.begin() + 1, cmdElements.end());
+
+ // Set up options
+ bool opts[256];
+ for(int o = 0; o < 256; ++o) opts[o] = false;
+ // BLOCK
+ {
+ // options
+ const char *c = options.c_str();
+ while(*c != 0)
+ {
+ // Valid option?
+ if(::strchr(validOptions[cmd], *c) == NULL)
+ {
+ printf("Invalid option '%c' for command %s\n", *c, commandNames[cmd]);
+ return;
+ }
+ opts[(int)*c] = true;
+ ++c;
+ }
+ }
+
+ if(cmd != COMMAND_Quit && cmd != COMMAND_Exit)
+ {
+ // If not a quit command, set the return code to zero
+ SetReturnCode(0);
+ }
+
+ // Handle command
+ switch(cmd)
+ {
+ case COMMAND_Quit:
+ case COMMAND_Exit:
+ mQuitNow = true;
+ break;
+
+ case COMMAND_List:
+ CommandList(args, opts);
+ break;
+
+ case COMMAND_pwd:
+ {
+ // Simple implementation, so do it here
+ printf("%s (%08llx)\n",
+ GetCurrentDirectoryName().c_str(),
+ (long long)GetCurrentDirectoryID());
+ }
+ break;
+
+ case COMMAND_cd:
+ CommandChangeDir(args, opts);
+ break;
+
+ case COMMAND_lcd:
+ CommandChangeLocalDir(args);
+ break;
+
+ case COMMAND_sh:
+ printf("The command to run must be specified as an argument.\n");
+ break;
+
+ case COMMAND_GetObject:
+ CommandGetObject(args, opts);
+ break;
+
+ case COMMAND_Get:
+ CommandGet(args, opts);
+ break;
+
+ case COMMAND_Compare:
+ CommandCompare(args, opts);
+ break;
+
+ case COMMAND_Restore:
+ CommandRestore(args, opts);
+ break;
+
+ case COMMAND_Usage:
+ CommandUsage();
+ break;
+
+ case COMMAND_Help:
+ CommandHelp(args);
+ break;
+
+ case COMMAND_Undelete:
+ CommandUndelete(args, opts);
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::CommandList(const std::vector<std::string> &, const bool *)
+// Purpose: List directories (optionally recursive)
+// Created: 2003/10/10
+//
+// --------------------------------------------------------------------------
+void BackupQueries::CommandList(const std::vector<std::string> &args, const bool *opts)
+{
+ #define LIST_OPTION_RECURSIVE 'r'
+ #define LIST_OPTION_ALLOWOLD 'o'
+ #define LIST_OPTION_ALLOWDELETED 'd'
+ #define LIST_OPTION_NOOBJECTID 'I'
+ #define LIST_OPTION_NOFLAGS 'F'
+ #define LIST_OPTION_TIMES 't'
+ #define LIST_OPTION_SIZEINBLOCKS 's'
+ #define LIST_OPTION_DISPLAY_HASH 'h'
+
+ // default to using the current directory
+ int64_t rootDir = GetCurrentDirectoryID();
+
+ // name of base directory
+ std::string listRoot; // blank
+
+ // Got a directory in the arguments?
+ if(args.size() > 0)
+ {
+#ifdef WIN32
+ std::string storeDirEncoded;
+ if(!ConvertConsoleToUtf8(args[0].c_str(), storeDirEncoded))
+ return;
+#else
+ const std::string& storeDirEncoded(args[0]);
+#endif
+
+ // Attempt to find the directory
+ rootDir = FindDirectoryObjectID(storeDirEncoded,
+ opts[LIST_OPTION_ALLOWOLD],
+ opts[LIST_OPTION_ALLOWDELETED]);
+
+ if(rootDir == 0)
+ {
+ printf("Directory '%s' not found on store\n",
+ args[0].c_str());
+ return;
+ }
+ }
+
+ // List it
+ List(rootDir, listRoot, opts, true /* first level to list */);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::CommandList2(int64_t, const std::string &, const bool *)
+// Purpose: Do the actual listing of directories and files
+// Created: 2003/10/10
+//
+// --------------------------------------------------------------------------
+void BackupQueries::List(int64_t DirID, const std::string &rListRoot, const bool *opts, bool FirstLevel)
+{
+ // Generate exclude flags
+ int16_t excludeFlags = BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING;
+ if(!opts[LIST_OPTION_ALLOWOLD]) excludeFlags |= BackupProtocolClientListDirectory::Flags_OldVersion;
+ if(!opts[LIST_OPTION_ALLOWDELETED]) excludeFlags |= BackupProtocolClientListDirectory::Flags_Deleted;
+
+ // Do communication
+ mrConnection.QueryListDirectory(
+ DirID,
+ BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING, // both files and directories
+ excludeFlags,
+ true /* want attributes */);
+
+ // Retrieve the directory from the stream following
+ BackupStoreDirectory dir;
+ std::auto_ptr<IOStream> dirstream(mrConnection.ReceiveStream());
+ dir.ReadFromStream(*dirstream, mrConnection.GetTimeout());
+
+ // Then... display everything
+ BackupStoreDirectory::Iterator i(dir);
+ BackupStoreDirectory::Entry *en = 0;
+ while((en = i.Next()) != 0)
+ {
+ // Display this entry
+ BackupStoreFilenameClear clear(en->GetName());
+
+ // Object ID?
+ if(!opts[LIST_OPTION_NOOBJECTID])
+ {
+ // add object ID to line
+#ifdef _MSC_VER
+ printf("%08I64x ", (int64_t)en->GetObjectID());
+#else
+ printf("%08llx ", (long long)en->GetObjectID());
+#endif
+ }
+
+ // Flags?
+ if(!opts[LIST_OPTION_NOFLAGS])
+ {
+ static const char *flags = BACKUPSTOREDIRECTORY_ENTRY_FLAGS_DISPLAY_NAMES;
+ char displayflags[16];
+ // make sure f is big enough
+ ASSERT(sizeof(displayflags) >= sizeof(BACKUPSTOREDIRECTORY_ENTRY_FLAGS_DISPLAY_NAMES) + 3);
+ // Insert flags
+ char *f = displayflags;
+ const char *t = flags;
+ int16_t en_flags = en->GetFlags();
+ while(*t != 0)
+ {
+ *f = ((en_flags&1) == 0)?'-':*t;
+ en_flags >>= 1;
+ f++;
+ t++;
+ }
+ // attributes flags
+ *(f++) = (en->HasAttributes())?'a':'-';
+
+ // terminate
+ *(f++) = ' ';
+ *(f++) = '\0';
+ printf(displayflags);
+
+ if(en_flags != 0)
+ {
+ printf("[ERROR: Entry has additional flags set] ");
+ }
+ }
+
+ if(opts[LIST_OPTION_TIMES])
+ {
+ // Show times...
+ std::string time = BoxTimeToISO8601String(
+ en->GetModificationTime());
+ printf("%s ", time.c_str());
+ }
+
+ if(opts[LIST_OPTION_DISPLAY_HASH])
+ {
+#ifdef _MSC_VER
+ printf("%016I64x ", (int64_t)en->GetAttributesHash());
+#else
+ printf("%016llx ", (long long)en->GetAttributesHash());
+#endif
+ }
+
+ if(opts[LIST_OPTION_SIZEINBLOCKS])
+ {
+#ifdef _MSC_VER
+ printf("%05I64d ", (int64_t)en->GetSizeInBlocks());
+#else
+ printf("%05lld ", (long long)en->GetSizeInBlocks());
+#endif
+ }
+
+ // add name
+ if(!FirstLevel)
+ {
+#ifdef WIN32
+ std::string listRootDecoded;
+ if(!ConvertUtf8ToConsole(rListRoot.c_str(),
+ listRootDecoded)) return;
+ printf("%s/", listRootDecoded.c_str());
+#else
+ printf("%s/", rListRoot.c_str());
+#endif
+ }
+
+#ifdef WIN32
+ {
+ std::string fileName;
+ if(!ConvertUtf8ToConsole(
+ clear.GetClearFilename().c_str(), fileName))
+ return;
+ printf("%s", fileName.c_str());
+ }
+#else
+ printf("%s", clear.GetClearFilename().c_str());
+#endif
+
+ if(!en->GetName().IsEncrypted())
+ {
+ printf("[FILENAME NOT ENCRYPTED]");
+ }
+
+ printf("\n");
+
+ // Directory?
+ if((en->GetFlags() & BackupStoreDirectory::Entry::Flags_Dir) != 0)
+ {
+ // Recurse?
+ if(opts[LIST_OPTION_RECURSIVE])
+ {
+ std::string subroot(rListRoot);
+ if(!FirstLevel) subroot += '/';
+ subroot += clear.GetClearFilename();
+ List(en->GetObjectID(), subroot, opts, false /* not the first level to list */);
+ }
+ }
+ }
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::FindDirectoryObjectID(const std::string &)
+// Purpose: Find the object ID of a directory on the store, or return 0 for not found.
+// If pStack != 0, the object is set to the stack of directories.
+// Will start from the current directory stack.
+// Created: 2003/10/10
+//
+// --------------------------------------------------------------------------
+int64_t BackupQueries::FindDirectoryObjectID(const std::string &rDirName, bool AllowOldVersion,
+ bool AllowDeletedDirs, std::vector<std::pair<std::string, int64_t> > *pStack)
+{
+ // Split up string into elements
+ std::vector<std::string> dirElements;
+ SplitString(rDirName, '/', dirElements);
+
+ // Start from current stack, or root, whichever is required
+ std::vector<std::pair<std::string, int64_t> > stack;
+ int64_t dirID = BackupProtocolClientListDirectory::RootDirectory;
+ if(rDirName.size() > 0 && rDirName[0] == '/')
+ {
+ // Root, do nothing
+ }
+ else
+ {
+ // Copy existing stack
+ stack = mDirStack;
+ if(stack.size() > 0)
+ {
+ dirID = stack[stack.size() - 1].second;
+ }
+ }
+
+ // Generate exclude flags
+ int16_t excludeFlags = BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING;
+ if(!AllowOldVersion) excludeFlags |= BackupProtocolClientListDirectory::Flags_OldVersion;
+ if(!AllowDeletedDirs) excludeFlags |= BackupProtocolClientListDirectory::Flags_Deleted;
+
+ // Read directories
+ for(unsigned int e = 0; e < dirElements.size(); ++e)
+ {
+ if(dirElements[e].size() > 0)
+ {
+ if(dirElements[e] == ".")
+ {
+ // Ignore.
+ }
+ else if(dirElements[e] == "..")
+ {
+ // Up one!
+ if(stack.size() > 0)
+ {
+ // Remove top element
+ stack.pop_back();
+
+ // New dir ID
+ dirID = (stack.size() > 0)?(stack[stack.size() - 1].second):BackupProtocolClientListDirectory::RootDirectory;
+ }
+ else
+ {
+ // At root anyway
+ dirID = BackupProtocolClientListDirectory::RootDirectory;
+ }
+ }
+ else
+ {
+ // Not blank element. Read current directory.
+ std::auto_ptr<BackupProtocolClientSuccess> dirreply(mrConnection.QueryListDirectory(
+ dirID,
+ BackupProtocolClientListDirectory::Flags_Dir, // just directories
+ excludeFlags,
+ true /* want attributes */));
+
+ // Retrieve the directory from the stream following
+ BackupStoreDirectory dir;
+ std::auto_ptr<IOStream> dirstream(mrConnection.ReceiveStream());
+ dir.ReadFromStream(*dirstream, mrConnection.GetTimeout());
+
+ // Then... find the directory within it
+ BackupStoreDirectory::Iterator i(dir);
+ BackupStoreFilenameClear dirname(dirElements[e]);
+ BackupStoreDirectory::Entry *en = i.FindMatchingClearName(dirname);
+ if(en == 0)
+ {
+ // Not found
+ return 0;
+ }
+
+ // Object ID for next round of searching
+ dirID = en->GetObjectID();
+
+ // Push onto stack
+ stack.push_back(std::pair<std::string, int64_t>(dirElements[e], dirID));
+ }
+ }
+ }
+
+ // If required, copy the new stack to the caller
+ if(pStack)
+ {
+ *pStack = stack;
+ }
+
+ return dirID;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::GetCurrentDirectoryID()
+// Purpose: Returns the ID of the current directory
+// Created: 2003/10/10
+//
+// --------------------------------------------------------------------------
+int64_t BackupQueries::GetCurrentDirectoryID()
+{
+ // Special case for root
+ if(mDirStack.size() == 0)
+ {
+ return BackupProtocolClientListDirectory::RootDirectory;
+ }
+
+ // Otherwise, get from the last entry on the stack
+ return mDirStack[mDirStack.size() - 1].second;
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::GetCurrentDirectoryName()
+// Purpose: Gets the name of the current directory
+// Created: 2003/10/10
+//
+// --------------------------------------------------------------------------
+std::string BackupQueries::GetCurrentDirectoryName()
+{
+ // Special case for root
+ if(mDirStack.size() == 0)
+ {
+ return std::string("/");
+ }
+
+ // Build path
+ std::string r;
+ for(unsigned int l = 0; l < mDirStack.size(); ++l)
+ {
+ r += "/";
+#ifdef WIN32
+ std::string dirName;
+ if(!ConvertUtf8ToConsole(mDirStack[l].first.c_str(), dirName))
+ return "error";
+ r += dirName;
+#else
+ r += mDirStack[l].first;
+#endif
+ }
+
+ return r;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::CommandChangeDir(const std::vector<std::string> &)
+// Purpose: Change directory command
+// Created: 2003/10/10
+//
+// --------------------------------------------------------------------------
+void BackupQueries::CommandChangeDir(const std::vector<std::string> &args, const bool *opts)
+{
+ if(args.size() != 1 || args[0].size() == 0)
+ {
+ printf("Incorrect usage.\ncd [-o] [-d] <directory>\n");
+ return;
+ }
+
+#ifdef WIN32
+ std::string dirName;
+ if(!ConvertConsoleToUtf8(args[0].c_str(), dirName)) return;
+#else
+ const std::string& dirName(args[0]);
+#endif
+
+ std::vector<std::pair<std::string, int64_t> > newStack;
+ int64_t id = FindDirectoryObjectID(dirName, opts['o'], opts['d'],
+ &newStack);
+
+ if(id == 0)
+ {
+ printf("Directory '%s' not found\n", args[0].c_str());
+ return;
+ }
+
+ // Store new stack
+ mDirStack = newStack;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::CommandChangeLocalDir(const std::vector<std::string> &)
+// Purpose: Change local directory command
+// Created: 2003/10/11
+//
+// --------------------------------------------------------------------------
+void BackupQueries::CommandChangeLocalDir(const std::vector<std::string> &args)
+{
+ if(args.size() != 1 || args[0].size() == 0)
+ {
+ printf("Incorrect usage.\nlcd <local-directory>\n");
+ return;
+ }
+
+ // Try changing directory
+#ifdef WIN32
+ std::string dirName;
+ if(!ConvertConsoleToUtf8(args[0].c_str(), dirName)) return;
+ int result = ::chdir(dirName.c_str());
+#else
+ int result = ::chdir(args[0].c_str());
+#endif
+ if(result != 0)
+ {
+ printf((errno == ENOENT || errno == ENOTDIR)?"Directory '%s' does not exist\n":"Error changing dir to '%s'\n",
+ args[0].c_str());
+ return;
+ }
+
+ // Report current dir
+ char wd[PATH_MAX];
+ if(::getcwd(wd, PATH_MAX) == 0)
+ {
+ printf("Error getting current directory\n");
+ return;
+ }
+
+#ifdef WIN32
+ if(!ConvertUtf8ToConsole(wd, dirName)) return;
+ printf("Local current directory is now '%s'\n", dirName.c_str());
+#else
+ printf("Local current directory is now '%s'\n", wd);
+#endif
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::CommandGetObject(const std::vector<std::string> &, const bool *)
+// Purpose: Gets an object without any translation.
+// Created: 2003/10/11
+//
+// --------------------------------------------------------------------------
+void BackupQueries::CommandGetObject(const std::vector<std::string> &args, const bool *opts)
+{
+ // Check args
+ if(args.size() != 2)
+ {
+ printf("Incorrect usage.\ngetobject <object-id> <local-filename>\n");
+ return;
+ }
+
+ int64_t id = ::strtoll(args[0].c_str(), 0, 16);
+ if(id == LLONG_MIN || id == LLONG_MAX || id == 0)
+ {
+ printf("Not a valid object ID (specified in hex)\n");
+ return;
+ }
+
+ // Does file exist?
+ struct stat st;
+ if(::stat(args[1].c_str(), &st) == 0 || errno != ENOENT)
+ {
+ printf("The local file %s already exists\n", args[1].c_str());
+ return;
+ }
+
+ // Open file
+ FileStream out(args[1].c_str(), O_WRONLY | O_CREAT | O_EXCL);
+
+ // Request that object
+ try
+ {
+ // Request object
+ std::auto_ptr<BackupProtocolClientSuccess> getobj(mrConnection.QueryGetObject(id));
+ if(getobj->GetObjectID() != BackupProtocolClientGetObject::NoObject)
+ {
+ // Stream that object out to the file
+ std::auto_ptr<IOStream> objectStream(mrConnection.ReceiveStream());
+ objectStream->CopyStreamTo(out);
+
+ printf("Object ID %08llx fetched successfully.\n", id);
+ }
+ else
+ {
+ printf("Object does not exist on store.\n");
+ ::unlink(args[1].c_str());
+ }
+ }
+ catch(...)
+ {
+ ::unlink(args[1].c_str());
+ printf("Error occured fetching object.\n");
+ }
+}
+
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::CommandGet(const std::vector<std::string> &, const bool *)
+// Purpose: Command to get a file from the store
+// Created: 2003/10/12
+//
+// --------------------------------------------------------------------------
+void BackupQueries::CommandGet(const std::vector<std::string> &args, const bool *opts)
+{
+ // At least one argument?
+ // Check args
+ if(args.size() < 1 || (opts['i'] && args.size() != 2) || args.size() > 2)
+ {
+ printf("Incorrect usage.\n"
+ "get <remote-filename> [<local-filename>] or\n"
+ "get -i <object-id> <local-filename>\n");
+ return;
+ }
+
+ // Find object ID somehow
+ int64_t id;
+ std::string localName;
+ // BLOCK
+ {
+ // Need to look it up in the current directory
+ mrConnection.QueryListDirectory(
+ GetCurrentDirectoryID(),
+ BackupProtocolClientListDirectory::Flags_File, // just files
+ (opts['i'])?(BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING):(BackupProtocolClientListDirectory::Flags_OldVersion | BackupProtocolClientListDirectory::Flags_Deleted), // only current versions
+ false /* don't want attributes */);
+
+ // Retrieve the directory from the stream following
+ BackupStoreDirectory dir;
+ std::auto_ptr<IOStream> dirstream(mrConnection.ReceiveStream());
+ dir.ReadFromStream(*dirstream, mrConnection.GetTimeout());
+
+ if(opts['i'])
+ {
+ // Specified as ID.
+ id = ::strtoll(args[0].c_str(), 0, 16);
+ if(id == LLONG_MIN || id == LLONG_MAX || id == 0)
+ {
+ printf("Not a valid object ID (specified in hex)\n");
+ return;
+ }
+
+ // Check that the item is actually in the directory
+ if(dir.FindEntryByID(id) == 0)
+ {
+ printf("ID '%08llx' not found in current directory on store.\n(You can only download objects by ID from the current directory.)\n", id);
+ return;
+ }
+
+ // Must have a local name in the arguments (check at beginning of function ensures this)
+ localName = args[1];
+ }
+ else
+ {
+ // Specified by name, find the object in the directory to get the ID
+ BackupStoreDirectory::Iterator i(dir);
+#ifdef WIN32
+ std::string fileName;
+ if(!ConvertConsoleToUtf8(args[0].c_str(), fileName))
+ return;
+ BackupStoreFilenameClear fn(fileName);
+#else
+ BackupStoreFilenameClear fn(args[0]);
+#endif
+ BackupStoreDirectory::Entry *en = i.FindMatchingClearName(fn);
+
+ if(en == 0)
+ {
+ printf("Filename '%s' not found in current directory on store.\n(Subdirectories in path not searched.)\n", args[0].c_str());
+ return;
+ }
+
+ id = en->GetObjectID();
+
+ // Local name is the last argument, which is either the looked up filename, or
+ // a filename specified by the user.
+ localName = args[args.size() - 1];
+ }
+ }
+
+ // Does local file already exist? (don't want to overwrite)
+ struct stat st;
+ if(::stat(localName.c_str(), &st) == 0 || errno != ENOENT)
+ {
+ printf("The local file %s already exists, will not overwrite it.\n", localName.c_str());
+ return;
+ }
+
+ // Request it from the store
+ try
+ {
+ // Request object
+ mrConnection.QueryGetFile(GetCurrentDirectoryID(), id);
+
+ // Stream containing encoded file
+ std::auto_ptr<IOStream> objectStream(mrConnection.ReceiveStream());
+
+ // Decode it
+ BackupStoreFile::DecodeFile(*objectStream, localName.c_str(), mrConnection.GetTimeout());
+
+ // Done.
+ printf("Object ID %08llx fetched sucessfully.\n", id);
+ }
+ catch(...)
+ {
+ ::unlink(localName.c_str());
+ printf("Error occured fetching file.\n");
+ }
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::CompareParams::CompareParams()
+// Purpose: Constructor
+// Created: 29/1/04
+//
+// --------------------------------------------------------------------------
+BackupQueries::CompareParams::CompareParams()
+ : mQuickCompare(false),
+ mIgnoreExcludes(false),
+ mDifferences(0),
+ mDifferencesExplainedByModTime(0),
+ mExcludedDirs(0),
+ mExcludedFiles(0),
+ mpExcludeFiles(0),
+ mpExcludeDirs(0),
+ mLatestFileUploadTime(0)
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::CompareParams::~CompareParams()
+// Purpose: Destructor
+// Created: 29/1/04
+//
+// --------------------------------------------------------------------------
+BackupQueries::CompareParams::~CompareParams()
+{
+ DeleteExcludeLists();
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::CompareParams::DeleteExcludeLists()
+// Purpose: Delete the include lists contained
+// Created: 29/1/04
+//
+// --------------------------------------------------------------------------
+void BackupQueries::CompareParams::DeleteExcludeLists()
+{
+ if(mpExcludeFiles != 0)
+ {
+ delete mpExcludeFiles;
+ mpExcludeFiles = 0;
+ }
+ if(mpExcludeDirs != 0)
+ {
+ delete mpExcludeDirs;
+ mpExcludeDirs = 0;
+ }
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::CommandCompare(const std::vector<std::string> &, const bool *)
+// Purpose: Command to compare data on the store with local data
+// Created: 2003/10/12
+//
+// --------------------------------------------------------------------------
+void BackupQueries::CommandCompare(const std::vector<std::string> &args, const bool *opts)
+{
+ // Parameters, including count of differences
+ BackupQueries::CompareParams params;
+ params.mQuickCompare = opts['q'];
+ params.mIgnoreExcludes = opts['E'];
+
+ // Try and work out the time before which all files should be on the server
+ {
+ std::string syncTimeFilename(mrConfiguration.GetKeyValue("DataDirectory") + DIRECTORY_SEPARATOR_ASCHAR);
+ syncTimeFilename += "last_sync_start";
+ // Stat it to get file time
+ struct stat st;
+ if(::stat(syncTimeFilename.c_str(), &st) == 0)
+ {
+ // Files modified after this time shouldn't be on the server, so report errors slightly differently
+ params.mLatestFileUploadTime = FileModificationTime(st)
+ - SecondsToBoxTime(mrConfiguration.GetKeyValueInt("MinimumFileAge"));
+ }
+ else
+ {
+ printf("Warning: couldn't determine the time of the last synchronisation -- checks not performed.\n");
+ }
+ }
+
+ // Quick compare?
+ if(params.mQuickCompare)
+ {
+ printf("WARNING: Quick compare used -- file attributes are not checked.\n");
+ }
+
+ if(!opts['l'] && opts['a'] && args.size() == 0)
+ {
+ // Compare all locations
+ const Configuration &locations(mrConfiguration.GetSubConfiguration("BackupLocations"));
+ for(std::list<std::pair<std::string, Configuration> >::const_iterator i = locations.mSubConfigurations.begin();
+ i != locations.mSubConfigurations.end(); ++i)
+ {
+ CompareLocation(i->first, params);
+ }
+ }
+ else if(opts['l'] && !opts['a'] && args.size() == 1)
+ {
+ // Compare one location
+ CompareLocation(args[0], params);
+ }
+ else if(!opts['l'] && !opts['a'] && args.size() == 2)
+ {
+ // Compare directory to directory
+
+ // Can't be bothered to do all the hard work to work out which location it's on, and hence which exclude list
+ if(!params.mIgnoreExcludes)
+ {
+ printf("Cannot use excludes on directory to directory comparison -- use -E flag to specify ignored excludes\n");
+ return;
+ }
+ else
+ {
+ // Do compare
+ Compare(args[0], args[1], params);
+ }
+ }
+ else
+ {
+ printf("Incorrect usage.\ncompare -a\n or compare -l <location-name>\n or compare <store-dir-name> <local-dir-name>\n");
+ return;
+ }
+
+ printf("\n[ %d (of %d) differences probably due to file modifications after the last upload ]\nDifferences: %d (%d dirs excluded, %d files excluded)\n",
+ params.mDifferencesExplainedByModTime, params.mDifferences, params.mDifferences, params.mExcludedDirs, params.mExcludedFiles);
+
+ // Set return code?
+ if(opts['c'])
+ {
+ SetReturnCode((params.mDifferences == 0)?COMPARE_RETURN_SAME:COMPARE_RETURN_DIFFERENT);
+ }
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::CompareLocation(const std::string &, BackupQueries::CompareParams &)
+// Purpose: Compare a location
+// Created: 2003/10/13
+//
+// --------------------------------------------------------------------------
+void BackupQueries::CompareLocation(const std::string &rLocation, BackupQueries::CompareParams &rParams)
+{
+ // Find the location's sub configuration
+ const Configuration &locations(mrConfiguration.GetSubConfiguration("BackupLocations"));
+ if(!locations.SubConfigurationExists(rLocation.c_str()))
+ {
+ printf("Location %s does not exist.\n", rLocation.c_str());
+ return;
+ }
+ const Configuration &loc(locations.GetSubConfiguration(rLocation.c_str()));
+
+ try
+ {
+ // Generate the exclude lists
+ if(!rParams.mIgnoreExcludes)
+ {
+ rParams.mpExcludeFiles = BackupClientMakeExcludeList_Files(loc);
+ rParams.mpExcludeDirs = BackupClientMakeExcludeList_Dirs(loc);
+ }
+
+ // Then get it compared
+ Compare(std::string("/") + rLocation,
+ loc.GetKeyValue("Path"), rParams);
+ }
+ catch(...)
+ {
+ // Clean up
+ rParams.DeleteExcludeLists();
+ throw;
+ }
+
+ // Delete exclude lists
+ rParams.DeleteExcludeLists();
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::Compare(const std::string &, const std::string &, BackupQueries::CompareParams &)
+// Purpose: Compare a store directory against a local directory
+// Created: 2003/10/13
+//
+// --------------------------------------------------------------------------
+void BackupQueries::Compare(const std::string &rStoreDir, const std::string &rLocalDir, BackupQueries::CompareParams &rParams)
+{
+#ifdef WIN32
+ std::string storeDirEncoded;
+ if(!ConvertConsoleToUtf8(rStoreDir.c_str(), storeDirEncoded)) return;
+#else
+ const std::string& storeDirEncoded(rStoreDir);
+#endif
+
+ // Get the directory ID of the directory -- only use current data
+ int64_t dirID = FindDirectoryObjectID(storeDirEncoded);
+
+ // Found?
+ if(dirID == 0)
+ {
+ printf("Local directory '%s' exists, but "
+ "server directory '%s' does not exist\n",
+ rLocalDir.c_str(), rStoreDir.c_str());
+ rParams.mDifferences ++;
+ return;
+ }
+
+#ifdef WIN32
+ std::string localDirEncoded;
+ if(!ConvertConsoleToUtf8(rLocalDir.c_str(), localDirEncoded)) return;
+#else
+ std::string localDirEncoded(rLocalDir);
+#endif
+
+ // Go!
+ Compare(dirID, storeDirEncoded, localDirEncoded, rParams);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::Compare(int64_t, const std::string &,
+// const std::string &, BackupQueries::CompareParams &)
+// Purpose: Compare a store directory against a local directory
+// Created: 2003/10/13
+//
+// --------------------------------------------------------------------------
+void BackupQueries::Compare(int64_t DirID, const std::string &rStoreDir, const std::string &rLocalDir, BackupQueries::CompareParams &rParams)
+{
+#ifdef WIN32
+ // By this point, rStoreDir and rLocalDir should be in UTF-8 encoding
+
+ std::string localName;
+ std::string storeName;
+
+ if(!ConvertUtf8ToConsole(rLocalDir.c_str(), localName)) return;
+ if(!ConvertUtf8ToConsole(rStoreDir.c_str(), storeName)) return;
+#else
+ const std::string& localName(rLocalDir);
+ const std::string& storeName(rStoreDir);
+#endif
+
+ // Get info on the local directory
+ struct stat st;
+ if(::lstat(rLocalDir.c_str(), &st) != 0)
+ {
+ // What kind of error?
+ if(errno == ENOTDIR)
+ {
+ printf("Local object '%s' is a file, "
+ "server object '%s' is a directory\n",
+ localName.c_str(), storeName.c_str());
+ rParams.mDifferences ++;
+ }
+ else if(errno == ENOENT)
+ {
+ printf("Local directory '%s' does not exist "
+ "(compared to server directory '%s')\n",
+ localName.c_str(), storeName.c_str());
+ }
+ else
+ {
+ printf("ERROR: stat on local dir '%s'\n",
+ localName.c_str());
+ }
+ return;
+ }
+
+ // Get the directory listing from the store
+ mrConnection.QueryListDirectory(
+ DirID,
+ BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING, // get everything
+ BackupProtocolClientListDirectory::Flags_OldVersion | BackupProtocolClientListDirectory::Flags_Deleted, // except for old versions and deleted files
+ true /* want attributes */);
+
+ // Retrieve the directory from the stream following
+ BackupStoreDirectory dir;
+ std::auto_ptr<IOStream> dirstream(mrConnection.ReceiveStream());
+ dir.ReadFromStream(*dirstream, mrConnection.GetTimeout());
+
+ // Test out the attributes
+ if(!dir.HasAttributes())
+ {
+ printf("Store directory '%s' doesn't have attributes.\n",
+ storeName.c_str());
+ }
+ else
+ {
+ // Fetch the attributes
+ const StreamableMemBlock &storeAttr(dir.GetAttributes());
+ BackupClientFileAttributes attr(storeAttr);
+
+ // Get attributes of local directory
+ BackupClientFileAttributes localAttr;
+ localAttr.ReadAttributes(rLocalDir.c_str(),
+ true /* directories have zero mod times */);
+
+ if(!(attr.Compare(localAttr, true, true /* ignore modification times */)))
+ {
+ printf("Local directory '%s' has different attributes "
+ "to store directory '%s'.\n",
+ localName.c_str(), storeName.c_str());
+ rParams.mDifferences ++;
+ }
+ }
+
+ // Open the local directory
+ DIR *dirhandle = ::opendir(rLocalDir.c_str());
+ if(dirhandle == 0)
+ {
+ printf("ERROR: opendir on local dir '%s'\n", localName.c_str());
+ return;
+ }
+ try
+ {
+ // Read the files and directories into sets
+ std::set<std::string> localFiles;
+ std::set<std::string> localDirs;
+ struct dirent *localDirEn = 0;
+ while((localDirEn = readdir(dirhandle)) != 0)
+ {
+ // Not . and ..!
+ if(localDirEn->d_name[0] == '.' &&
+ (localDirEn->d_name[1] == '\0' || (localDirEn->d_name[1] == '.' && localDirEn->d_name[2] == '\0')))
+ {
+ // ignore, it's . or ..
+ continue;
+ }
+
+#ifndef HAVE_VALID_DIRENT_D_TYPE
+ std::string fn(rLocalDir);
+ fn += DIRECTORY_SEPARATOR_ASCHAR;
+ fn += localDirEn->d_name;
+ struct stat st;
+ if(::lstat(fn.c_str(), &st) != 0)
+ {
+ THROW_EXCEPTION(CommonException, OSFileError)
+ }
+
+ // Entry -- file or dir?
+ if(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))
+ {
+ // File or symbolic link
+ localFiles.insert(std::string(localDirEn->d_name));
+ }
+ else if(S_ISDIR(st.st_mode))
+ {
+ // Directory
+ localDirs.insert(std::string(localDirEn->d_name));
+ }
+#else
+ // Entry -- file or dir?
+ if(localDirEn->d_type == DT_REG || localDirEn->d_type == DT_LNK)
+ {
+ // File or symbolic link
+ localFiles.insert(std::string(localDirEn->d_name));
+ }
+ else if(localDirEn->d_type == DT_DIR)
+ {
+ // Directory
+ localDirs.insert(std::string(localDirEn->d_name));
+ }
+#endif
+ }
+ // Close directory
+ if(::closedir(dirhandle) != 0)
+ {
+ printf("ERROR: closedir on local dir '%s'\n",
+ localName.c_str());
+ }
+ dirhandle = 0;
+
+ // Do the same for the store directories
+ std::set<std::pair<std::string, BackupStoreDirectory::Entry *> > storeFiles;
+ std::set<std::pair<std::string, BackupStoreDirectory::Entry *> > storeDirs;
+
+ BackupStoreDirectory::Iterator i(dir);
+ BackupStoreDirectory::Entry *storeDirEn = 0;
+ while((storeDirEn = i.Next()) != 0)
+ {
+ // Decrypt filename
+ BackupStoreFilenameClear name(storeDirEn->GetName());
+
+ // What is it?
+ if((storeDirEn->GetFlags() & BackupStoreDirectory::Entry::Flags_File) == BackupStoreDirectory::Entry::Flags_File)
+ {
+ // File
+ storeFiles.insert(std::pair<std::string, BackupStoreDirectory::Entry *>(name.GetClearFilename(), storeDirEn));
+ }
+ else
+ {
+ // Dir
+ storeDirs.insert(std::pair<std::string, BackupStoreDirectory::Entry *>(name.GetClearFilename(), storeDirEn));
+ }
+ }
+
+#ifdef _MSC_VER
+ typedef std::set<std::string>::iterator string_set_iter_t;
+#else
+ typedef std::set<std::string>::const_iterator string_set_iter_t;
+#endif
+
+ // Now compare files.
+ for(std::set<std::pair<std::string, BackupStoreDirectory::Entry *> >::const_iterator i = storeFiles.begin(); i != storeFiles.end(); ++i)
+ {
+ // Does the file exist locally?
+ string_set_iter_t local(localFiles.find(i->first));
+ if(local == localFiles.end())
+ {
+ // Not found -- report
+ printf("Local file '%s" DIRECTORY_SEPARATOR
+ "%s' does not exist, "
+ "but store file '%s/%s' does.\n",
+ localName.c_str(), i->first.c_str(),
+ storeName.c_str(), i->first.c_str());
+ rParams.mDifferences ++;
+ }
+ else
+ {
+ try
+ {
+ // make local name of file for comparison
+ std::string localName(rLocalDir + DIRECTORY_SEPARATOR + i->first);
+
+ // Files the same flag?
+ bool equal = true;
+
+ // File modified after last sync flag
+ bool modifiedAfterLastSync = false;
+
+ if(rParams.mQuickCompare)
+ {
+ // Compare file -- fetch it
+ mrConnection.QueryGetBlockIndexByID(i->second->GetObjectID());
+
+ // Stream containing block index
+ std::auto_ptr<IOStream> blockIndexStream(mrConnection.ReceiveStream());
+
+ // Compare
+ equal = BackupStoreFile::CompareFileContentsAgainstBlockIndex(localName.c_str(), *blockIndexStream, mrConnection.GetTimeout());
+ }
+ else
+ {
+ // Compare file -- fetch it
+ mrConnection.QueryGetFile(DirID, i->second->GetObjectID());
+
+ // Stream containing encoded file
+ std::auto_ptr<IOStream> objectStream(mrConnection.ReceiveStream());
+
+ // Decode it
+ std::auto_ptr<BackupStoreFile::DecodedStream> fileOnServerStream;
+ // Got additional attibutes?
+ if(i->second->HasAttributes())
+ {
+ // Use these attributes
+ const StreamableMemBlock &storeAttr(i->second->GetAttributes());
+ BackupClientFileAttributes attr(storeAttr);
+ fileOnServerStream.reset(BackupStoreFile::DecodeFileStream(*objectStream, mrConnection.GetTimeout(), &attr).release());
+ }
+ else
+ {
+ // Use attributes stored in file
+ fileOnServerStream.reset(BackupStoreFile::DecodeFileStream(*objectStream, mrConnection.GetTimeout()).release());
+ }
+
+ // Should always be something in the auto_ptr, it's how the interface is defined. But be paranoid.
+ if(!fileOnServerStream.get())
+ {
+ THROW_EXCEPTION(BackupStoreException, Internal)
+ }
+
+ // Compare attributes
+ BackupClientFileAttributes localAttr;
+ box_time_t fileModTime = 0;
+ localAttr.ReadAttributes(localName.c_str(), false /* don't zero mod times */, &fileModTime);
+ modifiedAfterLastSync = (fileModTime > rParams.mLatestFileUploadTime);
+ if(!localAttr.Compare(fileOnServerStream->GetAttributes(),
+ true /* ignore attr mod time */,
+ fileOnServerStream->IsSymLink() /* ignore modification time if it's a symlink */))
+ {
+ printf("Local file '%s"
+ DIRECTORY_SEPARATOR
+ "%s' has different attributes "
+ "to store file '%s/%s'.\n",
+ localName.c_str(), i->first.c_str(), storeName.c_str(), i->first.c_str());
+ rParams.mDifferences ++;
+ if(modifiedAfterLastSync)
+ {
+ rParams.mDifferencesExplainedByModTime ++;
+ printf("(the file above was modified after the last sync time -- might be reason for difference)\n");
+ }
+ else if(i->second->HasAttributes())
+ {
+ printf("(the file above has had new attributes applied)\n");
+ }
+ }
+
+ // Compare contents, if it's a regular file not a link
+ // Remember, we MUST read the entire stream from the server.
+ if(!fileOnServerStream->IsSymLink())
+ {
+ // Open the local file
+ FileStream l(localName.c_str());
+
+ // Size
+ IOStream::pos_type fileSizeLocal = l.BytesLeftToRead();
+ IOStream::pos_type fileSizeServer = 0;
+
+ // Test the contents
+ char buf1[2048];
+ char buf2[2048];
+ while(fileOnServerStream->StreamDataLeft() && l.StreamDataLeft())
+ {
+ int size = fileOnServerStream->Read(buf1, sizeof(buf1), mrConnection.GetTimeout());
+ fileSizeServer += size;
+
+ if(l.Read(buf2, size) != size
+ || ::memcmp(buf1, buf2, size) != 0)
+ {
+ equal = false;
+ break;
+ }
+ }
+
+ // Check read all the data from the server and file -- can't be equal if local and remote aren't the same length
+ // Can't use StreamDataLeft() test on file, because if it's the same size, it won't know
+ // it's EOF yet.
+ if(fileOnServerStream->StreamDataLeft() || fileSizeServer != fileSizeLocal)
+ {
+ equal = false;
+ }
+
+ // Must always read the entire decoded string, if it's not a symlink
+ if(fileOnServerStream->StreamDataLeft())
+ {
+ // Absorb all the data remaining
+ char buffer[2048];
+ while(fileOnServerStream->StreamDataLeft())
+ {
+ fileOnServerStream->Read(buffer, sizeof(buffer), mrConnection.GetTimeout());
+ }
+ }
+ }
+ }
+
+ // Report if not equal.
+ if(!equal)
+ {
+ printf("Local file '%s"
+ DIRECTORY_SEPARATOR
+ "%s' has different contents "
+ "to store file '%s/%s'.\n",
+ localName.c_str(), i->first.c_str(), storeName.c_str(), i->first.c_str());
+ rParams.mDifferences ++;
+ if(modifiedAfterLastSync)
+ {
+ rParams.mDifferencesExplainedByModTime ++;
+ printf("(the file above was modified after the last sync time -- might be reason for difference)\n");
+ }
+ else if(i->second->HasAttributes())
+ {
+ printf("(the file above has had new attributes applied)\n");
+ }
+ }
+ }
+ catch(BoxException &e)
+ {
+ printf("ERROR: (%d/%d) during file fetch and comparsion for '%s/%s'\n",
+ e.GetType(),
+ e.GetSubType(),
+ storeName.c_str(),
+ i->first.c_str());
+ }
+ catch(...)
+ {
+ printf("ERROR: (unknown) during file fetch and comparsion for '%s/%s'\n", storeName.c_str(), i->first.c_str());
+ }
+
+ // Remove from set so that we know it's been compared
+ localFiles.erase(local);
+ }
+ }
+
+ // Report any files which exist on the locally, but not on the store
+ for(string_set_iter_t i = localFiles.begin(); i != localFiles.end(); ++i)
+ {
+ std::string localFileName(rLocalDir +
+ DIRECTORY_SEPARATOR + *i);
+ // Should this be ignored (ie is excluded)?
+ if(rParams.mpExcludeFiles == 0 ||
+ !(rParams.mpExcludeFiles->IsExcluded(localFileName)))
+ {
+ printf("Local file '%s" DIRECTORY_SEPARATOR
+ "%s' exists, but store file '%s/%s' "
+ "does not exist.\n",
+ localName.c_str(), (*i).c_str(),
+ storeName.c_str(), (*i).c_str());
+ rParams.mDifferences ++;
+
+ // Check the file modification time
+ {
+ struct stat st;
+ if(::stat(localFileName.c_str(), &st) == 0)
+ {
+ if(FileModificationTime(st) > rParams.mLatestFileUploadTime)
+ {
+ rParams.mDifferencesExplainedByModTime ++;
+ printf("(the file above was modified after the last sync time -- might be reason for difference)\n");
+ }
+ }
+ }
+ }
+ else
+ {
+ rParams.mExcludedFiles ++;
+ }
+ }
+
+ // Finished with the files, clear the sets to reduce memory usage slightly
+ localFiles.clear();
+ storeFiles.clear();
+
+ // Now do the directories, recusively to check subdirectories
+ for(std::set<std::pair<std::string, BackupStoreDirectory::Entry *> >::const_iterator i = storeDirs.begin(); i != storeDirs.end(); ++i)
+ {
+ // Does the directory exist locally?
+ string_set_iter_t local(localDirs.find(i->first));
+ if(local == localDirs.end())
+ {
+ // Not found -- report
+ printf("Local directory '%s"
+ DIRECTORY_SEPARATOR "%s' "
+ "does not exist, but store directory "
+ "'%s/%s' does.\n",
+ localName.c_str(), i->first.c_str(),
+ storeName.c_str(), i->first.c_str());
+ rParams.mDifferences ++;
+ }
+ else
+ {
+ // Compare directory
+ Compare(i->second->GetObjectID(), rStoreDir + "/" + i->first, rLocalDir + DIRECTORY_SEPARATOR + i->first, rParams);
+
+ // Remove from set so that we know it's been compared
+ localDirs.erase(local);
+ }
+ }
+
+ // Report any files which exist on the locally, but not on the store
+ for(std::set<std::string>::const_iterator i = localDirs.begin(); i != localDirs.end(); ++i)
+ {
+ std::string localName(rLocalDir + DIRECTORY_SEPARATOR + *i);
+ // Should this be ignored (ie is excluded)?
+ if(rParams.mpExcludeDirs == 0 || !(rParams.mpExcludeDirs->IsExcluded(localName)))
+ {
+ printf("Local directory '%s/%s' exists, but "
+ "store directory '%s/%s' does not exist.\n",
+ localName.c_str(), (*i).c_str(),
+ storeName.c_str(), (*i).c_str());
+ rParams.mDifferences ++;
+ }
+ else
+ {
+ rParams.mExcludedDirs ++;
+ }
+ }
+
+ }
+ catch(...)
+ {
+ if(dirhandle != 0)
+ {
+ ::closedir(dirhandle);
+ }
+ }
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::CommandRestore(const std::vector<std::string> &, const bool *)
+// Purpose: Restore a directory
+// Created: 23/11/03
+//
+// --------------------------------------------------------------------------
+void BackupQueries::CommandRestore(const std::vector<std::string> &args, const bool *opts)
+{
+ // Check arguments
+ if(args.size() != 2)
+ {
+ printf("Incorrect usage.\nrestore [-d] [-r] [-i] <directory-name> <local-directory-name>\n");
+ return;
+ }
+
+ // Restoring deleted things?
+ bool restoreDeleted = opts['d'];
+
+ // Get directory ID
+ int64_t dirID = 0;
+ if(opts['i'])
+ {
+ // Specified as ID.
+ dirID = ::strtoll(args[0].c_str(), 0, 16);
+ if(dirID == LLONG_MIN || dirID == LLONG_MAX || dirID == 0)
+ {
+ printf("Not a valid object ID (specified in hex)\n");
+ return;
+ }
+ }
+ else
+ {
+#ifdef WIN32
+ std::string storeDirEncoded;
+ if(!ConvertConsoleToUtf8(args[0].c_str(), storeDirEncoded))
+ return;
+#else
+ const std::string& storeDirEncoded(args[0]);
+#endif
+
+ // Look up directory ID
+ dirID = FindDirectoryObjectID(storeDirEncoded,
+ false /* no old versions */,
+ restoreDeleted /* find deleted dirs */);
+ }
+
+ // Allowable?
+ if(dirID == 0)
+ {
+ printf("Directory '%s' not found on server\n", args[0].c_str());
+ return;
+ }
+ if(dirID == BackupProtocolClientListDirectory::RootDirectory)
+ {
+ printf("Cannot restore the root directory -- restore locations individually.\n");
+ return;
+ }
+
+#ifdef WIN32
+ std::string localName;
+ if(!ConvertConsoleToUtf8(args[1].c_str(), localName)) return;
+#else
+ std::string localName(args[1]);
+#endif
+
+ // Go and restore...
+ switch(BackupClientRestore(mrConnection, dirID, localName.c_str(),
+ true /* print progress dots */, restoreDeleted,
+ false /* don't undelete after restore! */,
+ opts['r'] /* resume? */))
+ {
+ case Restore_Complete:
+ printf("Restore complete\n");
+ break;
+
+ case Restore_ResumePossible:
+ printf("Resume possible -- repeat command with -r flag to resume\n");
+ break;
+
+ case Restore_TargetExists:
+ printf("The target directory exists. You cannot restore over an existing directory.\n");
+ break;
+
+ default:
+ printf("ERROR: Unknown restore result.\n");
+ break;
+ }
+}
+
+
+
+// These are autogenerated by a script.
+extern char *help_commands[];
+extern char *help_text[];
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::CommandHelp(const std::vector<std::string> &args)
+// Purpose: Display help on commands
+// Created: 15/2/04
+//
+// --------------------------------------------------------------------------
+void BackupQueries::CommandHelp(const std::vector<std::string> &args)
+{
+ if(args.size() == 0)
+ {
+ // Display a list of all commands
+ printf("Available commands are:\n");
+ for(int c = 0; help_commands[c] != 0; ++c)
+ {
+ printf(" %s\n", help_commands[c]);
+ }
+ printf("Type \"help <command>\" for more information on a command.\n\n");
+ }
+ else
+ {
+ // Display help on a particular command
+ int c;
+ for(c = 0; help_commands[c] != 0; ++c)
+ {
+ if(::strcmp(help_commands[c], args[0].c_str()) == 0)
+ {
+ // Found the command, print help
+ printf("\n%s\n", help_text[c]);
+ break;
+ }
+ }
+ if(help_commands[c] == 0)
+ {
+ printf("No help found for command '%s'\n", args[0].c_str());
+ }
+ }
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::CommandUsage()
+// Purpose: Display storage space used on server
+// Created: 19/4/04
+//
+// --------------------------------------------------------------------------
+void BackupQueries::CommandUsage()
+{
+ // Request full details from the server
+ std::auto_ptr<BackupProtocolClientAccountUsage> usage(mrConnection.QueryGetAccountUsage());
+
+ // Display each entry in turn
+ int64_t hardLimit = usage->GetBlocksHardLimit();
+ int32_t blockSize = usage->GetBlockSize();
+ CommandUsageDisplayEntry("Used", usage->GetBlocksUsed(), hardLimit, blockSize);
+ CommandUsageDisplayEntry("Old files", usage->GetBlocksInOldFiles(), hardLimit, blockSize);
+ CommandUsageDisplayEntry("Deleted files", usage->GetBlocksInDeletedFiles(), hardLimit, blockSize);
+ CommandUsageDisplayEntry("Directories", usage->GetBlocksInDirectories(), hardLimit, blockSize);
+ CommandUsageDisplayEntry("Soft limit", usage->GetBlocksSoftLimit(), hardLimit, blockSize);
+ CommandUsageDisplayEntry("Hard limit", hardLimit, hardLimit, blockSize);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::CommandUsageDisplayEntry(const char *, int64_t, int64_t, int32_t)
+// Purpose: Display an entry in the usage table
+// Created: 19/4/04
+//
+// --------------------------------------------------------------------------
+void BackupQueries::CommandUsageDisplayEntry(const char *Name, int64_t Size, int64_t HardLimit, int32_t BlockSize)
+{
+ // Calculate size in Mb
+ double mb = (((double)Size) * ((double)BlockSize)) / ((double)(1024*1024));
+ int64_t percent = (Size * 100) / HardLimit;
+
+ // Bar graph
+ char bar[41];
+ unsigned int b = (int)((Size * (sizeof(bar)-1)) / HardLimit);
+ if(b > sizeof(bar)-1) {b = sizeof(bar)-1;}
+ for(unsigned int l = 0; l < b; l++)
+ {
+ bar[l] = '*';
+ }
+ bar[b] = '\0';
+
+ // Print the entryj
+ ::printf("%14s %10.1fMb %3d%% %s\n", Name, mb, (int32_t)percent, bar);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::CommandUndelete(const std::vector<std::string> &, const bool *)
+// Purpose: Undelete a directory
+// Created: 23/11/03
+//
+// --------------------------------------------------------------------------
+void BackupQueries::CommandUndelete(const std::vector<std::string> &args, const bool *opts)
+{
+ // Check arguments
+ if(args.size() != 1)
+ {
+ printf("Incorrect usage.\nundelete <directory-name>\n");
+ return;
+ }
+
+#ifdef WIN32
+ std::string storeDirEncoded;
+ if(!ConvertConsoleToUtf8(args[0].c_str(), storeDirEncoded)) return;
+#else
+ const std::string& storeDirEncoded(args[0]);
+#endif
+
+ // Get directory ID
+ int64_t dirID = FindDirectoryObjectID(storeDirEncoded,
+ false /* no old versions */, true /* find deleted dirs */);
+
+ // Allowable?
+ if(dirID == 0)
+ {
+ printf("Directory '%s' not found on server\n", args[0].c_str());
+ return;
+ }
+ if(dirID == BackupProtocolClientListDirectory::RootDirectory)
+ {
+ printf("Cannot undelete the root directory.\n");
+ return;
+ }
+
+ // Undelete
+ mrConnection.QueryUndeleteDirectory(dirID);
+}