summaryrefslogtreecommitdiff
path: root/lib/bbackupquery
diff options
context:
space:
mode:
Diffstat (limited to 'lib/bbackupquery')
-rw-r--r--lib/bbackupquery/BackupQueries.cpp2410
-rw-r--r--lib/bbackupquery/BackupQueries.h440
-rw-r--r--lib/bbackupquery/BoxBackupCompareParams.h112
-rw-r--r--lib/bbackupquery/CommandCompletion.cpp604
-rw-r--r--lib/bbackupquery/documentation.txt194
-rwxr-xr-xlib/bbackupquery/makedocumentation.pl.in75
6 files changed, 3835 insertions, 0 deletions
diff --git a/lib/bbackupquery/BackupQueries.cpp b/lib/bbackupquery/BackupQueries.cpp
new file mode 100644
index 00000000..bcb1827e
--- /dev/null
+++ b/lib/bbackupquery/BackupQueries.cpp
@@ -0,0 +1,2410 @@
+// --------------------------------------------------------------------------
+//
+// 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 <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 <algorithm>
+#include <cstring>
+#include <limits>
+#include <iostream>
+#include <ostream>
+#include <set>
+
+#include "BackupClientFileAttributes.h"
+#include "BackupClientMakeExcludeList.h"
+#include "BackupClientRestore.h"
+#include "BackupQueries.h"
+#include "BackupStoreDirectory.h"
+#include "BackupStoreException.h"
+#include "BackupStoreFile.h"
+#include "BackupStoreFilenameClear.h"
+#include "BoxTimeToText.h"
+#include "CommonException.h"
+#include "Configuration.h"
+#include "ExcludeList.h"
+#include "FileModificationTime.h"
+#include "FileStream.h"
+#include "IOStream.h"
+#include "Logging.h"
+#include "PathUtils.h"
+#include "SelfFlushingStream.h"
+#include "Utils.h"
+#include "autogen_BackupProtocol.h"
+#include "autogen_CipherException.h"
+
+#include "MemLeakFindOn.h"
+
+// min() and max() macros from stdlib.h break numeric_limits<>::min(), etc.
+#undef min
+#undef max
+
+#define COMPARE_RETURN_SAME 1
+#define COMPARE_RETURN_DIFFERENT 2
+#define COMPARE_RETURN_ERROR 3
+#define COMMAND_RETURN_ERROR 4
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::BackupQueries()
+// Purpose: Constructor
+// Created: 2003/10/10
+//
+// --------------------------------------------------------------------------
+BackupQueries::BackupQueries(BackupProtocolCallable &rConnection,
+ const Configuration &rConfiguration, bool readWrite)
+ : mReadWrite(readWrite),
+ mrConnection(rConnection),
+ mrConfiguration(rConfiguration),
+ mQuitNow(false),
+ mRunningAsRoot(false),
+ mWarnedAboutOwnerAttributes(false),
+ mReturnCode(0) // default return code
+{
+ #ifdef WIN32
+ mRunningAsRoot = TRUE;
+ #else
+ mRunningAsRoot = (::geteuid() == 0);
+ #endif
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::~BackupQueries()
+// Purpose: Destructor
+// Created: 2003/10/10
+//
+// --------------------------------------------------------------------------
+BackupQueries::~BackupQueries()
+{
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::DoCommand(const char *, bool)
+// Purpose: Perform a command
+// Created: 2003/10/10
+//
+// --------------------------------------------------------------------------
+void BackupQueries::DoCommand(ParsedCommand& rCommand)
+{
+ // Check...
+
+ if(rCommand.mFailed)
+ {
+ BOX_ERROR("Parse failed: unknown command '" <<
+ rCommand.mCmdElements[0] << "' or failed to convert "
+ "encoding of arguments");
+ return;
+ }
+
+ if(rCommand.mCmdElements.size() < 1)
+ {
+ // blank command
+ return;
+ }
+
+ if(rCommand.pSpec->type == Command_sh &&
+ rCommand.mCmdElements.size() == 2)
+ {
+ // Yes, run shell command
+ int result = ::system(rCommand.mCmdElements[1].c_str());
+ if(result != 0)
+ {
+ BOX_WARNING("System command returned error code " <<
+ result);
+ SetReturnCode(ReturnCode::Command_Error);
+ }
+ return;
+ }
+
+ if(rCommand.pSpec->type == Command_Unknown)
+ {
+ // No such command
+ BOX_ERROR("Unrecognised command: " << rCommand.mCmdElements[0]);
+ return;
+ }
+
+ // Arguments
+ std::vector<std::string> args(rCommand.mCmdElements.begin() + 1,
+ rCommand.mCmdElements.end());
+
+ // Set up options
+ bool opts[256];
+ for(int o = 0; o < 256; ++o) opts[o] = false;
+ // BLOCK
+ {
+ // options
+ const char *c = rCommand.mOptions.c_str();
+ while(*c != 0)
+ {
+ // Valid option?
+ if(::strchr(rCommand.pSpec->opts, *c) == NULL)
+ {
+ BOX_ERROR("Invalid option '" << *c << "' for "
+ "command " << rCommand.pSpec->name);
+ return;
+ }
+ opts[(int)*c] = true;
+ ++c;
+ }
+ }
+
+ if(rCommand.pSpec->type != Command_Quit)
+ {
+ // If not a quit command, set the return code to zero
+ SetReturnCode(ReturnCode::Command_OK);
+ }
+
+ // Handle command
+ switch(rCommand.pSpec->type)
+ {
+ case Command_Quit:
+ mQuitNow = true;
+ break;
+
+ case Command_List:
+ CommandList(args, opts);
+ break;
+
+ case Command_pwd:
+ {
+ // Simple implementation, so do it here
+ BOX_NOTICE(GetCurrentDirectoryName() << " (" <<
+ BOX_FORMAT_OBJECTID(GetCurrentDirectoryID()) <<
+ ")");
+ }
+ break;
+
+ case Command_cd:
+ CommandChangeDir(args, opts);
+ break;
+
+ case Command_lcd:
+ CommandChangeLocalDir(args);
+ break;
+
+ case Command_sh:
+ BOX_ERROR("The command to run must be specified as an argument.");
+ 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(opts);
+ break;
+
+ case Command_Help:
+ CommandHelp(args);
+ break;
+
+ case Command_Undelete:
+ CommandUndelete(args, opts);
+ break;
+
+ case Command_Delete:
+ CommandDelete(args, opts);
+ break;
+
+ default:
+ BOX_ERROR("Unknown command: " << rCommand.mCmdElements[0]);
+ break;
+ }
+}
+
+#define LIST_OPTION_TIMES_ATTRIBS 'a'
+#define LIST_OPTION_SORT_NO_DIRS_FIRST 'D'
+#define LIST_OPTION_NOFLAGS 'F'
+#define LIST_OPTION_DISPLAY_HASH 'h'
+#define LIST_OPTION_SORT_ID 'i'
+#define LIST_OPTION_NOOBJECTID 'I'
+#define LIST_OPTION_SORT_REVERSE 'r'
+#define LIST_OPTION_RECURSIVE 'R'
+#define LIST_OPTION_SIZEINBLOCKS 's'
+#define LIST_OPTION_SORT_SIZE 'S'
+#define LIST_OPTION_TIMES_LOCAL 't'
+#define LIST_OPTION_TIMES_UTC 'T'
+#define LIST_OPTION_SORT_NONE 'U'
+
+// --------------------------------------------------------------------------
+//
+// 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)
+{
+ // 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)
+ {
+ BOX_ERROR("Directory '" << args[0] << "' not found "
+ "on store.");
+ SetReturnCode(ReturnCode::Command_Error);
+ return;
+ }
+ }
+
+ // List it
+ List(rootDir, listRoot, opts, true /* first level to list */);
+}
+
+static std::string GetTimeString(BackupStoreDirectory::Entry& en,
+ bool useLocalTime, bool showAttrModificationTimes)
+{
+ std::ostringstream out;
+ box_time_t originalTime, newAttributesTime;
+
+ // there is no attribute modification time in the directory
+ // entry, unfortunately, so we can't display it.
+ originalTime = en.GetModificationTime();
+ out << BoxTimeToISO8601String(originalTime, useLocalTime);
+
+ if(en.HasAttributes())
+ {
+ const StreamableMemBlock &storeAttr(en.GetAttributes());
+ BackupClientFileAttributes attr(storeAttr);
+
+ box_time_t NewModificationTime, NewAttrModificationTime;
+ attr.GetModificationTimes(&NewModificationTime,
+ &NewAttrModificationTime);
+
+ if (showAttrModificationTimes)
+ {
+ newAttributesTime = NewAttrModificationTime;
+ }
+ else
+ {
+ newAttributesTime = NewModificationTime;
+ }
+
+ if (newAttributesTime == originalTime)
+ {
+ out << "*";
+ }
+ else
+ {
+ out << "~" << BoxTimeToISO8601String(newAttributesTime,
+ useLocalTime);
+ }
+ }
+ else
+ {
+ out << " ";
+ }
+
+ return out.str();
+}
+
+/* We need a way to pass options to sort functions for sorting. The algorithm
+ * doesn't seem to provide a way to do this, so I'm using a global variable.
+ * Which is not thread safe, but we don't currently use threads so that should
+ * be OK. Do not use threads without checking!
+ */
+const bool *gThreadUnsafeOptions;
+
+int DirsFirst(BackupStoreDirectory::Entry* a,
+ BackupStoreDirectory::Entry* b)
+{
+ if (a->IsDir() && !b->IsDir())
+ {
+ return -1; // a < b
+ }
+ else if (!a->IsDir() && b->IsDir())
+ {
+ return 1; // b > a
+ }
+ else
+ {
+ return 0; // continue comparison
+ }
+}
+
+#define MAYBE_DIRS_FIRST(a, b) \
+ if (!gThreadUnsafeOptions[LIST_OPTION_SORT_NO_DIRS_FIRST]) \
+ { \
+ int result = DirsFirst(a, b); \
+ if (result < 0) return true; /* a < b */ \
+ else if (result > 0) return false; /* a > b */ \
+ /* else: fall through */ \
+ }
+
+#define MAYBE_REVERSE(result) \
+ (result != gThreadUnsafeOptions[LIST_OPTION_SORT_REVERSE])
+// result is false, opts[reverse] is false => return false
+// result is false, opts[reverse] is true => return true
+// result is true, opts[reverse] is false => return true
+// result is true, opts[reverse] is true => return false
+// this is logical XOR, for which the boolean operator is !=.
+
+bool SortById(BackupStoreDirectory::Entry* a,
+ BackupStoreDirectory::Entry* b)
+{
+ MAYBE_DIRS_FIRST(a, b);
+ bool result = (a->GetObjectID() < b->GetObjectID());
+ return MAYBE_REVERSE(result);
+}
+
+bool SortBySize(BackupStoreDirectory::Entry* a,
+ BackupStoreDirectory::Entry* b)
+{
+ MAYBE_DIRS_FIRST(a, b);
+ bool result = (a->GetSizeInBlocks() < b->GetSizeInBlocks());
+ return MAYBE_REVERSE(result);
+}
+
+bool SortByName(BackupStoreDirectory::Entry* a,
+ BackupStoreDirectory::Entry* b)
+{
+ MAYBE_DIRS_FIRST(a, b);
+ BackupStoreFilenameClear afc(a->GetName());
+ BackupStoreFilenameClear bfc(b->GetName());
+ std::string an = afc.GetClearFilename();
+ std::string bn = bfc.GetClearFilename();
+ bool result = (an < bn);
+ return MAYBE_REVERSE(result);
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::List(int64_t, const std::string &, const bool *, 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, std::ostream* pOut)
+{
+#ifdef WIN32
+ DWORD n_chars;
+ HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
+#endif
+
+ // Generate exclude flags
+ int16_t excludeFlags = BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING;
+ if(!opts[LIST_OPTION_ALLOWOLD]) excludeFlags |= BackupProtocolListDirectory::Flags_OldVersion;
+ if(!opts[LIST_OPTION_ALLOWDELETED]) excludeFlags |= BackupProtocolListDirectory::Flags_Deleted;
+
+ // Do communication
+ try
+ {
+ mrConnection.QueryListDirectory(
+ DirID,
+ BackupProtocolListDirectory::Flags_INCLUDE_EVERYTHING,
+ // both files and directories
+ excludeFlags,
+ true /* want attributes */);
+ }
+ catch (std::exception &e)
+ {
+ BOX_ERROR("Failed to list directory: " << e.what());
+ SetReturnCode(ReturnCode::Command_Error);
+ return;
+ }
+ catch (...)
+ {
+ BOX_ERROR("Failed to list directory: unknown error");
+ SetReturnCode(ReturnCode::Command_Error);
+ return;
+ }
+
+ // Retrieve the directory from the stream following
+ BackupStoreDirectory dir;
+ std::auto_ptr<IOStream> dirstream(mrConnection.ReceiveStream());
+ dir.ReadFromStream(*dirstream, mrConnection.GetTimeout());
+
+ // Store entry pointers in a std::vector for sorting
+ BackupStoreDirectory::Iterator i(dir);
+ BackupStoreDirectory::Entry *en = 0;
+ std::vector<BackupStoreDirectory::Entry*> sorted_entries;
+ while((en = i.Next()) != 0)
+ {
+ sorted_entries.push_back(en);
+ }
+
+ // Typedef to avoid mind-bending while dealing with pointers to functions.
+ typedef bool (EntryComparator_t)(BackupStoreDirectory::Entry* a,
+ BackupStoreDirectory::Entry* b);
+ // Default is no comparator, i.e. no sorting.
+ EntryComparator_t* pComparator = NULL;
+
+ if (opts[LIST_OPTION_SORT_ID])
+ {
+ pComparator = &SortById;
+ }
+ else if (opts[LIST_OPTION_SORT_SIZE])
+ {
+ pComparator = &SortBySize;
+ }
+ else if (opts[LIST_OPTION_SORT_NONE])
+ {
+ // do nothing
+ }
+ else // sort by name
+ {
+ pComparator = &SortByName;
+ }
+
+ if (pComparator != NULL)
+ {
+ gThreadUnsafeOptions = opts;
+ sort(sorted_entries.begin(), sorted_entries.end(),
+ pComparator);
+ gThreadUnsafeOptions = NULL;
+ }
+
+ for (std::vector<BackupStoreDirectory::Entry*>::const_iterator
+ i = sorted_entries.begin();
+ i != sorted_entries.end(); i++)
+ {
+ en = *i;
+ std::ostringstream buf;
+
+ // Display this entry
+ BackupStoreFilenameClear clear(en->GetName());
+
+ // Object ID?
+ if(!opts[LIST_OPTION_NOOBJECTID])
+ {
+ // add object ID to line
+ buf << std::hex << std::internal << std::setw(8) <<
+ std::setfill('0') << en->GetObjectID() <<
+ std::dec << " ";
+ }
+
+ // 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';
+ buf << displayflags;
+
+ if(en_flags != 0)
+ {
+ buf << "[ERROR: Entry has additional flags set] ";
+ }
+ }
+
+ if(opts[LIST_OPTION_TIMES_UTC])
+ {
+ // Show UTC times...
+ buf << GetTimeString(*en, false,
+ opts[LIST_OPTION_TIMES_ATTRIBS]) << " ";
+ }
+
+ if(opts[LIST_OPTION_TIMES_LOCAL])
+ {
+ // Show local times...
+ buf << GetTimeString(*en, true,
+ opts[LIST_OPTION_TIMES_ATTRIBS]) << " ";
+ }
+
+ if(opts[LIST_OPTION_DISPLAY_HASH])
+ {
+ buf << std::hex << std::internal << std::setw(16) <<
+ std::setfill('0') << en->GetAttributesHash() <<
+ std::dec;
+ }
+
+ if(opts[LIST_OPTION_SIZEINBLOCKS])
+ {
+ buf << std::internal << std::setw(5) <<
+ std::setfill('0') << en->GetSizeInBlocks() <<
+ " ";
+ }
+
+ // add name
+ if(!FirstLevel)
+ {
+#ifdef WIN32
+ std::string listRootDecoded;
+ if(!ConvertUtf8ToConsole(rListRoot.c_str(),
+ listRootDecoded)) return;
+ listRootDecoded += "/";
+ buf << listRootDecoded;
+ WriteConsole(hOut, listRootDecoded.c_str(),
+ strlen(listRootDecoded.c_str()), &n_chars, NULL);
+#else
+ buf << rListRoot << "/";
+#endif
+ }
+
+ std::string fileName;
+ try
+ {
+ fileName = clear.GetClearFilename();
+ }
+ catch(CipherException &e)
+ {
+ fileName = "<decrypt failed>";
+ }
+
+#ifdef WIN32
+ std::string fileNameUtf8 = fileName;
+ if(!ConvertUtf8ToConsole(fileNameUtf8, fileName))
+ {
+ fileName = fileNameUtf8 + " [convert encoding failed]";
+ }
+#endif
+
+ buf << fileName;
+
+ if(!en->GetName().IsEncrypted())
+ {
+ buf << " [FILENAME NOT ENCRYPTED]";
+ }
+
+ buf << std::endl;
+
+ if(pOut)
+ {
+ (*pOut) << buf.str();
+ }
+ else
+ {
+#ifdef WIN32
+ std::string line = buf.str();
+ if (!WriteConsole(hOut, line.c_str(), line.size(),
+ &n_chars, NULL))
+ {
+ // WriteConsole failed, try standard method
+ std::cout << buf.str();
+ }
+#else
+ std::cout << buf.str();
+#endif
+ }
+
+ // 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 */,
+ pOut);
+ }
+ }
+ }
+}
+
+
+// --------------------------------------------------------------------------
+//
+// 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 = BackupProtocolListDirectory::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 = BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING;
+ if(!AllowOldVersion) excludeFlags |= BackupProtocolListDirectory::Flags_OldVersion;
+ if(!AllowDeletedDirs) excludeFlags |= BackupProtocolListDirectory::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):BackupProtocolListDirectory::RootDirectory;
+ }
+ else
+ {
+ // At root anyway
+ dirID = BackupProtocolListDirectory::RootDirectory;
+ }
+ }
+ else
+ {
+ // Not blank element. Read current directory.
+ std::auto_ptr<BackupProtocolSuccess> dirreply(mrConnection.QueryListDirectory(
+ dirID,
+ BackupProtocolListDirectory::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 BackupProtocolListDirectory::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)
+ {
+ BOX_ERROR("Incorrect usage. cd [-o] [-d] <directory>");
+ SetReturnCode(ReturnCode::Command_Error);
+ 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)
+ {
+ BOX_ERROR("Directory '" << args[0] << "' not found.");
+ SetReturnCode(ReturnCode::Command_Error);
+ 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)
+ {
+ BOX_ERROR("Incorrect usage. lcd <local-directory>");
+ SetReturnCode(ReturnCode::Command_Error);
+ return;
+ }
+
+ // Try changing directory
+#ifdef WIN32
+ std::string dirName;
+ if(!ConvertConsoleToUtf8(args[0].c_str(), dirName))
+ {
+ BOX_ERROR("Failed to convert path from console encoding.");
+ SetReturnCode(ReturnCode::Command_Error);
+ return;
+ }
+ int result = ::chdir(dirName.c_str());
+#else
+ int result = ::chdir(args[0].c_str());
+#endif
+ if(result != 0)
+ {
+ if(errno == ENOENT || errno == ENOTDIR)
+ {
+ BOX_ERROR("Directory '" << args[0] << "' does not exist.");
+ }
+ else
+ {
+ BOX_LOG_SYS_ERROR("Failed to change to directory "
+ "'" << args[0] << "'");
+ }
+
+ SetReturnCode(ReturnCode::Command_Error);
+ return;
+ }
+
+ // Report current dir
+ char wd[PATH_MAX];
+ if(::getcwd(wd, PATH_MAX) == 0)
+ {
+ BOX_LOG_SYS_ERROR("Error getting current directory");
+ SetReturnCode(ReturnCode::Command_Error);
+ return;
+ }
+
+#ifdef WIN32
+ if(!ConvertUtf8ToConsole(wd, dirName))
+ {
+ BOX_ERROR("Failed to convert new path from console encoding.");
+ SetReturnCode(ReturnCode::Command_Error);
+ return;
+ }
+ BOX_INFO("Local current directory is now '" << dirName << "'.");
+#else
+ BOX_INFO("Local current directory is now '" << 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)
+ {
+ BOX_ERROR("Incorrect usage. getobject <object-id> "
+ "<local-filename>");
+ return;
+ }
+
+ int64_t id = ::strtoll(args[0].c_str(), 0, 16);
+ if(id == std::numeric_limits<long long>::min() || id == std::numeric_limits<long long>::max() || id == 0)
+ {
+ BOX_ERROR("Not a valid object ID (specified in hex): " <<
+ args[0]);
+ return;
+ }
+
+ // Does file exist?
+ EMU_STRUCT_STAT st;
+ if(EMU_STAT(args[1].c_str(), &st) == 0 || errno != ENOENT)
+ {
+ BOX_ERROR("The local file '" << args[1] << " already exists.");
+ return;
+ }
+
+ // Open file
+ FileStream out(args[1].c_str(), O_WRONLY | O_CREAT | O_EXCL);
+
+ // Request that object
+ try
+ {
+ // Request object
+ std::auto_ptr<BackupProtocolSuccess> getobj(mrConnection.QueryGetObject(id));
+
+ // Stream that object out to the file
+ std::auto_ptr<IOStream> objectStream(mrConnection.ReceiveStream());
+ objectStream->CopyStreamTo(out);
+
+ BOX_INFO("Object ID " << BOX_FORMAT_OBJECTID(id) <<
+ " fetched successfully.");
+ }
+ catch(ConnectionException &e)
+ {
+ if(mrConnection.GetLastErrorType() == BackupProtocolError::Err_DoesNotExist)
+ {
+ BOX_ERROR("Object ID " << BOX_FORMAT_OBJECTID(id) <<
+ " does not exist on store.");
+ ::unlink(args[1].c_str());
+ }
+ else
+ {
+ BOX_ERROR("Error occured fetching object.");
+ ::unlink(args[1].c_str());
+ }
+ }
+ catch(...)
+ {
+ ::unlink(args[1].c_str());
+ BOX_ERROR("Error occured fetching object.");
+ }
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::FindFileID(const std::string&
+// rNameOrIdString, const bool *options,
+// int64_t *pDirIdOut, std::string* pFileNameOut)
+// Purpose: Locate a file on the store (either by name or by
+// object ID, depending on opts['i'], where name can
+// include a path) and return the file ID, placing the
+// directory ID in *pDirIdOut and the filename part
+// of the path in *pFileNameOut (if not NULL).
+// Created: 2008-09-12
+//
+// --------------------------------------------------------------------------
+int64_t BackupQueries::FindFileID(const std::string& rNameOrIdString,
+ const bool *opts, int64_t *pDirIdOut, std::string* pFileNameOut,
+ int16_t flagsInclude, int16_t flagsExclude, int16_t* pFlagsOut)
+{
+ // Find object ID somehow
+ int64_t fileId;
+ int64_t dirId = GetCurrentDirectoryID();
+ std::string fileName = rNameOrIdString;
+
+ if(!opts['i'])
+ {
+ // does this remote filename include a path?
+ std::string::size_type index = fileName.rfind('/');
+ if(index != std::string::npos)
+ {
+ std::string dirName(fileName.substr(0, index));
+ fileName = fileName.substr(index + 1);
+
+ dirId = FindDirectoryObjectID(dirName);
+ if(dirId == 0)
+ {
+ BOX_ERROR("Directory '" << dirName <<
+ "' not found.");
+ return 0;
+ }
+ }
+ }
+
+ BackupStoreFilenameClear fn(fileName);
+
+ // Need to look it up in the current directory
+ mrConnection.QueryListDirectory(
+ dirId, flagsInclude, flagsExclude,
+ true /* do want attributes */);
+
+ // Retrieve the directory from the stream following
+ BackupStoreDirectory dir;
+ std::auto_ptr<IOStream> dirstream(mrConnection.ReceiveStream());
+ dir.ReadFromStream(*dirstream, mrConnection.GetTimeout());
+ BackupStoreDirectory::Entry *en;
+
+ if(opts['i'])
+ {
+ // Specified as ID.
+ fileId = ::strtoll(rNameOrIdString.c_str(), 0, 16);
+ if(fileId == std::numeric_limits<long long>::min() ||
+ fileId == std::numeric_limits<long long>::max() ||
+ fileId == 0)
+ {
+ BOX_ERROR("Not a valid object ID (specified in hex): "
+ << rNameOrIdString);
+ return 0;
+ }
+
+ // Check that the item is actually in the directory
+ en = dir.FindEntryByID(fileId);
+ if(en == 0)
+ {
+ BOX_ERROR("File ID " <<
+ BOX_FORMAT_OBJECTID(fileId) <<
+ " not found in current directory on store.\n"
+ "(You can only access files by ID from the "
+ "current directory.)");
+ return 0;
+ }
+ }
+ else
+ {
+ // Specified by name, find the object in the directory to get the ID
+ BackupStoreDirectory::Iterator i(dir);
+ en = i.FindMatchingClearName(fn);
+ if(en == 0)
+ {
+ BOX_ERROR("Filename '" << rNameOrIdString << "' "
+ "not found in current directory on store.\n"
+ "(Subdirectories in path not searched.)");
+ return 0;
+ }
+
+ fileId = en->GetObjectID();
+ }
+
+ *pDirIdOut = dirId;
+
+ if(pFlagsOut)
+ {
+ *pFlagsOut = en->GetFlags();
+ }
+
+ if(pFileNameOut)
+ {
+ BackupStoreFilenameClear entryName(en->GetName());
+ *pFileNameOut = entryName.GetClearFilename();
+ }
+
+ return fileId;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// 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(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)
+ {
+ BOX_ERROR("Incorrect usage.\n"
+ "get <remote-filename> [<local-filename>] or\n"
+ "get -i <object-id> <local-filename>");
+ return;
+ }
+
+ // Find object ID somehow
+ int64_t fileId, dirId;
+ std::string localName;
+
+#ifdef WIN32
+ for (std::vector<std::string>::iterator
+ i = args.begin(); i != args.end(); i++)
+ {
+ std::string out;
+ if(!ConvertConsoleToUtf8(i->c_str(), out))
+ {
+ BOX_ERROR("Failed to convert encoding.");
+ return;
+ }
+ *i = out;
+ }
+#endif
+
+ int16_t flagsExclude;
+
+ if(opts['i'])
+ {
+ // can retrieve anything by ID
+ flagsExclude = BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING;
+ }
+ else
+ {
+ // only current versions by name
+ flagsExclude =
+ BackupProtocolListDirectory::Flags_OldVersion |
+ BackupProtocolListDirectory::Flags_Deleted;
+ }
+
+
+ fileId = FindFileID(args[0], opts, &dirId, &localName,
+ BackupProtocolListDirectory::Flags_File, // just files
+ flagsExclude, NULL /* don't care about flags found */);
+
+ if (fileId == 0)
+ {
+ // error already reported
+ return;
+ }
+
+ if(opts['i'])
+ {
+ // Specified as ID. Must have a local name in the arguments
+ // (check at beginning of function ensures this)
+ localName = args[1];
+ }
+ else
+ {
+ // Specified by name. Local name already set by FindFileID,
+ // but may be overridden by user supplying a second argument.
+ if(args.size() == 2)
+ {
+ localName = args[1];
+ }
+ }
+
+ // Does local file already exist? (don't want to overwrite)
+ EMU_STRUCT_STAT st;
+ if(EMU_STAT(localName.c_str(), &st) == 0 || errno != ENOENT)
+ {
+ BOX_ERROR("The local file " << localName << " already exists, "
+ "will not overwrite it.");
+ SetReturnCode(ReturnCode::Command_Error);
+ return;
+ }
+
+ // Request it from the store
+ try
+ {
+ // Request object
+ mrConnection.QueryGetFile(dirId, fileId);
+
+ // Stream containing encoded file
+ std::auto_ptr<IOStream> objectStream(mrConnection.ReceiveStream());
+
+ // Decode it
+ BackupStoreFile::DecodeFile(*objectStream, localName.c_str(), mrConnection.GetTimeout());
+
+ // Done.
+ BOX_INFO("Object ID " << BOX_FORMAT_OBJECTID(fileId) <<
+ " fetched successfully.");
+ }
+ catch (BoxException &e)
+ {
+ BOX_ERROR("Failed to fetch file: " <<
+ e.what());
+ ::unlink(localName.c_str());
+ }
+ catch(std::exception &e)
+ {
+ BOX_ERROR("Failed to fetch file: " <<
+ e.what());
+ ::unlink(localName.c_str());
+ }
+ catch(...)
+ {
+ BOX_ERROR("Failed to fetch file: unknown error");
+ ::unlink(localName.c_str());
+ }
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::CompareParams::CompareParams()
+// Purpose: Constructor
+// Created: 29/1/04
+//
+// --------------------------------------------------------------------------
+BackupQueries::CompareParams::CompareParams(bool QuickCompare,
+ bool IgnoreExcludes, bool IgnoreAttributes,
+ box_time_t LatestFileUploadTime)
+: BoxBackupCompareParams(QuickCompare, IgnoreExcludes, IgnoreAttributes,
+ LatestFileUploadTime),
+ mDifferences(0),
+ mDifferencesExplainedByModTime(0),
+ mUncheckedFiles(0),
+ mExcludedDirs(0),
+ mExcludedFiles(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)
+{
+ box_time_t LatestFileUploadTime = GetCurrentBoxTime();
+
+ // 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
+ EMU_STRUCT_STAT st;
+ if(EMU_STAT(syncTimeFilename.c_str(), &st) == 0)
+ {
+ // Files modified after this time shouldn't be on the server, so report errors slightly differently
+ LatestFileUploadTime = FileModificationTime(st) -
+ SecondsToBoxTime(mrConfiguration.GetKeyValueInt("MinimumFileAge"));
+ }
+ else
+ {
+ BOX_WARNING("Failed to determine the time of the last "
+ "synchronisation -- checks not performed.");
+ }
+ }
+
+ // Parameters, including count of differences
+ BackupQueries::CompareParams params(opts['q'], // quick compare?
+ opts['E'], // ignore excludes
+ opts['A'], // ignore attributes
+ LatestFileUploadTime);
+
+ params.mQuietCompare = opts['Q'];
+
+ // Quick compare?
+ if(params.QuickCompare())
+ {
+ BOX_WARNING("Quick compare used -- file attributes are not "
+ "checked.");
+ }
+
+ if(!opts['l'] && opts['a'] && args.size() == 0)
+ {
+ // Compare all locations
+ const Configuration &rLocations(
+ mrConfiguration.GetSubConfiguration("BackupLocations"));
+ std::vector<std::string> locNames =
+ rLocations.GetSubConfigurationNames();
+ for(std::vector<std::string>::iterator
+ pLocName = locNames.begin();
+ pLocName != locNames.end();
+ pLocName++)
+ {
+ CompareLocation(*pLocName, 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.IgnoreExcludes())
+ {
+ BOX_ERROR("Cannot use excludes on directory to directory comparison -- use -E flag to specify ignored excludes.");
+ return;
+ }
+ else
+ {
+ // Do compare
+ Compare(args[0], args[1], params);
+ }
+ }
+ else
+ {
+ BOX_ERROR("Incorrect usage.\ncompare -a\n or compare -l <location-name>\n or compare <store-dir-name> <local-dir-name>");
+ return;
+ }
+
+ if (!params.mQuietCompare)
+ {
+ BOX_INFO("[ " <<
+ params.mDifferencesExplainedByModTime << " (of " <<
+ params.mDifferences << ") differences probably "
+ "due to file modifications after the last upload ]");
+ }
+
+ BOX_INFO("Differences: " << params.mDifferences << " (" <<
+ params.mExcludedDirs << " dirs excluded, " <<
+ params.mExcludedFiles << " files excluded, " <<
+ params.mUncheckedFiles << " files not checked)");
+
+ // Set return code?
+ if(opts['c'])
+ {
+ if (params.mUncheckedFiles != 0)
+ {
+ SetReturnCode(ReturnCode::Compare_Error);
+ }
+ else if (params.mDifferences != 0)
+ {
+ SetReturnCode(ReturnCode::Compare_Different);
+ }
+ else
+ {
+ SetReturnCode(ReturnCode::Compare_Same);
+ }
+ }
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::CompareLocation(const std::string &, BackupQueries::CompareParams &)
+// Purpose: Compare a location
+// Created: 2003/10/13
+//
+// --------------------------------------------------------------------------
+void BackupQueries::CompareLocation(const std::string &rLocation,
+ BoxBackupCompareParams &rParams)
+{
+ // Find the location's sub configuration
+ const Configuration &locations(mrConfiguration.GetSubConfiguration("BackupLocations"));
+ if(!locations.SubConfigurationExists(rLocation.c_str()))
+ {
+ BOX_ERROR("Location " << rLocation << " does not exist.");
+ return;
+ }
+ const Configuration &loc(locations.GetSubConfiguration(rLocation.c_str()));
+
+ #ifdef WIN32
+ {
+ std::string path = loc.GetKeyValue("Path");
+ if (path.size() > 0 && path[path.size()-1] ==
+ DIRECTORY_SEPARATOR_ASCHAR)
+ {
+ BOX_WARNING("Location '" << rLocation << "' path ends "
+ "with '" DIRECTORY_SEPARATOR "', "
+ "compare may fail!");
+ }
+ }
+ #endif
+
+ // Generate the exclude lists
+ if(!rParams.IgnoreExcludes())
+ {
+ rParams.LoadExcludeLists(loc);
+ }
+
+ // Then get it compared
+ Compare(std::string("/") + rLocation, loc.GetKeyValue("Path"), rParams);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// 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, BoxBackupCompareParams &rParams)
+{
+#ifdef WIN32
+ std::string localDirEncoded;
+ std::string storeDirEncoded;
+ if(!ConvertConsoleToUtf8(rLocalDir.c_str(), localDirEncoded)) return;
+ if(!ConvertConsoleToUtf8(rStoreDir.c_str(), storeDirEncoded)) return;
+#else
+ const std::string& localDirEncoded(rLocalDir);
+ 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)
+ {
+ bool modifiedAfterLastSync = false;
+
+ EMU_STRUCT_STAT st;
+ if(EMU_STAT(rLocalDir.c_str(), &st) == 0)
+ {
+ if(FileAttrModificationTime(st) >
+ rParams.LatestFileUploadTime())
+ {
+ modifiedAfterLastSync = true;
+ }
+ }
+
+ rParams.NotifyRemoteFileMissing(localDirEncoded,
+ storeDirEncoded, modifiedAfterLastSync);
+ return;
+ }
+
+ // Go!
+ Compare(dirID, storeDirEncoded, localDirEncoded, rParams);
+}
+
+void BackupQueries::CompareOneFile(int64_t DirID,
+ BackupStoreDirectory::Entry *pEntry,
+ const std::string& rLocalPath,
+ const std::string& rStorePath,
+ BoxBackupCompareParams &rParams)
+{
+ int64_t fileId = pEntry->GetObjectID();
+ int64_t fileSize = 0;
+
+ EMU_STRUCT_STAT st;
+ if(EMU_STAT(rLocalPath.c_str(), &st) == 0)
+ {
+ fileSize = st.st_size;
+ }
+
+ try
+ {
+ // Files the same flag?
+ bool equal = true;
+
+ // File modified after last sync flag
+ bool modifiedAfterLastSync = false;
+
+ bool hasDifferentAttribs = false;
+
+ bool alreadyReported = false;
+
+ if(rParams.QuickCompare())
+ {
+ // Compare file -- fetch it
+ mrConnection.QueryGetBlockIndexByID(fileId);
+
+ // Stream containing block index
+ std::auto_ptr<IOStream> blockIndexStream(mrConnection.ReceiveStream());
+
+ // Compare
+ equal = BackupStoreFile::CompareFileContentsAgainstBlockIndex(
+ rLocalPath.c_str(), *blockIndexStream,
+ mrConnection.GetTimeout());
+ }
+ else
+ {
+ // Compare file -- fetch it
+ mrConnection.QueryGetFile(DirID, pEntry->GetObjectID());
+
+ // Stream containing encoded file
+ std::auto_ptr<IOStream> objectStream(mrConnection.ReceiveStream());
+
+ // Decode it
+ std::auto_ptr<BackupStoreFile::DecodedStream> fileOnServerStream;
+
+ // Got additional attributes?
+ if(pEntry->HasAttributes())
+ {
+ // Use these attributes
+ const StreamableMemBlock &storeAttr(pEntry->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(rLocalPath.c_str(), false /* don't zero mod times */, &fileModTime);
+ modifiedAfterLastSync = (fileModTime > rParams.LatestFileUploadTime());
+ bool ignoreAttrModTime = true;
+
+ #ifdef WIN32
+ // attr mod time is really
+ // creation time, so check it
+ ignoreAttrModTime = false;
+ #endif
+
+ if(!rParams.IgnoreAttributes() &&
+ #ifdef PLATFORM_DISABLE_SYMLINK_ATTRIB_COMPARE
+ !fileOnServerStream->IsSymLink() &&
+ #endif
+ !localAttr.Compare(fileOnServerStream->GetAttributes(),
+ ignoreAttrModTime,
+ fileOnServerStream->IsSymLink() /* ignore modification time if it's a symlink */))
+ {
+ hasDifferentAttribs = true;
+ }
+
+ // Compare contents, if it's a regular file not a link
+ // Remember, we MUST read the entire stream from the server.
+ SelfFlushingStream flushObject(*objectStream);
+
+ if(!fileOnServerStream->IsSymLink())
+ {
+ SelfFlushingStream flushFile(*fileOnServerStream);
+ // Open the local file
+ std::auto_ptr<FileStream> apLocalFile;
+
+ try
+ {
+ apLocalFile.reset(new FileStream(rLocalPath.c_str()));
+ }
+ catch(std::exception &e)
+ {
+ rParams.NotifyLocalFileReadFailed(rLocalPath,
+ rStorePath, fileSize, e);
+ alreadyReported = true;
+ }
+ catch(...)
+ {
+ rParams.NotifyLocalFileReadFailed(rLocalPath,
+ rStorePath, fileSize);
+ alreadyReported = true;
+ }
+
+ if(apLocalFile.get())
+ {
+ equal = apLocalFile->CompareWith(*fileOnServerStream,
+ mrConnection.GetTimeout());
+ }
+ }
+ }
+
+ rParams.NotifyFileCompared(rLocalPath, rStorePath, fileSize,
+ hasDifferentAttribs, !equal, modifiedAfterLastSync,
+ pEntry->HasAttributes());
+ }
+ catch(BoxException &e)
+ {
+ rParams.NotifyDownloadFailed(rLocalPath, rStorePath, fileSize,
+ e);
+ }
+ catch(std::exception &e)
+ {
+ rParams.NotifyDownloadFailed(rLocalPath, rStorePath, fileSize,
+ e);
+ }
+ catch(...)
+ {
+ rParams.NotifyDownloadFailed(rLocalPath, rStorePath, fileSize);
+ }
+}
+
+// --------------------------------------------------------------------------
+//
+// 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, BoxBackupCompareParams &rParams)
+{
+ rParams.NotifyDirComparing(rLocalDir, rStoreDir);
+
+ // Get info on the local directory
+ EMU_STRUCT_STAT st;
+ if(EMU_LSTAT(rLocalDir.c_str(), &st) != 0)
+ {
+ // What kind of error?
+ if(errno == ENOTDIR || errno == ENOENT)
+ {
+ rParams.NotifyLocalDirMissing(rLocalDir, rStoreDir);
+ }
+ else
+ {
+ rParams.NotifyLocalDirAccessFailed(rLocalDir, rStoreDir);
+ }
+ return;
+ }
+
+ // Get the directory listing from the store
+ mrConnection.QueryListDirectory(
+ DirID,
+ BackupProtocolListDirectory::Flags_INCLUDE_EVERYTHING,
+ // get everything
+ BackupProtocolListDirectory::Flags_OldVersion |
+ BackupProtocolListDirectory::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())
+ {
+ rParams.NotifyStoreDirMissingAttributes(rLocalDir, rStoreDir);
+ }
+ 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 */))
+ {
+ rParams.NotifyDirCompared(rLocalDir, rStoreDir,
+ false, false /* actually we didn't check :) */);
+ }
+ else
+ {
+ bool modifiedAfterLastSync = false;
+
+ EMU_STRUCT_STAT st;
+ if(EMU_STAT(rLocalDir.c_str(), &st) == 0)
+ {
+ if(FileAttrModificationTime(st) >
+ rParams.LatestFileUploadTime())
+ {
+ modifiedAfterLastSync = true;
+ }
+ }
+
+ rParams.NotifyDirCompared(rLocalDir, rStoreDir,
+ true, modifiedAfterLastSync);
+ }
+ }
+
+ // Open the local directory
+ DIR *dirhandle = ::opendir(rLocalDir.c_str());
+ if(dirhandle == 0)
+ {
+ rParams.NotifyLocalDirAccessFailed(rLocalDir, rStoreDir);
+ 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 ..
+
+#ifdef HAVE_VALID_DIRENT_D_TYPE
+ if (localDirEn->d_type != DT_DIR)
+ {
+ BOX_ERROR("d_type does not really "
+ "work on your platform. "
+ "Reconfigure Box!");
+ return;
+ }
+#endif
+
+ continue;
+ }
+
+ std::string localDirPath(MakeFullPath(rLocalDir,
+ localDirEn->d_name));
+ std::string storeDirPath(rStoreDir + "/" +
+ localDirEn->d_name);
+
+#ifndef HAVE_VALID_DIRENT_D_TYPE
+ EMU_STRUCT_STAT st;
+ if(EMU_LSTAT(localDirPath.c_str(), &st) != 0)
+ {
+ // Check whether dir is excluded before trying
+ // to stat it, to fix problems with .gvfs
+ // directories that are not readable by root
+ // causing compare to crash:
+ // http://lists.boxbackup.org/pipermail/boxbackup/2010-January/000013.html
+ if(rParams.IsExcludedDir(localDirPath))
+ {
+ rParams.NotifyExcludedDir(localDirPath,
+ storeDirPath);
+ continue;
+ }
+ else
+ {
+ THROW_EXCEPTION_MESSAGE(CommonException,
+ OSFileError, localDirPath);
+ }
+ }
+
+ // 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)
+ {
+ BOX_LOG_SYS_ERROR("Failed to close local directory "
+ "'" << rLocalDir << "'");
+ }
+ 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)
+ {
+ const std::string& fileName(i->first);
+
+ std::string localPath(MakeFullPath(rLocalDir, fileName));
+ std::string storePath(rStoreDir + "/" + fileName);
+
+ rParams.NotifyFileComparing(localPath, storePath);
+
+ // Does the file exist locally?
+ string_set_iter_t local(localFiles.find(fileName));
+ if(local == localFiles.end())
+ {
+ // Not found -- report
+ rParams.NotifyLocalFileMissing(localPath,
+ storePath);
+ }
+ else
+ {
+ CompareOneFile(DirID, i->second, localPath,
+ storePath, rParams);
+
+ // Remove from set so that we know it's been compared
+ localFiles.erase(local);
+ }
+ }
+
+ // Report any files which exist locally, but not on the store
+ for(string_set_iter_t i = localFiles.begin(); i != localFiles.end(); ++i)
+ {
+ std::string localPath(MakeFullPath(rLocalDir, *i));
+ std::string storePath(rStoreDir + "/" + *i);
+
+ // Should this be ignored (ie is excluded)?
+ if(!rParams.IsExcludedFile(localPath))
+ {
+ bool modifiedAfterLastSync = false;
+
+ EMU_STRUCT_STAT st;
+ if(EMU_STAT(localPath.c_str(), &st) == 0)
+ {
+ if(FileModificationTime(st) >
+ rParams.LatestFileUploadTime())
+ {
+ modifiedAfterLastSync = true;
+ }
+ }
+
+ rParams.NotifyRemoteFileMissing(localPath,
+ storePath, modifiedAfterLastSync);
+ }
+ else
+ {
+ rParams.NotifyExcludedFile(localPath,
+ storePath);
+ }
+ }
+
+ // Finished with the files, clear the sets to reduce memory usage slightly
+ localFiles.clear();
+ storeFiles.clear();
+
+ // Now do the directories, recursively to check subdirectories
+ for(std::set<std::pair<std::string, BackupStoreDirectory::Entry *> >::const_iterator i = storeDirs.begin(); i != storeDirs.end(); ++i)
+ {
+ std::string localPath(MakeFullPath(rLocalDir, i->first));
+ std::string storePath(rStoreDir + "/" + i->first);
+
+ // Does the directory exist locally?
+ string_set_iter_t local(localDirs.find(i->first));
+ if(local == localDirs.end() &&
+ rParams.IsExcludedDir(localPath))
+ {
+ rParams.NotifyExcludedFileNotDeleted(localPath,
+ storePath);
+ }
+ else if(local == localDirs.end())
+ {
+ // Not found -- report
+ rParams.NotifyLocalFileMissing(localPath,
+ storePath);
+ }
+ else if(rParams.IsExcludedDir(localPath))
+ {
+ // don't recurse into excluded directories
+ }
+ else
+ {
+ // Compare directory
+ Compare(i->second->GetObjectID(),
+ storePath, localPath, rParams);
+
+ // Remove from set so that we know it's been compared
+ localDirs.erase(local);
+ }
+ }
+
+ // Report any directories which exist locally, but not on the store
+ for(std::set<std::string>::const_iterator
+ i = localDirs.begin();
+ i != localDirs.end(); ++i)
+ {
+ std::string localPath(MakeFullPath(rLocalDir, *i));
+ std::string storePath(rStoreDir + "/" + *i);
+
+ // Should this be ignored (ie is excluded)?
+ if(!rParams.IsExcludedDir(localPath))
+ {
+ bool modifiedAfterLastSync = false;
+
+ // Check the dir modification time
+ EMU_STRUCT_STAT st;
+ if(EMU_STAT(localPath.c_str(), &st) == 0 &&
+ FileModificationTime(st) >
+ rParams.LatestFileUploadTime())
+ {
+ modifiedAfterLastSync = true;
+ }
+
+ rParams.NotifyRemoteFileMissing(localPath,
+ storePath, modifiedAfterLastSync);
+ }
+ else
+ {
+ rParams.NotifyExcludedDir(localPath, storePath);
+ }
+ }
+ }
+ catch(...)
+ {
+ if(dirhandle != 0)
+ {
+ ::closedir(dirhandle);
+ }
+ throw;
+ }
+}
+
+
+// --------------------------------------------------------------------------
+//
+// 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() < 1 || args.size() > 2)
+ {
+ BOX_ERROR("Incorrect usage. restore [-drif] <remote-name> "
+ "[<local-name>]");
+ return;
+ }
+
+ // Restoring deleted things?
+ bool restoreDeleted = opts['d'];
+
+ std::string storeDirEncoded;
+
+ // Get directory ID
+ int64_t dirID = 0;
+ if(opts['i'])
+ {
+ // Specified as ID.
+ dirID = ::strtoll(args[0].c_str(), 0, 16);
+ if(dirID == std::numeric_limits<long long>::min() || dirID == std::numeric_limits<long long>::max() || dirID == 0)
+ {
+ BOX_ERROR("Not a valid object ID (specified in hex): "
+ << args[0]);
+ return;
+ }
+ std::ostringstream oss;
+ oss << BOX_FORMAT_OBJECTID(args[0]);
+ storeDirEncoded = oss.str();
+ }
+ else
+ {
+#ifdef WIN32
+ if(!ConvertConsoleToUtf8(args[0].c_str(), storeDirEncoded))
+ return;
+#else
+ storeDirEncoded = args[0];
+#endif
+
+ // Look up directory ID
+ dirID = FindDirectoryObjectID(storeDirEncoded,
+ false /* no old versions */,
+ restoreDeleted /* find deleted dirs */);
+ }
+
+ // Allowable?
+ if(dirID == 0)
+ {
+ BOX_ERROR("Directory '" << args[0] << "' not found on server");
+ return;
+ }
+
+ if(dirID == BackupProtocolListDirectory::RootDirectory)
+ {
+ BOX_ERROR("Cannot restore the root directory -- restore locations individually.");
+ return;
+ }
+
+ std::string localName;
+
+ if(args.size() == 2)
+ {
+ #ifdef WIN32
+ if(!ConvertConsoleToUtf8(args[1].c_str(), localName))
+ {
+ return;
+ }
+ #else
+ localName = args[1];
+ #endif
+ }
+ else
+ {
+ localName = args[0];
+ }
+
+ // Go and restore...
+ int result;
+
+ try
+ {
+ // At TRACE level, we print a line for each file and
+ // directory, so we don't need dots.
+
+ result = BackupClientRestore(mrConnection, dirID,
+ storeDirEncoded.c_str(), localName.c_str(),
+ true /* print progress dots */, restoreDeleted,
+ false /* don't undelete after restore! */,
+ opts['r'] /* resume? */,
+ opts['f'] /* force continue after errors */);
+ }
+ catch(std::exception &e)
+ {
+ BOX_ERROR("Failed to restore: " << e.what());
+ SetReturnCode(ReturnCode::Command_Error);
+ return;
+ }
+ catch(...)
+ {
+ BOX_ERROR("Failed to restore: unknown exception");
+ SetReturnCode(ReturnCode::Command_Error);
+ return;
+ }
+
+ switch(result)
+ {
+ case Restore_Complete:
+ BOX_INFO("Restore complete.");
+ break;
+
+ case Restore_CompleteWithErrors:
+ BOX_WARNING("Restore complete, but some files could not be "
+ "restored.");
+ break;
+
+ case Restore_ResumePossible:
+ BOX_ERROR("Resume possible -- repeat command with -r flag "
+ "to resume.");
+ SetReturnCode(ReturnCode::Command_Error);
+ break;
+
+ case Restore_TargetExists:
+ BOX_ERROR("The target directory exists. You cannot restore "
+ "over an existing directory.");
+ SetReturnCode(ReturnCode::Command_Error);
+ break;
+
+ case Restore_TargetPathNotFound:
+ BOX_ERROR("The target directory path does not exist.\n"
+ "To restore to a directory whose parent "
+ "does not exist, create the parent first.");
+ SetReturnCode(ReturnCode::Command_Error);
+ break;
+
+ case Restore_UnknownError:
+ BOX_ERROR("Unknown error during restore.");
+ SetReturnCode(ReturnCode::Command_Error);
+ break;
+
+ default:
+ BOX_ERROR("Unknown restore result " << result << ".");
+ SetReturnCode(ReturnCode::Command_Error);
+ break;
+ }
+}
+
+
+
+// These are autogenerated by a script.
+extern const char *help_commands[];
+extern const 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(const bool *opts)
+{
+ bool MachineReadable = opts['m'];
+
+ // Request full details from the server
+ std::auto_ptr<BackupProtocolAccountUsage> usage(mrConnection.QueryGetAccountUsage());
+
+ // Display each entry in turn
+ int64_t hardLimit = usage->GetBlocksHardLimit();
+ int32_t blockSize = usage->GetBlockSize();
+ CommandUsageDisplayEntry("Used", usage->GetBlocksUsed(), hardLimit,
+ blockSize, MachineReadable);
+ CommandUsageDisplayEntry("Old files", usage->GetBlocksInOldFiles(),
+ hardLimit, blockSize, MachineReadable);
+ CommandUsageDisplayEntry("Deleted files", usage->GetBlocksInDeletedFiles(),
+ hardLimit, blockSize, MachineReadable);
+ CommandUsageDisplayEntry("Directories", usage->GetBlocksInDirectories(),
+ hardLimit, blockSize, MachineReadable);
+ CommandUsageDisplayEntry("Soft limit", usage->GetBlocksSoftLimit(),
+ hardLimit, blockSize, MachineReadable);
+ CommandUsageDisplayEntry("Hard limit", hardLimit, hardLimit, blockSize,
+ MachineReadable);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::CommandUsageDisplayEntry(const char *,
+// int64_t, int64_t, int32_t, bool)
+// 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, bool MachineReadable)
+{
+ std::cout << FormatUsageLineStart(Name, MachineReadable) <<
+ FormatUsageBar(Size, Size * BlockSize, HardLimit * BlockSize,
+ MachineReadable) << std::endl;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// 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)
+{
+ if (!mReadWrite)
+ {
+ BOX_ERROR("This command requires a read-write connection. "
+ "Please reconnect with the -w option.");
+ return;
+ }
+
+ // Check arguments
+ if(args.size() != 1)
+ {
+ BOX_ERROR("Incorrect usage. undelete <name> or undelete -i <object-id>");
+ return;
+ }
+
+#ifdef WIN32
+ std::string storeDirEncoded;
+ if(!ConvertConsoleToUtf8(args[0].c_str(), storeDirEncoded)) return;
+#else
+ const std::string& storeDirEncoded(args[0]);
+#endif
+
+ // Find object ID somehow
+ int64_t fileId, parentId;
+ std::string fileName;
+ int16_t flagsOut;
+
+ fileId = FindFileID(storeDirEncoded, opts, &parentId, &fileName,
+ /* include files and directories */
+ BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING,
+ /* include old and deleted files */
+ BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING,
+ &flagsOut);
+
+ if (fileId == 0)
+ {
+ // error already reported
+ return;
+ }
+
+ // Undelete it on the store
+ try
+ {
+ // Undelete object
+ if(flagsOut & BackupProtocolListDirectory::Flags_File)
+ {
+ mrConnection.QueryUndeleteFile(parentId, fileId);
+ }
+ else
+ {
+ mrConnection.QueryUndeleteDirectory(fileId);
+ }
+ }
+ catch (BoxException &e)
+ {
+ BOX_ERROR("Failed to undelete object: " <<
+ e.what());
+ }
+ catch(std::exception &e)
+ {
+ BOX_ERROR("Failed to undelete object: " <<
+ e.what());
+ }
+ catch(...)
+ {
+ BOX_ERROR("Failed to undelete object: unknown error");
+ }
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::CommandDelete(const
+// std::vector<std::string> &, const bool *)
+// Purpose: Deletes a file
+// Created: 23/11/03
+//
+// --------------------------------------------------------------------------
+void BackupQueries::CommandDelete(const std::vector<std::string> &args,
+ const bool *opts)
+{
+ if (!mReadWrite)
+ {
+ BOX_ERROR("This command requires a read-write connection. "
+ "Please reconnect with the -w option.");
+ return;
+ }
+
+ // Check arguments
+ if(args.size() != 1)
+ {
+ BOX_ERROR("Incorrect usage. delete <name>");
+ return;
+ }
+
+#ifdef WIN32
+ std::string storeDirEncoded;
+ if(!ConvertConsoleToUtf8(args[0].c_str(), storeDirEncoded)) return;
+#else
+ const std::string& storeDirEncoded(args[0]);
+#endif
+
+ // Find object ID somehow
+ int64_t fileId, parentId;
+ std::string fileName;
+ int16_t flagsOut;
+
+ fileId = FindFileID(storeDirEncoded, opts, &parentId, &fileName,
+ /* include files and directories */
+ BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING,
+ /* exclude old and deleted files */
+ BackupProtocolListDirectory::Flags_OldVersion |
+ BackupProtocolListDirectory::Flags_Deleted,
+ &flagsOut);
+
+ if (fileId == 0)
+ {
+ // error already reported
+ return;
+ }
+
+ BackupStoreFilenameClear fn(fileName);
+
+ // Delete it on the store
+ try
+ {
+ // Delete object
+ if(flagsOut & BackupProtocolListDirectory::Flags_File)
+ {
+ mrConnection.QueryDeleteFile(parentId, fn);
+ }
+ else
+ {
+ mrConnection.QueryDeleteDirectory(fileId);
+ }
+ }
+ catch (BoxException &e)
+ {
+ BOX_ERROR("Failed to delete object: " <<
+ e.what());
+ }
+ catch(std::exception &e)
+ {
+ BOX_ERROR("Failed to delete object: " <<
+ e.what());
+ }
+ catch(...)
+ {
+ BOX_ERROR("Failed to delete object: unknown error");
+ }
+}
diff --git a/lib/bbackupquery/BackupQueries.h b/lib/bbackupquery/BackupQueries.h
new file mode 100644
index 00000000..96df34f5
--- /dev/null
+++ b/lib/bbackupquery/BackupQueries.h
@@ -0,0 +1,440 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: BackupQueries.h
+// Purpose: Perform various queries on the backup store server.
+// Created: 2003/10/10
+//
+// --------------------------------------------------------------------------
+
+#ifndef BACKUPQUERIES__H
+#define BACKUPQUERIES__H
+
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include "BoxTime.h"
+#include "BoxBackupCompareParams.h"
+#include "BackupStoreDirectory.h"
+
+class BackupProtocolCallable;
+class Configuration;
+class ExcludeList;
+
+typedef enum
+{
+ Command_Unknown = 0,
+ Command_Quit,
+ Command_List,
+ Command_pwd,
+ Command_cd,
+ Command_lcd,
+ Command_sh,
+ Command_GetObject,
+ Command_Get,
+ Command_Compare,
+ Command_Restore,
+ Command_Help,
+ Command_Usage,
+ Command_Undelete,
+ Command_Delete,
+}
+CommandType;
+
+struct QueryCommandSpecification;
+
+// --------------------------------------------------------------------------
+//
+// Class
+// Name: BackupQueries
+// Purpose: Perform various queries on the backup store server.
+// Created: 2003/10/10
+//
+// --------------------------------------------------------------------------
+class BackupQueries
+{
+public:
+ BackupQueries(BackupProtocolCallable &rConnection,
+ const Configuration &rConfiguration,
+ bool readWrite);
+ ~BackupQueries();
+private:
+ BackupQueries(const BackupQueries &);
+public:
+ struct ParsedCommand
+ {
+ std::vector<std::string> mCmdElements;
+ std::string mOptions;
+ std::string mCompleteCommand;
+ bool mInOptions, mFailed;
+ QueryCommandSpecification* pSpec;
+ // mArgCount is the same as mCmdElements.size() for a complete
+ // command, but if the command line ends in a space,
+ // e.g. during readline parsing, it can be one greater,
+ // to indicate that we should complete the next item instead.
+ size_t mCompleteArgCount;
+ ParsedCommand(const std::string& Command,
+ bool isFromCommandLine);
+ bool IsEmpty() { return mCmdElements.empty(); }
+ bool IsFailed() { return mFailed; }
+ };
+
+ void DoCommand(ParsedCommand& rCommand);
+
+ // Ready to stop?
+ bool Stop() {return mQuitNow;}
+
+ // Return code?
+ int GetReturnCode() {return mReturnCode;}
+
+ void List(int64_t DirID, const std::string &rListRoot, const bool *opts,
+ bool FirstLevel, std::ostream* pOut = NULL);
+ void CommandList(const std::vector<std::string> &args, const bool *opts);
+
+ // Commands
+ void CommandChangeDir(const std::vector<std::string> &args, const bool *opts);
+ void CommandChangeLocalDir(const std::vector<std::string> &args);
+ void CommandGetObject(const std::vector<std::string> &args, const bool *opts);
+ void CommandGet(std::vector<std::string> args, const bool *opts);
+ void CommandCompare(const std::vector<std::string> &args, const bool *opts);
+ void CommandRestore(const std::vector<std::string> &args, const bool *opts);
+ void CommandUndelete(const std::vector<std::string> &args, const bool *opts);
+ void CommandDelete(const std::vector<std::string> &args,
+ const bool *opts);
+ void CommandUsage(const bool *opts);
+ void CommandUsageDisplayEntry(const char *Name, int64_t Size,
+ int64_t HardLimit, int32_t BlockSize, bool MachineReadable);
+ void CommandHelp(const std::vector<std::string> &args);
+
+ class CompareParams : public BoxBackupCompareParams
+ {
+ public:
+ CompareParams(bool QuickCompare, bool IgnoreExcludes,
+ bool IgnoreAttributes, box_time_t LatestFileUploadTime);
+
+ bool mQuietCompare;
+ int mDifferences;
+ int mDifferencesExplainedByModTime;
+ int mUncheckedFiles;
+ int mExcludedDirs;
+ int mExcludedFiles;
+
+ std::string ConvertForConsole(const std::string& rUtf8String)
+ {
+ #ifdef WIN32
+ std::string output;
+
+ if(!ConvertUtf8ToConsole(rUtf8String.c_str(), output))
+ {
+ BOX_WARNING("Character set conversion failed "
+ "on string: " << rUtf8String);
+ return rUtf8String;
+ }
+
+ return output;
+ #else
+ return rUtf8String;
+ #endif
+ }
+
+ virtual void NotifyLocalDirMissing(const std::string& rLocalPath,
+ const std::string& rRemotePath)
+ {
+ BOX_WARNING("Local directory '" <<
+ ConvertForConsole(rLocalPath) << "' "
+ "does not exist, but remote directory does.");
+ mDifferences ++;
+ }
+
+ virtual void NotifyLocalDirAccessFailed(
+ const std::string& rLocalPath,
+ const std::string& rRemotePath)
+ {
+ BOX_LOG_SYS_WARNING("Failed to access local directory "
+ "'" << ConvertForConsole(rLocalPath) << "'");
+ mUncheckedFiles ++;
+ }
+
+ virtual void NotifyStoreDirMissingAttributes(
+ const std::string& rLocalPath,
+ const std::string& rRemotePath)
+ {
+ BOX_WARNING("Store directory '" <<
+ ConvertForConsole(rRemotePath) << "' "
+ "doesn't have attributes.");
+ }
+
+ virtual void NotifyRemoteFileMissing(
+ const std::string& rLocalPath,
+ const std::string& rRemotePath,
+ bool modifiedAfterLastSync)
+ {
+ BOX_WARNING("Local file '" <<
+ ConvertForConsole(rLocalPath) << "' "
+ "exists, but remote file '" <<
+ ConvertForConsole(rRemotePath) << "' "
+ "does not.");
+ mDifferences ++;
+
+ if(modifiedAfterLastSync)
+ {
+ mDifferencesExplainedByModTime ++;
+ BOX_INFO("(the file above was modified after "
+ "the last sync time -- might be "
+ "reason for difference)");
+ }
+ }
+
+ virtual void NotifyLocalFileMissing(
+ const std::string& rLocalPath,
+ const std::string& rRemotePath)
+ {
+ BOX_WARNING("Remote file '" <<
+ ConvertForConsole(rRemotePath) << "' "
+ "exists, but local file '" <<
+ ConvertForConsole(rLocalPath) << "' does not.");
+ mDifferences ++;
+ }
+
+ virtual void NotifyExcludedFileNotDeleted(
+ const std::string& rLocalPath,
+ const std::string& rRemotePath)
+ {
+ BOX_WARNING("Local file '" <<
+ ConvertForConsole(rLocalPath) << "' "
+ "is excluded, but remote file '" <<
+ ConvertForConsole(rRemotePath) << "' "
+ "still exists.");
+ mDifferences ++;
+ }
+
+ virtual void NotifyDownloadFailed(const std::string& rLocalPath,
+ const std::string& rRemotePath, int64_t NumBytes,
+ BoxException& rException)
+ {
+ BOX_ERROR("Failed to download remote file '" <<
+ ConvertForConsole(rRemotePath) << "': " <<
+ rException.what() << " (" <<
+ rException.GetType() << "/" <<
+ rException.GetSubType() << ")");
+ mUncheckedFiles ++;
+ }
+
+ virtual void NotifyDownloadFailed(const std::string& rLocalPath,
+ const std::string& rRemotePath, int64_t NumBytes,
+ std::exception& rException)
+ {
+ BOX_ERROR("Failed to download remote file '" <<
+ ConvertForConsole(rRemotePath) << "': " <<
+ rException.what());
+ mUncheckedFiles ++;
+ }
+
+ virtual void NotifyDownloadFailed(const std::string& rLocalPath,
+ const std::string& rRemotePath, int64_t NumBytes)
+ {
+ BOX_ERROR("Failed to download remote file '" <<
+ ConvertForConsole(rRemotePath));
+ mUncheckedFiles ++;
+ }
+
+ virtual void NotifyLocalFileReadFailed(const std::string& rLocalPath,
+ const std::string& rRemotePath, int64_t NumBytes,
+ std::exception& rException)
+ {
+ BOX_ERROR("Failed to read local file '" <<
+ ConvertForConsole(rLocalPath) << "': " <<
+ rException.what());
+ mUncheckedFiles ++;
+ }
+
+ virtual void NotifyLocalFileReadFailed(const std::string& rLocalPath,
+ const std::string& rRemotePath, int64_t NumBytes)
+ {
+ BOX_ERROR("Failed to read local file '" <<
+ ConvertForConsole(rLocalPath));
+ mUncheckedFiles ++;
+ }
+
+ virtual void NotifyExcludedFile(const std::string& rLocalPath,
+ const std::string& rRemotePath)
+ {
+ mExcludedFiles ++;
+ }
+
+ virtual void NotifyExcludedDir(const std::string& rLocalPath,
+ const std::string& rRemotePath)
+ {
+ mExcludedDirs ++;
+ }
+
+ virtual void NotifyDirComparing(const std::string& rLocalPath,
+ const std::string& rRemotePath)
+ {
+ BOX_INFO("Comparing directory: " << rLocalPath);
+ }
+
+ virtual void NotifyDirCompared(
+ const std::string& rLocalPath,
+ const std::string& rRemotePath,
+ bool HasDifferentAttributes,
+ bool modifiedAfterLastSync)
+ {
+ if(HasDifferentAttributes)
+ {
+ BOX_WARNING("Local directory '" <<
+ ConvertForConsole(rLocalPath) << "' "
+ "has different attributes to "
+ "store directory '" <<
+ ConvertForConsole(rRemotePath) << "'.");
+ mDifferences ++;
+
+ if(modifiedAfterLastSync)
+ {
+ mDifferencesExplainedByModTime ++;
+ BOX_INFO("(the directory above was "
+ "modified after the last sync "
+ "time -- might be reason for "
+ "difference)");
+ }
+ }
+ }
+
+ virtual void NotifyFileComparing(const std::string& rLocalPath,
+ const std::string& rRemotePath)
+ {
+ BOX_TRACE("Comparing file: " << rLocalPath);
+ }
+
+ virtual void NotifyFileCompared(const std::string& rLocalPath,
+ const std::string& rRemotePath, int64_t NumBytes,
+ bool HasDifferentAttributes, bool HasDifferentContents,
+ bool ModifiedAfterLastSync, bool NewAttributesApplied)
+ {
+ int NewDifferences = 0;
+
+ if(HasDifferentAttributes)
+ {
+ BOX_WARNING("Local file '" <<
+ ConvertForConsole(rLocalPath) << "' "
+ "has different attributes to "
+ "store file '" <<
+ ConvertForConsole(rRemotePath) << "'.");
+ NewDifferences ++;
+ }
+
+ if(HasDifferentContents)
+ {
+ BOX_WARNING("Local file '" <<
+ ConvertForConsole(rLocalPath) << "' "
+ "has different contents to "
+ "store file '" <<
+ ConvertForConsole(rRemotePath) << "'.");
+ NewDifferences ++;
+ }
+
+ if(HasDifferentAttributes || HasDifferentContents)
+ {
+ if(ModifiedAfterLastSync)
+ {
+ mDifferencesExplainedByModTime +=
+ NewDifferences;
+ BOX_INFO("(the file above was modified "
+ "after the last sync time -- "
+ "might be reason for difference)");
+ }
+ else if(NewAttributesApplied)
+ {
+ BOX_INFO("(the file above has had new "
+ "attributes applied)\n");
+ }
+ }
+
+ mDifferences += NewDifferences;
+ }
+ };
+ void CompareLocation(const std::string &rLocation,
+ BoxBackupCompareParams &rParams);
+ void Compare(const std::string &rStoreDir,
+ const std::string &rLocalDir, BoxBackupCompareParams &rParams);
+ void Compare(int64_t DirID, const std::string &rStoreDir,
+ const std::string &rLocalDir, BoxBackupCompareParams &rParams);
+ void CompareOneFile(int64_t DirID, BackupStoreDirectory::Entry *pEntry,
+ const std::string& rLocalPath, const std::string& rStorePath,
+ BoxBackupCompareParams &rParams);
+
+public:
+
+ class ReturnCode
+ {
+ public:
+ typedef enum {
+ Command_OK = 0,
+ Compare_Same = 1,
+ Compare_Different,
+ Compare_Error,
+ Command_Error,
+ } Type;
+ };
+
+ // Were private, but needed by completion functions:
+ int64_t GetCurrentDirectoryID();
+ int64_t FindDirectoryObjectID(const std::string &rDirName,
+ bool AllowOldVersion = false, bool AllowDeletedDirs = false,
+ std::vector<std::pair<std::string, int64_t> > *pStack = 0);
+
+private:
+
+ // Utility functions
+ int64_t FindFileID(const std::string& rNameOrIdString,
+ const bool *opts, int64_t *pDirIdOut,
+ std::string* pFileNameOut, int16_t flagsInclude,
+ int16_t flagsExclude, int16_t* pFlagsOut);
+ std::string GetCurrentDirectoryName();
+ void SetReturnCode(int code) {mReturnCode = code;}
+
+private:
+ bool mReadWrite;
+ BackupProtocolCallable &mrConnection;
+ const Configuration &mrConfiguration;
+ bool mQuitNow;
+ std::vector<std::pair<std::string, int64_t> > mDirStack;
+ bool mRunningAsRoot;
+ bool mWarnedAboutOwnerAttributes;
+ int mReturnCode;
+};
+
+typedef std::vector<std::string> (*CompletionHandler)
+ (BackupQueries::ParsedCommand& rCommand, const std::string& prefix,
+ BackupProtocolCallable& rProtocol, const Configuration& rConfig,
+ BackupQueries& rQueries);
+
+std::vector<std::string> CompleteCommand(BackupQueries::ParsedCommand& rCommand,
+ const std::string& prefix, BackupProtocolCallable& rProtocol,
+ const Configuration& rConfig, BackupQueries& rQueries);
+std::vector<std::string> CompleteOptions(BackupQueries::ParsedCommand& rCommand,
+ const std::string& prefix, BackupProtocolCallable& rProtocol,
+ const Configuration& rConfig, BackupQueries& rQueries);
+
+#define MAX_COMPLETION_HANDLERS 4
+
+struct QueryCommandSpecification
+{
+ const char* name;
+ const char* opts;
+ CommandType type;
+ CompletionHandler complete[MAX_COMPLETION_HANDLERS];
+};
+
+// Data about commands
+extern QueryCommandSpecification commands[];
+
+extern const char *alias[];
+extern const int aliasIs[];
+
+#define LIST_OPTION_ALLOWOLD 'o'
+#define LIST_OPTION_ALLOWDELETED 'd'
+
+#endif // BACKUPQUERIES__H
+
diff --git a/lib/bbackupquery/BoxBackupCompareParams.h b/lib/bbackupquery/BoxBackupCompareParams.h
new file mode 100644
index 00000000..655df947
--- /dev/null
+++ b/lib/bbackupquery/BoxBackupCompareParams.h
@@ -0,0 +1,112 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: BoxBackupCompareParams.h
+// Purpose: Parameters and notifiers for a compare operation
+// Created: 2008/12/30
+//
+// --------------------------------------------------------------------------
+
+#ifndef BOXBACKUPCOMPAREPARAMS__H
+#define BOXBACKUPCOMPAREPARAMS__H
+
+#include <memory>
+#include <string>
+
+#include "BoxTime.h"
+#include "ExcludeList.h"
+#include "BackupClientMakeExcludeList.h"
+
+// --------------------------------------------------------------------------
+//
+// Class
+// Name: BoxBackupCompareParams
+// Purpose: Parameters and notifiers for a compare operation
+// Created: 2003/10/10
+//
+// --------------------------------------------------------------------------
+class BoxBackupCompareParams
+{
+private:
+ std::auto_ptr<const ExcludeList> mapExcludeFiles, mapExcludeDirs;
+ bool mQuickCompare;
+ bool mIgnoreExcludes;
+ bool mIgnoreAttributes;
+ box_time_t mLatestFileUploadTime;
+
+public:
+ BoxBackupCompareParams(bool QuickCompare, bool IgnoreExcludes,
+ bool IgnoreAttributes, box_time_t LatestFileUploadTime)
+ : mQuickCompare(QuickCompare),
+ mIgnoreExcludes(IgnoreExcludes),
+ mIgnoreAttributes(IgnoreAttributes),
+ mLatestFileUploadTime(LatestFileUploadTime)
+ { }
+
+ virtual ~BoxBackupCompareParams() { }
+
+ bool QuickCompare() { return mQuickCompare; }
+ bool IgnoreExcludes() { return mIgnoreExcludes; }
+ bool IgnoreAttributes() { return mIgnoreAttributes; }
+ box_time_t LatestFileUploadTime() { return mLatestFileUploadTime; }
+
+ void LoadExcludeLists(const Configuration& rLoc)
+ {
+ mapExcludeFiles.reset(BackupClientMakeExcludeList_Files(rLoc));
+ mapExcludeDirs.reset(BackupClientMakeExcludeList_Dirs(rLoc));
+ }
+ bool IsExcludedFile(const std::string& rLocalPath)
+ {
+ if (!mapExcludeFiles.get()) return false;
+ return mapExcludeFiles->IsExcluded(rLocalPath);
+ }
+ bool IsExcludedDir(const std::string& rLocalPath)
+ {
+ if (!mapExcludeDirs.get()) return false;
+ return mapExcludeDirs->IsExcluded(rLocalPath);
+ }
+
+ virtual void NotifyLocalDirMissing(const std::string& rLocalPath,
+ const std::string& rRemotePath) = 0;
+ virtual void NotifyLocalDirAccessFailed(const std::string& rLocalPath,
+ const std::string& rRemotePath) = 0;
+ virtual void NotifyStoreDirMissingAttributes(const std::string& rLocalPath,
+ const std::string& rRemotePath) = 0;
+ virtual void NotifyRemoteFileMissing(const std::string& rLocalPath,
+ const std::string& rRemotePath,
+ bool modifiedAfterLastSync) = 0;
+ virtual void NotifyLocalFileMissing(const std::string& rLocalPath,
+ const std::string& rRemotePath) = 0;
+ virtual void NotifyExcludedFileNotDeleted(const std::string& rLocalPath,
+ const std::string& rRemotePath) = 0;
+ virtual void NotifyDownloadFailed(const std::string& rLocalPath,
+ const std::string& rRemotePath, int64_t NumBytes,
+ BoxException& rException) = 0;
+ virtual void NotifyLocalFileReadFailed(const std::string& rLocalPath,
+ const std::string& rRemotePath, int64_t NumBytes,
+ std::exception& rException) = 0;
+ virtual void NotifyLocalFileReadFailed(const std::string& rLocalPath,
+ const std::string& rRemotePath, int64_t NumBytes) = 0;
+ virtual void NotifyDownloadFailed(const std::string& rLocalPath,
+ const std::string& rRemotePath, int64_t NumBytes,
+ std::exception& rException) = 0;
+ virtual void NotifyDownloadFailed(const std::string& rLocalPath,
+ const std::string& rRemotePath, int64_t NumBytes) = 0;
+ virtual void NotifyExcludedFile(const std::string& rLocalPath,
+ const std::string& rRemotePath) = 0;
+ virtual void NotifyExcludedDir(const std::string& rLocalPath,
+ const std::string& rRemotePath) = 0;
+ virtual void NotifyDirComparing(const std::string& rLocalPath,
+ const std::string& rRemotePath) = 0;
+ virtual void NotifyDirCompared(const std::string& rLocalPath,
+ const std::string& rRemotePath, bool HasDifferentAttributes,
+ bool modifiedAfterLastSync) = 0;
+ virtual void NotifyFileComparing(const std::string& rLocalPath,
+ const std::string& rRemotePath) = 0;
+ virtual void NotifyFileCompared(const std::string& rLocalPath,
+ const std::string& rRemotePath, int64_t NumBytes,
+ bool HasDifferentAttributes, bool HasDifferentContents,
+ bool modifiedAfterLastSync, bool newAttributesApplied) = 0;
+};
+
+#endif // BOXBACKUPCOMPAREPARAMS__H
diff --git a/lib/bbackupquery/CommandCompletion.cpp b/lib/bbackupquery/CommandCompletion.cpp
new file mode 100644
index 00000000..761fc97e
--- /dev/null
+++ b/lib/bbackupquery/CommandCompletion.cpp
@@ -0,0 +1,604 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: CommandCompletion.cpp
+// Purpose: Parts of BackupQueries that depend on readline
+// Created: 2011/01/21
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#ifdef HAVE_LIBREADLINE
+ #ifdef HAVE_READLINE_READLINE_H
+ #include <readline/readline.h>
+ #elif defined(HAVE_EDITLINE_READLINE_H)
+ #include <editline/readline.h>
+ #elif defined(HAVE_READLINE_H)
+ #include <readline.h>
+ #endif
+#endif
+
+#ifdef HAVE_READLINE_HISTORY
+ #ifdef HAVE_READLINE_HISTORY_H
+ #include <readline/history.h>
+ #elif defined(HAVE_HISTORY_H)
+ #include <history.h>
+ #endif
+#endif
+
+#include <cstring>
+
+#include "BackupQueries.h"
+#include "Configuration.h"
+
+#include "autogen_BackupProtocol.h"
+
+#include "MemLeakFindOn.h"
+
+#define COMPARE_RETURN_SAME 1
+#define COMPARE_RETURN_DIFFERENT 2
+#define COMPARE_RETURN_ERROR 3
+#define COMMAND_RETURN_ERROR 4
+
+#define COMPLETION_FUNCTION(name, code) \
+std::vector<std::string> Complete ## name( \
+ BackupQueries::ParsedCommand& rCommand, \
+ const std::string& prefix, \
+ BackupProtocolCallable& rProtocol, const Configuration& rConfig, \
+ BackupQueries& rQueries) \
+{ \
+ std::vector<std::string> completions; \
+ \
+ try \
+ { \
+ code \
+ } \
+ catch(std::exception &e) \
+ { \
+ BOX_TRACE("Failed to complete " << prefix << ": " << e.what()); \
+ } \
+ catch(...) \
+ { \
+ BOX_TRACE("Failed to complete " << prefix << ": " \
+ "unknown error"); \
+ } \
+ \
+ return completions; \
+}
+
+#define DELEGATE_COMPLETION(name) \
+ completions = Complete ## name(rCommand, prefix, rProtocol, rConfig, \
+ rQueries);
+
+COMPLETION_FUNCTION(None,)
+
+#ifdef HAVE_RL_FILENAME_COMPLETION_FUNCTION
+ #define RL_FILENAME_COMPLETION_FUNCTION rl_filename_completion_function
+ #define HAVE_A_FILENAME_COMPLETION_FUNCTION 1
+#elif defined HAVE_FILENAME_COMPLETION_FUNCTION
+ #define RL_FILENAME_COMPLETION_FUNCTION filename_completion_function
+ #define HAVE_A_FILENAME_COMPLETION_FUNCTION 1
+#endif
+
+#ifdef HAVE_A_FILENAME_COMPLETION_FUNCTION
+COMPLETION_FUNCTION(Default,
+ int i = 0;
+
+ while (const char *match = RL_FILENAME_COMPLETION_FUNCTION(prefix.c_str(), i))
+ {
+ completions.push_back(match);
+ ++i;
+ }
+)
+#else // !HAVE_A_FILENAME_COMPLETION_FUNCTION
+COMPLETION_FUNCTION(Default,)
+#endif // HAVE_A_FILENAME_COMPLETION_FUNCTION
+
+COMPLETION_FUNCTION(Command,
+ int len = prefix.length();
+
+ for(int i = 0; commands[i].name != NULL; i++)
+ {
+ if(::strncmp(commands[i].name, prefix.c_str(), len) == 0)
+ {
+ completions.push_back(commands[i].name);
+ }
+ }
+)
+
+void CompleteOptionsInternal(const std::string& prefix,
+ BackupQueries::ParsedCommand& rCommand,
+ std::vector<std::string>& completions)
+{
+ std::string availableOptions = rCommand.pSpec->opts;
+
+ for(std::string::iterator
+ opt = availableOptions.begin();
+ opt != availableOptions.end(); opt++)
+ {
+ if(rCommand.mOptions.find(*opt) == std::string::npos)
+ {
+ if(prefix == "")
+ {
+ // complete with possible option strings
+ completions.push_back(std::string("-") + *opt);
+ }
+ else
+ {
+ // complete with possible additional options
+ completions.push_back(prefix + *opt);
+ }
+ }
+ }
+}
+
+COMPLETION_FUNCTION(Options,
+ CompleteOptionsInternal(prefix, rCommand, completions);
+)
+
+std::string EncodeFileName(const std::string &rUnEncodedName)
+{
+#ifdef WIN32
+ std::string encodedName;
+ if(!ConvertConsoleToUtf8(rUnEncodedName, encodedName))
+ {
+ return std::string();
+ }
+ return encodedName;
+#else
+ return rUnEncodedName;
+#endif
+}
+
+int16_t GetExcludeFlags(BackupQueries::ParsedCommand& rCommand)
+{
+ int16_t excludeFlags = 0;
+
+ if (rCommand.mOptions.find(LIST_OPTION_ALLOWOLD) == std::string::npos)
+ {
+ excludeFlags |= BackupProtocolListDirectory::Flags_OldVersion;
+ }
+
+ if (rCommand.mOptions.find(LIST_OPTION_ALLOWDELETED) == std::string::npos)
+ {
+ excludeFlags |= BackupProtocolListDirectory::Flags_Deleted;
+ }
+
+ return excludeFlags;
+}
+
+std::vector<std::string> CompleteRemoteFileOrDirectory(
+ BackupQueries::ParsedCommand& rCommand,
+ const std::string& prefix, BackupProtocolCallable& rProtocol,
+ BackupQueries& rQueries, int16_t includeFlags)
+{
+ std::vector<std::string> completions;
+
+ // default to using the current directory
+ int64_t listDirId = rQueries.GetCurrentDirectoryID();
+ std::string searchPrefix;
+ std::string listDir = prefix;
+
+ if(rCommand.mCompleteArgCount == rCommand.mCmdElements.size())
+ {
+ // completing an empty name, from the current directory
+ // nothing to change
+ }
+ else
+ {
+ // completing a partially-completed subdirectory name
+ searchPrefix = prefix;
+ listDir = "";
+
+ // do we need to list a subdirectory to complete?
+ size_t lastSlash = searchPrefix.rfind('/');
+ if(lastSlash == std::string::npos)
+ {
+ // no slashes, so the whole name is the prefix
+ // nothing to change
+ }
+ else
+ {
+ // listing a partially-completed subdirectory name
+ listDir = searchPrefix.substr(0, lastSlash);
+
+ listDirId = rQueries.FindDirectoryObjectID(listDir,
+ rCommand.mOptions.find(LIST_OPTION_ALLOWOLD)
+ != std::string::npos,
+ rCommand.mOptions.find(LIST_OPTION_ALLOWDELETED)
+ != std::string::npos);
+
+ if(listDirId == 0)
+ {
+ // no matches for subdir to list,
+ // return empty-handed.
+ return completions;
+ }
+
+ // matched, and updated listDir and listDirId already
+ searchPrefix = searchPrefix.substr(lastSlash + 1);
+ }
+ }
+
+ // Always include directories, because they contain files.
+ // We will append a slash later for each directory if we're
+ // actually looking for files.
+ //
+ // If we're looking for directories, then only list directories.
+
+ bool completeFiles = includeFlags &
+ BackupProtocolListDirectory::Flags_File;
+ bool completeDirs = includeFlags &
+ BackupProtocolListDirectory::Flags_Dir;
+ int16_t listFlags = 0;
+
+ if(completeFiles)
+ {
+ listFlags = BackupProtocolListDirectory::Flags_INCLUDE_EVERYTHING;
+ }
+ else if(completeDirs)
+ {
+ listFlags = BackupProtocolListDirectory::Flags_Dir;
+ }
+
+ rProtocol.QueryListDirectory(listDirId,
+ listFlags, GetExcludeFlags(rCommand),
+ false /* no attributes */);
+
+ // Retrieve the directory from the stream following
+ BackupStoreDirectory dir;
+ std::auto_ptr<IOStream> dirstream(rProtocol.ReceiveStream());
+ dir.ReadFromStream(*dirstream, rProtocol.GetTimeout());
+
+ // Then... display everything
+ BackupStoreDirectory::Iterator i(dir);
+ BackupStoreDirectory::Entry *en = 0;
+ while((en = i.Next()) != 0)
+ {
+ BackupStoreFilenameClear clear(en->GetName());
+ std::string name = clear.GetClearFilename().c_str();
+ if(name.compare(0, searchPrefix.length(), searchPrefix) == 0)
+ {
+ bool dir_added = false;
+
+ if(en->IsDir() &&
+ (includeFlags & BackupProtocolListDirectory::Flags_Dir) == 0)
+ {
+ // Was looking for a file, but this is a
+ // directory, so append a slash to the name
+ name += "/";
+ }
+
+ #ifdef HAVE_LIBREADLINE
+ if(strchr(name.c_str(), ' '))
+ {
+ int n_quote = 0;
+
+ for(int k = strlen(rl_line_buffer); k >= 0; k--)
+ {
+ if (rl_line_buffer[k] == '\"') {
+ ++n_quote;
+ }
+ }
+
+ dir_added = false;
+
+ if (!(n_quote % 2))
+ {
+ name = "\"" + (listDir == "" ? name : listDir + "/" + name);
+ dir_added = true;
+ }
+
+ name = name + "\"";
+ }
+ #endif
+
+ if(listDir == "" || dir_added)
+ {
+ completions.push_back(name);
+ }
+ else
+ {
+ completions.push_back(listDir + "/" + name);
+ }
+ }
+ }
+
+ return completions;
+}
+
+COMPLETION_FUNCTION(RemoteDir,
+ completions = CompleteRemoteFileOrDirectory(rCommand, prefix,
+ rProtocol, rQueries,
+ BackupProtocolListDirectory::Flags_Dir);
+)
+
+COMPLETION_FUNCTION(RemoteFile,
+ completions = CompleteRemoteFileOrDirectory(rCommand, prefix,
+ rProtocol, rQueries,
+ BackupProtocolListDirectory::Flags_File);
+)
+
+COMPLETION_FUNCTION(LocalDir,
+ DELEGATE_COMPLETION(Default);
+)
+
+COMPLETION_FUNCTION(LocalFile,
+ DELEGATE_COMPLETION(Default);
+)
+
+COMPLETION_FUNCTION(LocationName,
+ const Configuration &locations(rConfig.GetSubConfiguration(
+ "BackupLocations"));
+
+ std::vector<std::string> locNames =
+ locations.GetSubConfigurationNames();
+
+ for(std::vector<std::string>::iterator
+ pLocName = locNames.begin();
+ pLocName != locNames.end();
+ pLocName++)
+ {
+ if(pLocName->compare(0, pLocName->length(), prefix) == 0)
+ {
+ completions.push_back(*pLocName);
+ }
+ }
+)
+
+COMPLETION_FUNCTION(RemoteFileIdInCurrentDir,
+ int64_t listDirId = rQueries.GetCurrentDirectoryID();
+ int16_t excludeFlags = GetExcludeFlags(rCommand);
+
+ rProtocol.QueryListDirectory(
+ listDirId,
+ BackupProtocolListDirectory::Flags_File,
+ excludeFlags, false /* no attributes */);
+
+ // Retrieve the directory from the stream following
+ BackupStoreDirectory dir;
+ std::auto_ptr<IOStream> dirstream(rProtocol.ReceiveStream());
+ dir.ReadFromStream(*dirstream, rProtocol.GetTimeout());
+
+ // Then... compare each item
+ BackupStoreDirectory::Iterator i(dir);
+ BackupStoreDirectory::Entry *en = 0;
+ while((en = i.Next()) != 0)
+ {
+ std::ostringstream hexId;
+ hexId << std::hex << en->GetObjectID();
+ if(hexId.str().compare(0, prefix.length(), prefix) == 0)
+ {
+ completions.push_back(hexId.str());
+ }
+ }
+)
+
+// TODO implement completion of hex IDs up to the maximum according to Usage
+COMPLETION_FUNCTION(RemoteId,)
+
+COMPLETION_FUNCTION(GetFileOrId,
+ if(rCommand.mOptions.find('i') != std::string::npos)
+ {
+ DELEGATE_COMPLETION(RemoteFileIdInCurrentDir);
+ }
+ else
+ {
+ DELEGATE_COMPLETION(RemoteFile);
+ }
+)
+
+COMPLETION_FUNCTION(CompareLocationOrRemoteDir,
+ if(rCommand.mOptions.find('l') != std::string::npos)
+ {
+ DELEGATE_COMPLETION(LocationName);
+ }
+ else
+ {
+ DELEGATE_COMPLETION(RemoteDir);
+ }
+)
+
+COMPLETION_FUNCTION(CompareNoneOrLocalDir,
+ if(rCommand.mOptions.find('l') != std::string::npos)
+ {
+ // no completions
+ DELEGATE_COMPLETION(None);
+ }
+ else
+ {
+ DELEGATE_COMPLETION(LocalDir);
+ }
+)
+
+COMPLETION_FUNCTION(RestoreRemoteDirOrId,
+ if(rCommand.mOptions.find('i') != std::string::npos)
+ {
+ DELEGATE_COMPLETION(RemoteId);
+ }
+ else
+ {
+ DELEGATE_COMPLETION(RemoteDir);
+ }
+)
+
+// Data about commands
+QueryCommandSpecification commands[] =
+{
+ { "quit", "", Command_Quit, {} },
+ { "exit", "", Command_Quit, {} },
+ { "list", "adDFhiIorRsStTU", Command_List, {CompleteRemoteDir} },
+ { "pwd", "", Command_pwd, {} },
+ { "cd", "od", Command_cd, {CompleteRemoteDir} },
+ { "lcd", "", Command_lcd, {CompleteLocalDir} },
+ { "sh", "", Command_sh, {CompleteDefault} },
+ { "getobject", "", Command_GetObject,
+ {CompleteRemoteId, CompleteLocalDir} },
+ { "get", "i", Command_Get,
+ {CompleteGetFileOrId, CompleteLocalDir} },
+ { "compare", "alcqAEQ", Command_Compare,
+ {CompleteCompareLocationOrRemoteDir, CompleteCompareNoneOrLocalDir} },
+ { "restore", "drif", Command_Restore,
+ {CompleteRestoreRemoteDirOrId, CompleteLocalDir} },
+ { "help", "", Command_Help, {} },
+ { "usage", "m", Command_Usage, {} },
+ { "undelete", "i", Command_Undelete,
+ {CompleteGetFileOrId} },
+ { "delete", "i", Command_Delete, {CompleteGetFileOrId} },
+ { NULL, NULL, Command_Unknown, {} }
+};
+
+const char *alias[] = {"ls", 0};
+const int aliasIs[] = {Command_List, 0};
+
+BackupQueries::ParsedCommand::ParsedCommand(const std::string& Command,
+ bool isFromCommandLine)
+: mInOptions(false),
+ mFailed(false),
+ pSpec(NULL),
+ mCompleteArgCount(0)
+{
+ mCompleteCommand = Command;
+
+ // is the command a shell command?
+ if(Command[0] == 's' && Command[1] == 'h' && Command[2] == ' ' && Command[3] != '\0')
+ {
+ // Yes, run shell command
+ for(int i = 0; commands[i].type != Command_Unknown; i++)
+ {
+ if(commands[i].type == Command_sh)
+ {
+ pSpec = &(commands[i]);
+ break;
+ }
+ }
+
+ mCmdElements[0] = "sh";
+ mCmdElements[1] = Command.c_str() + 3;
+ return;
+ }
+
+ // split command into components
+ bool inQuoted = false;
+ mInOptions = false;
+
+ std::string currentArg;
+ for (std::string::const_iterator c = Command.begin();
+ c != Command.end(); c++)
+ {
+ // Terminating char?
+ if(*c == ((inQuoted)?'"':' '))
+ {
+ if(!currentArg.empty())
+ {
+ mCmdElements.push_back(currentArg);
+
+ // Because we just found a space, and the last
+ // word was not options (otherwise currentArg
+ // would be empty), we've received a complete
+ // command or non-option argument.
+ mCompleteArgCount++;
+ }
+
+ currentArg.resize(0);
+ inQuoted = false;
+ mInOptions = false;
+ }
+ // Start of quoted parameter?
+ else if(currentArg.empty() && *c == '"')
+ {
+ inQuoted = true;
+ }
+ // Start of options? You can't have options if there's no
+ // command before them, so treat the options as a command (which
+ // doesn't exist, so it will fail to parse) in that case.
+ else if(currentArg.empty() && *c == '-' && !mCmdElements.empty())
+ {
+ mInOptions = true;
+ }
+ else if(mInOptions)
+ {
+ // Option char
+ mOptions += *c;
+ }
+ else
+ {
+ // Normal string char, part of current arg
+ currentArg += *c;
+ }
+ }
+
+ if(!currentArg.empty())
+ {
+ mCmdElements.push_back(currentArg);
+ }
+
+ // If there are no commands then there's nothing to do except return
+ if(mCmdElements.empty())
+ {
+ return;
+ }
+
+ // Work out which command it is...
+ int cmd = 0;
+ while(commands[cmd].name != 0 &&
+ mCmdElements[0] != commands[cmd].name)
+ {
+ cmd++;
+ }
+
+ if(commands[cmd].name == 0)
+ {
+ // Check for aliases
+ int a;
+ for(a = 0; alias[a] != 0; ++a)
+ {
+ if(mCmdElements[0] == alias[a])
+ {
+ // Found an alias
+ cmd = aliasIs[a];
+ break;
+ }
+ }
+ }
+
+ if(commands[cmd].name == 0)
+ {
+ mFailed = true;
+ return;
+ }
+
+ pSpec = &(commands[cmd]);
+
+ #ifdef WIN32
+ if(isFromCommandLine)
+ {
+ std::string converted;
+
+ if(!ConvertEncoding(mCompleteCommand, CP_ACP, converted,
+ GetConsoleCP()))
+ {
+ BOX_ERROR("Failed to convert encoding");
+ mFailed = true;
+ }
+
+ mCompleteCommand = converted;
+
+ for(std::vector<std::string>::iterator
+ i = mCmdElements.begin();
+ i != mCmdElements.end(); i++)
+ {
+ if(!ConvertEncoding(*i, CP_ACP, converted,
+ GetConsoleCP()))
+ {
+ BOX_ERROR("Failed to convert encoding");
+ mFailed = true;
+ }
+
+ *i = converted;
+ }
+ }
+ #endif
+}
+
diff --git a/lib/bbackupquery/documentation.txt b/lib/bbackupquery/documentation.txt
new file mode 100644
index 00000000..b16a6f7c
--- /dev/null
+++ b/lib/bbackupquery/documentation.txt
@@ -0,0 +1,194 @@
+
+bbackupquery utility -- examine store, compare files, restore, etc.
+
+This file has markers for automatic help generation script -- '>' marks a start of a command/help topic,
+and '<' marks the end of a section.
+
+Command line:
+=============
+
+> bbackupquery [-q] [-c configfile] [commands ...]
+
+ -q -- quiet, no information prompts
+ -c -- specify another bbackupd configuation file
+
+The commands following the options are executed, then (if there was no quit
+command) an interactive mode is entered.
+
+If a command contains a space, enclose it in quotes. Example
+
+ bbackupquery "list testdir1" quit
+
+to list the contents of testdir1, and then exit without interactive mode.
+<
+
+Commands:
+=========
+
+All directory names relative to a "current" directory, or from root if they
+start with '/'. The initial directory is always the root directory.
+
+
+> ls [options] [directory-name]
+
+ List contents of current directory, or specified directory.
+
+ -R -- recursively list all files
+ -d -- list deleted files/directories
+ -o -- list old versions of files/directories
+ -I -- don't display object ID
+ -F -- don't display flags
+ -t -- show file modification time in local time
+ (and attr mod time if has the object has attributes, ~ separated)
+ -T -- show file modification time in GMT
+ -a -- show updated attribute instead of file modification time
+ -s -- show file size in blocks used on server
+ (only very approximate indication of size locally)
+ -h -- show file attributes hash
+ -D -- sort directories together with files (not dirs first)
+ -i -- sort by object ID (the old default)
+ -S -- sort by object size in blocks
+ -U -- don't sort the results (new default is to sort by name)
+
+list can be used as an alias.
+<
+
+> list
+
+ Alias for 'ls'. Type 'help ls' for options.
+<
+
+> cd [options] <directory-name>
+
+ Change directory
+
+ -d -- consider deleted directories for traversal
+ -o -- consider old versions of directories for traversal
+ (this option should never be useful in a correctly formed store)
+<
+
+> pwd
+
+ Print current directory, always root relative.
+<
+
+> lcd <local-directory-name>
+
+ Change local directory.
+
+ Type "sh ls" to list the contents.
+<
+
+> sh <shell command>
+
+ All of the parameters after the "sh" are run as a shell command.
+
+ For example, to list the contents of the location directory, type "sh ls"
+<
+
+> get <object-filename> [<local-filename>]
+get -i <object-id> <local-filename>
+
+ Gets a file from the store. Object is specified as the filename within
+ the current directory, and local filename is optional. Ignores old and
+ deleted files when searching the directory for the file to retrieve.
+
+ To get an old or deleted file, use the -i option and select the object
+ as a hex object ID (first column in listing). The local filename must
+ be specified.
+<
+
+> compare -a
+compare -l <location-name>
+compare <store-dir-name> <local-dir-name>
+
+ Compares the (current) data on the store with the data on the disc.
+ All the data will be downloaded -- this is potentially a very long
+ operation.
+
+ -a -- compare all locations
+ -l -- compare one backup location as specified in the configuration file.
+ -c -- set return code
+ -q -- quick compare. Only checks file contents against checksums,
+ doesn't do a full download
+ -A -- ignore attribute differences
+ -E -- ignore exclusion settings
+
+ Comparing with the root directory is an error, use -a option instead.
+
+ If -c is set, then the return code (if quit is the next command) will be
+ 1 Comparison was exact
+ 2 Differences were found
+ 3 An error occured
+ This can be used for automated tests.
+<
+
+> restore [-drif] <directory-name> [<local-directory-name>]
+
+ Restores a directory to the local disc. The local directory specified
+ must not exist (unless a previous restore is being restarted). If the
+ local directory is omitted, the default is to restore to the same
+ directory name and path, relative to the current local directory,
+ as set with the "lcd" command.
+
+ The root cannot be restored -- restore locations individually.
+
+ -d -- restore a deleted directory or deleted files inside
+ -r -- resume an interrupted restoration
+ -i -- directory name is actually an ID
+ -f -- force restore to continue if errors are encountered
+
+ If a restore operation is interrupted for any reason, it can be restarted
+ using the -r switch. Restore progress information is saved in a file at
+ regular intervals during the restore operation to allow restarts.
+<
+
+> getobject <object-id> <local-filename>
+
+ Gets the object specified by the object id (in hex) and stores the raw
+ contents in the local file specified.
+
+ This is only useful for debugging as it does not decode files from the
+ stored format, which is encrypted and compressed.
+<
+
+> usage [-m]
+
+ Show space used on the server for this account.
+
+ -m -- display the output in machine-readable form
+
+ Used: Total amount of space used on the server.
+ Old files: Space used by old files
+ Deleted files: Space used by deleted files
+ Directories: Space used by the directory structure.
+
+ When Used exceeds the soft limit, the server will start to remove old and
+ deleted files until the usage drops below the soft limit.
+
+ After a while, you would expect to see the usage stay at just below the
+ soft limit. You only need more space if the space used by old and deleted
+ files is near zero.
+<
+
+> undelete <directory-name>
+undelete -i <object-id>
+
+ Removes the deleted flag from the specified directory name (in the
+ current directory) or hex object ID. Be careful not to use this
+ command where a directory already exists with the same name which is
+ not marked as deleted.
+<
+
+> delete <file-name>
+
+ Sets the deleted flag on the specified file name (in the current
+ directory, or with a relative path).
+<
+
+> quit
+
+ End session and exit.
+<
+
+
diff --git a/lib/bbackupquery/makedocumentation.pl.in b/lib/bbackupquery/makedocumentation.pl.in
new file mode 100755
index 00000000..530c4ff6
--- /dev/null
+++ b/lib/bbackupquery/makedocumentation.pl.in
@@ -0,0 +1,75 @@
+#!@PERL@
+use strict;
+
+print "Creating built-in documentation for bbackupquery...\n";
+
+open DOC,"documentation.txt" or die "Can't open documentation.txt file";
+my $section;
+my %help;
+my @in_order;
+
+while(<DOC>)
+{
+ if(m/\A>\s+(\w+)/)
+ {
+ $section = $1;
+ m/\A>\s+(.+)\Z/;
+ $help{$section} = $1."\n";
+ push @in_order,$section;
+ }
+ elsif(m/\A</)
+ {
+ $section = '';
+ }
+ elsif($section ne '')
+ {
+ $help{$section} .= $_;
+ }
+}
+
+close DOC;
+
+open OUT,">autogen_Documentation.cpp" or die "Can't open output file for writing";
+
+print OUT <<__E;
+//
+// Automatically generated file, do not edit.
+//
+
+#include "Box.h"
+
+#include "MemLeakFindOn.h"
+
+const char *help_commands[] =
+{
+__E
+
+for(@in_order)
+{
+ print OUT qq:\t"$_",\n:;
+}
+
+print OUT <<__E;
+ 0
+};
+
+const char *help_text[] =
+{
+__E
+
+for(@in_order)
+{
+ my $t = $help{$_};
+ $t =~ s/\t/ /g;
+ $t =~ s/\n/\\n/g;
+ $t =~ s/"/\\"/g;
+ print OUT qq:\t"$t",\n:;
+}
+
+print OUT <<__E;
+ 0
+};
+
+__E
+
+close OUT;