summaryrefslogtreecommitdiff
path: root/bin/bbackupquery
diff options
context:
space:
mode:
Diffstat (limited to 'bin/bbackupquery')
-rw-r--r--bin/bbackupquery/BackupQueries.cpp688
-rw-r--r--bin/bbackupquery/BackupQueries.h119
-rw-r--r--bin/bbackupquery/BoxBackupCompareParams.h5
-rw-r--r--bin/bbackupquery/CommandCompletion.cpp602
-rw-r--r--bin/bbackupquery/bbackupquery.cpp282
-rw-r--r--bin/bbackupquery/documentation.txt7
6 files changed, 1255 insertions, 448 deletions
diff --git a/bin/bbackupquery/BackupQueries.cpp b/bin/bbackupquery/BackupQueries.cpp
index 60724800..b8b9525b 100644
--- a/bin/bbackupquery/BackupQueries.cpp
+++ b/bin/bbackupquery/BackupQueries.cpp
@@ -48,9 +48,9 @@
#include "Logging.h"
#include "PathUtils.h"
#include "SelfFlushingStream.h"
-#include "TemporaryDirectory.h"
#include "Utils.h"
-#include "autogen_BackupProtocolClient.h"
+#include "autogen_BackupProtocol.h"
+#include "autogen_CipherException.h"
#include "MemLeakFindOn.h"
@@ -100,12 +100,6 @@ BackupQueries::~BackupQueries()
{
}
-typedef struct
-{
- const char* name;
- const char* opts;
-} QueryCommandSpecification;
-
// --------------------------------------------------------------------------
//
// Function
@@ -114,173 +108,46 @@ typedef struct
// Created: 2003/10/10
//
// --------------------------------------------------------------------------
-void BackupQueries::DoCommand(const char *Command, bool isFromCommandLine)
+void BackupQueries::DoCommand(ParsedCommand& rCommand)
{
- // is the command a shell command?
- if(Command[0] == 's' && Command[1] == 'h' && Command[2] == ' ' && Command[3] != '\0')
+ // Check...
+
+ if(rCommand.mFailed)
{
- // Yes, run shell command
- int result = ::system(Command + 3);
- if(result != 0)
- {
- BOX_WARNING("System command returned error code " <<
- result);
- SetReturnCode(ReturnCode::Command_Error);
- }
+ BOX_ERROR("Parse failed");
return;
}
- // split command into components
- std::vector<std::string> cmdElements;
- std::string options;
+ if(rCommand.mCmdElements.size() < 1)
{
- const char *c = Command;
- bool inQuoted = false;
- bool inOptions = false;
-
- std::string s;
- while(*c != 0)
- {
- // Terminating char?
- if(*c == ((inQuoted)?'"':' '))
- {
- if(!s.empty()) cmdElements.push_back(s);
- s.resize(0);
- inQuoted = false;
- inOptions = false;
- }
- else
- {
- // No. Start of quoted parameter?
- if(s.empty() && *c == '"')
- {
- inQuoted = true;
- }
- // Start of options?
- else if(s.empty() && *c == '-')
- {
- inOptions = true;
- }
- else
- {
- if(inOptions)
- {
- // Option char
- options += *c;
- }
- else
- {
- // Normal string char
- s += *c;
- }
- }
- }
-
- ++c;
- }
- if(!s.empty()) cmdElements.push_back(s);
+ // blank command
+ return;
}
-
- #ifdef WIN32
- if (isFromCommandLine)
+
+ if(rCommand.pSpec->type == Command_sh &&
+ rCommand.mCmdElements.size() == 2)
{
- for (std::vector<std::string>::iterator
- i = cmdElements.begin();
- i != cmdElements.end(); i++)
+ // Yes, run shell command
+ int result = ::system(rCommand.mCmdElements[1].c_str());
+ if(result != 0)
{
- std::string converted;
- if (!ConvertEncoding(*i, CP_ACP, converted,
- GetConsoleCP()))
- {
- BOX_ERROR("Failed to convert encoding");
- return;
- }
- *i = converted;
+ BOX_WARNING("System command returned error code " <<
+ result);
+ SetReturnCode(ReturnCode::Command_Error);
}
- }
- #endif
-
- // Check...
- if(cmdElements.size() < 1)
- {
- // blank command
return;
}
-
- // Data about commands
- static QueryCommandSpecification commands[] =
- {
- { "quit", "" },
- { "exit", "" },
- { "list", "rodIFtTash", },
- { "pwd", "" },
- { "cd", "od" },
- { "lcd", "" },
- { "sh", "" },
- { "getobject", "" },
- { "get", "i" },
- { "compare", "alcqAEQ" },
- { "restore", "drif" },
- { "help", "" },
- { "usage", "m" },
- { "undelete", "i" },
- { "delete", "i" },
- { NULL, NULL }
- };
-
- typedef enum
- {
- Command_Quit = 0,
- Command_Exit,
- 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;
-
- static const char *alias[] = {"ls", 0};
- static const int aliasIs[] = {Command_List, 0};
-
- // Work out which command it is...
- int cmd = 0;
- while(commands[cmd].name != 0 && ::strcmp(cmdElements[0].c_str(), commands[cmd].name) != 0)
- {
- cmd++;
- }
- if(commands[cmd].name == 0)
+
+ if(rCommand.pSpec->type == Command_Unknown)
{
- // Check for aliases
- int a;
- for(a = 0; alias[a] != 0; ++a)
- {
- if(::strcmp(cmdElements[0].c_str(), alias[a]) == 0)
- {
- // Found an alias
- cmd = aliasIs[a];
- break;
- }
- }
-
// No such command
- if(alias[a] == 0)
- {
- BOX_ERROR("Unrecognised command: " << Command);
- return;
- }
+ BOX_ERROR("Unrecognised command: " << rCommand.mCmdElements[0]);
+ return;
}
// Arguments
- std::vector<std::string> args(cmdElements.begin() + 1, cmdElements.end());
+ std::vector<std::string> args(rCommand.mCmdElements.begin() + 1,
+ rCommand.mCmdElements.end());
// Set up options
bool opts[256];
@@ -288,14 +155,14 @@ void BackupQueries::DoCommand(const char *Command, bool isFromCommandLine)
// BLOCK
{
// options
- const char *c = options.c_str();
+ const char *c = rCommand.mOptions.c_str();
while(*c != 0)
{
// Valid option?
- if(::strchr(commands[cmd].opts, *c) == NULL)
+ if(::strchr(rCommand.pSpec->opts, *c) == NULL)
{
BOX_ERROR("Invalid option '" << *c << "' for "
- "command " << commands[cmd].name);
+ "command " << rCommand.pSpec->name);
return;
}
opts[(int)*c] = true;
@@ -303,17 +170,16 @@ void BackupQueries::DoCommand(const char *Command, bool isFromCommandLine)
}
}
- if(cmd != Command_Quit && cmd != Command_Exit)
+ if(rCommand.pSpec->type != Command_Quit)
{
// If not a quit command, set the return code to zero
SetReturnCode(ReturnCode::Command_OK);
}
// Handle command
- switch(cmd)
+ switch(rCommand.pSpec->type)
{
case Command_Quit:
- case Command_Exit:
mQuitNow = true;
break;
@@ -375,7 +241,7 @@ void BackupQueries::DoCommand(const char *Command, bool isFromCommandLine)
break;
default:
- BOX_ERROR("Unknown command: " << Command);
+ BOX_ERROR("Unknown command: " << rCommand.mCmdElements[0]);
break;
}
}
@@ -392,8 +258,6 @@ void BackupQueries::DoCommand(const char *Command, bool isFromCommandLine)
void BackupQueries::CommandList(const std::vector<std::string> &args, const bool *opts)
{
#define LIST_OPTION_RECURSIVE 'r'
- #define LIST_OPTION_ALLOWOLD 'o'
- #define LIST_OPTION_ALLOWDELETED 'd'
#define LIST_OPTION_NOOBJECTID 'I'
#define LIST_OPTION_NOFLAGS 'F'
#define LIST_OPTION_TIMES_LOCAL 't'
@@ -492,22 +356,28 @@ static std::string GetTimeString(BackupStoreDirectory::Entry& en,
// Created: 2003/10/10
//
// --------------------------------------------------------------------------
-void BackupQueries::List(int64_t DirID, const std::string &rListRoot, const bool *opts, bool FirstLevel)
+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 = BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING;
- if(!opts[LIST_OPTION_ALLOWOLD]) excludeFlags |= BackupProtocolClientListDirectory::Flags_OldVersion;
- if(!opts[LIST_OPTION_ALLOWDELETED]) excludeFlags |= BackupProtocolClientListDirectory::Flags_Deleted;
+ 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,
- BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING,
- // both files and directories
- excludeFlags,
- true /* want attributes */);
+ DirID,
+ BackupProtocolListDirectory::Flags_INCLUDE_EVERYTHING,
+ // both files and directories
+ excludeFlags,
+ true /* want attributes */);
}
catch (std::exception &e)
{
@@ -522,7 +392,6 @@ void BackupQueries::List(int64_t DirID, const std::string &rListRoot, const bool
return;
}
-
// Retrieve the directory from the stream following
BackupStoreDirectory dir;
std::auto_ptr<IOStream> dirstream(mrConnection.ReceiveStream());
@@ -533,6 +402,8 @@ void BackupQueries::List(int64_t DirID, const std::string &rListRoot, const bool
BackupStoreDirectory::Entry *en = 0;
while((en = i.Next()) != 0)
{
+ std::ostringstream buf;
+
// Display this entry
BackupStoreFilenameClear clear(en->GetName());
@@ -540,11 +411,9 @@ void BackupQueries::List(int64_t DirID, const std::string &rListRoot, const bool
if(!opts[LIST_OPTION_NOOBJECTID])
{
// add object ID to line
-#ifdef _MSC_VER
- printf("%08I64x ", (int64_t)en->GetObjectID());
-#else
- printf("%08llx ", (long long)en->GetObjectID());
-#endif
+ buf << std::hex << std::internal << std::setw(8) <<
+ std::setfill('0') << en->GetObjectID() <<
+ std::dec << " ";
}
// Flags?
@@ -571,44 +440,40 @@ void BackupQueries::List(int64_t DirID, const std::string &rListRoot, const bool
// terminate
*(f++) = ' ';
*(f++) = '\0';
- printf(displayflags);
+ buf << displayflags;
if(en_flags != 0)
{
- printf("[ERROR: Entry has additional flags set] ");
+ buf << "[ERROR: Entry has additional flags set] ";
}
}
if(opts[LIST_OPTION_TIMES_UTC])
{
// Show UTC times...
- printf("%s ", GetTimeString(*en, false,
- opts[LIST_OPTION_TIMES_ATTRIBS]).c_str());
+ buf << GetTimeString(*en, false,
+ opts[LIST_OPTION_TIMES_ATTRIBS]) << " ";
}
if(opts[LIST_OPTION_TIMES_LOCAL])
{
// Show local times...
- printf("%s ", GetTimeString(*en, true,
- opts[LIST_OPTION_TIMES_ATTRIBS]).c_str());
+ buf << GetTimeString(*en, true,
+ opts[LIST_OPTION_TIMES_ATTRIBS]) << " ";
}
if(opts[LIST_OPTION_DISPLAY_HASH])
{
-#ifdef _MSC_VER
- printf("%016I64x ", (int64_t)en->GetAttributesHash());
-#else
- printf("%016llx ", (long long)en->GetAttributesHash());
-#endif
+ buf << std::hex << std::internal << std::setw(16) <<
+ std::setfill('0') << en->GetAttributesHash() <<
+ std::dec;
}
if(opts[LIST_OPTION_SIZEINBLOCKS])
{
-#ifdef _MSC_VER
- printf("%05I64d ", (int64_t)en->GetSizeInBlocks());
-#else
- printf("%05lld ", (long long)en->GetSizeInBlocks());
-#endif
+ buf << std::internal << std::setw(5) <<
+ std::setfill('0') << en->GetSizeInBlocks() <<
+ " ";
}
// add name
@@ -618,30 +483,60 @@ void BackupQueries::List(int64_t DirID, const std::string &rListRoot, const bool
std::string listRootDecoded;
if(!ConvertUtf8ToConsole(rListRoot.c_str(),
listRootDecoded)) return;
- printf("%s/", listRootDecoded.c_str());
+ listRootDecoded += "/";
+ buf << listRootDecoded;
+ WriteConsole(hOut, listRootDecoded.c_str(),
+ strlen(listRootDecoded.c_str()), &n_chars, NULL);
#else
- printf("%s/", rListRoot.c_str());
+ 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))
{
- std::string fileName;
- if(!ConvertUtf8ToConsole(
- clear.GetClearFilename().c_str(), fileName))
- return;
- printf("%s", fileName.c_str());
+ fileName = fileNameUtf8 + " [convert encoding failed]";
}
-#else
- printf("%s", clear.GetClearFilename().c_str());
#endif
-
+
+ buf << fileName;
+
if(!en->GetName().IsEncrypted())
{
- printf("[FILENAME NOT ENCRYPTED]");
+ buf << " [FILENAME NOT ENCRYPTED]";
}
- printf("\n");
+ 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)
@@ -652,7 +547,9 @@ void BackupQueries::List(int64_t DirID, const std::string &rListRoot, const bool
std::string subroot(rListRoot);
if(!FirstLevel) subroot += '/';
subroot += clear.GetClearFilename();
- List(en->GetObjectID(), subroot, opts, false /* not the first level to list */);
+ List(en->GetObjectID(), subroot, opts,
+ false /* not the first level to list */,
+ pOut);
}
}
}
@@ -681,7 +578,7 @@ int64_t BackupQueries::FindDirectoryObjectID(const std::string &rDirName,
// Start from current stack, or root, whichever is required
std::vector<std::pair<std::string, int64_t> > stack;
- int64_t dirID = BackupProtocolClientListDirectory::RootDirectory;
+ int64_t dirID = BackupProtocolListDirectory::RootDirectory;
if(rDirName.size() > 0 && rDirName[0] == '/')
{
// Root, do nothing
@@ -697,9 +594,9 @@ int64_t BackupQueries::FindDirectoryObjectID(const std::string &rDirName,
}
// Generate exclude flags
- int16_t excludeFlags = BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING;
- if(!AllowOldVersion) excludeFlags |= BackupProtocolClientListDirectory::Flags_OldVersion;
- if(!AllowDeletedDirs) excludeFlags |= BackupProtocolClientListDirectory::Flags_Deleted;
+ 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)
@@ -719,20 +616,20 @@ int64_t BackupQueries::FindDirectoryObjectID(const std::string &rDirName,
stack.pop_back();
// New dir ID
- dirID = (stack.size() > 0)?(stack[stack.size() - 1].second):BackupProtocolClientListDirectory::RootDirectory;
+ dirID = (stack.size() > 0)?(stack[stack.size() - 1].second):BackupProtocolListDirectory::RootDirectory;
}
else
{
// At root anyway
- dirID = BackupProtocolClientListDirectory::RootDirectory;
+ dirID = BackupProtocolListDirectory::RootDirectory;
}
}
else
{
// Not blank element. Read current directory.
- std::auto_ptr<BackupProtocolClientSuccess> dirreply(mrConnection.QueryListDirectory(
+ std::auto_ptr<BackupProtocolSuccess> dirreply(mrConnection.QueryListDirectory(
dirID,
- BackupProtocolClientListDirectory::Flags_Dir, // just directories
+ BackupProtocolListDirectory::Flags_Dir, // just directories
excludeFlags,
true /* want attributes */));
@@ -783,7 +680,7 @@ int64_t BackupQueries::GetCurrentDirectoryID()
// Special case for root
if(mDirStack.size() == 0)
{
- return BackupProtocolClientListDirectory::RootDirectory;
+ return BackupProtocolListDirectory::RootDirectory;
}
// Otherwise, get from the last entry on the stack
@@ -955,7 +852,8 @@ void BackupQueries::CommandGetObject(const std::vector<std::string> &args, const
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).");
+ BOX_ERROR("Not a valid object ID (specified in hex): " <<
+ args[0]);
return;
}
@@ -974,8 +872,8 @@ void BackupQueries::CommandGetObject(const std::vector<std::string> &args, const
try
{
// Request object
- std::auto_ptr<BackupProtocolClientSuccess> getobj(mrConnection.QueryGetObject(id));
- if(getobj->GetObjectID() != BackupProtocolClientGetObject::NoObject)
+ std::auto_ptr<BackupProtocolSuccess> getobj(mrConnection.QueryGetObject(id));
+ if(getobj->GetObjectID() != BackupProtocolGetObject::NoObject)
{
// Stream that object out to the file
std::auto_ptr<IOStream> objectStream(mrConnection.ReceiveStream());
@@ -1062,7 +960,8 @@ int64_t BackupQueries::FindFileID(const std::string& rNameOrIdString,
fileId == std::numeric_limits<long long>::max() ||
fileId == 0)
{
- BOX_ERROR("Not a valid object ID (specified in hex).");
+ BOX_ERROR("Not a valid object ID (specified in hex): "
+ << rNameOrIdString);
return 0;
}
@@ -1154,19 +1053,19 @@ void BackupQueries::CommandGet(std::vector<std::string> args, const bool *opts)
if(opts['i'])
{
// can retrieve anything by ID
- flagsExclude = BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING;
+ flagsExclude = BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING;
}
else
{
// only current versions by name
flagsExclude =
- BackupProtocolClientListDirectory::Flags_OldVersion |
- BackupProtocolClientListDirectory::Flags_Deleted;
+ BackupProtocolListDirectory::Flags_OldVersion |
+ BackupProtocolListDirectory::Flags_Deleted;
}
fileId = FindFileID(args[0], opts, &dirId, &localName,
- BackupProtocolClientListDirectory::Flags_File, // just files
+ BackupProtocolListDirectory::Flags_File, // just files
flagsExclude, NULL /* don't care about flags found */);
if (fileId == 0)
@@ -1469,6 +1368,159 @@ void BackupQueries::Compare(const std::string &rStoreDir,
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);
+ }
+}
// --------------------------------------------------------------------------
//
@@ -1503,10 +1555,10 @@ void BackupQueries::Compare(int64_t DirID, const std::string &rStoreDir,
// Get the directory listing from the store
mrConnection.QueryListDirectory(
DirID,
- BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING,
+ BackupProtocolListDirectory::Flags_INCLUDE_EVERYTHING,
// get everything
- BackupProtocolClientListDirectory::Flags_OldVersion |
- BackupProtocolClientListDirectory::Flags_Deleted,
+ BackupProtocolListDirectory::Flags_OldVersion |
+ BackupProtocolListDirectory::Flags_Deleted,
// except for old versions and deleted files
true /* want attributes */);
@@ -1700,124 +1752,8 @@ void BackupQueries::Compare(int64_t DirID, const std::string &rStoreDir,
}
else
{
- int64_t fileSize = 0;
-
- EMU_STRUCT_STAT st;
- if(EMU_STAT(localPath.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;
-
- if(rParams.QuickCompare())
- {
- // Compare file -- fetch it
- mrConnection.QueryGetBlockIndexByID(i->second->GetObjectID());
-
- // Stream containing block index
- std::auto_ptr<IOStream> blockIndexStream(mrConnection.ReceiveStream());
-
- // Compare
- equal = BackupStoreFile::CompareFileContentsAgainstBlockIndex(localPath.c_str(), *blockIndexStream, mrConnection.GetTimeout());
- }
- else
- {
- // Compare file -- fetch it
- mrConnection.QueryGetFile(DirID, i->second->GetObjectID());
-
- // Stream containing encoded file
- std::auto_ptr<IOStream> objectStream(mrConnection.ReceiveStream());
-
- // Decode it
- std::auto_ptr<BackupStoreFile::DecodedStream> fileOnServerStream;
- // Got additional attributes?
- if(i->second->HasAttributes())
- {
- // Use these attributes
- const StreamableMemBlock &storeAttr(i->second->GetAttributes());
- BackupClientFileAttributes attr(storeAttr);
- fileOnServerStream.reset(BackupStoreFile::DecodeFileStream(*objectStream, mrConnection.GetTimeout(), &attr).release());
- }
- else
- {
- // Use attributes stored in file
- fileOnServerStream.reset(BackupStoreFile::DecodeFileStream(*objectStream, mrConnection.GetTimeout()).release());
- }
-
- // Should always be something in the auto_ptr, it's how the interface is defined. But be paranoid.
- if(!fileOnServerStream.get())
- {
- THROW_EXCEPTION(BackupStoreException, Internal)
- }
-
- // Compare attributes
- BackupClientFileAttributes localAttr;
- box_time_t fileModTime = 0;
- localAttr.ReadAttributes(localPath.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
- FileStream l(localPath.c_str());
- equal = l.CompareWith(*fileOnServerStream,
- mrConnection.GetTimeout());
- }
- }
-
- rParams.NotifyFileCompared(localPath,
- storePath, fileSize,
- hasDifferentAttribs, !equal,
- modifiedAfterLastSync,
- i->second->HasAttributes());
- }
- catch(BoxException &e)
- {
- rParams.NotifyDownloadFailed(localPath,
- storePath, fileSize, e);
- }
- catch(std::exception &e)
- {
- rParams.NotifyDownloadFailed(localPath,
- storePath, fileSize, e);
- }
- catch(...)
- {
- rParams.NotifyDownloadFailed(localPath,
- storePath, fileSize);
- }
+ CompareOneFile(DirID, i->second, localPath,
+ storePath, rParams);
// Remove from set so that we know it's been compared
localFiles.erase(local);
@@ -1947,9 +1883,10 @@ void BackupQueries::Compare(int64_t DirID, const std::string &rStoreDir,
void BackupQueries::CommandRestore(const std::vector<std::string> &args, const bool *opts)
{
// Check arguments
- if(args.size() != 2)
+ if(args.size() < 1 || args.size() > 2)
{
- BOX_ERROR("Incorrect usage. restore [-drif] <remote-name> <local-name>");
+ BOX_ERROR("Incorrect usage. restore [-drif] <remote-name> "
+ "[<local-name>]");
return;
}
@@ -1966,7 +1903,8 @@ void BackupQueries::CommandRestore(const std::vector<std::string> &args, const b
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)");
+ BOX_ERROR("Not a valid object ID (specified in hex): "
+ << args[0]);
return;
}
std::ostringstream oss;
@@ -1994,18 +1932,30 @@ void BackupQueries::CommandRestore(const std::vector<std::string> &args, const b
BOX_ERROR("Directory '" << args[0] << "' not found on server");
return;
}
- if(dirID == BackupProtocolClientListDirectory::RootDirectory)
+
+ if(dirID == BackupProtocolListDirectory::RootDirectory)
{
BOX_ERROR("Cannot restore the root directory -- restore locations individually.");
return;
}
-
-#ifdef WIN32
+
std::string localName;
- if(!ConvertConsoleToUtf8(args[1].c_str(), localName)) return;
-#else
- std::string localName(args[1]);
-#endif
+
+ 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;
@@ -2082,8 +2032,8 @@ void BackupQueries::CommandRestore(const std::vector<std::string> &args, const b
// These are autogenerated by a script.
-extern char *help_commands[];
-extern char *help_text[];
+extern const char *help_commands[];
+extern const char *help_text[];
// --------------------------------------------------------------------------
@@ -2140,7 +2090,7 @@ void BackupQueries::CommandUsage(const bool *opts)
bool MachineReadable = opts['m'];
// Request full details from the server
- std::auto_ptr<BackupProtocolClientAccountUsage> usage(mrConnection.QueryGetAccountUsage());
+ std::auto_ptr<BackupProtocolAccountUsage> usage(mrConnection.QueryGetAccountUsage());
// Display each entry in turn
int64_t hardLimit = usage->GetBlocksHardLimit();
@@ -2216,9 +2166,9 @@ void BackupQueries::CommandUndelete(const std::vector<std::string> &args, const
fileId = FindFileID(storeDirEncoded, opts, &parentId, &fileName,
/* include files and directories */
- BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING,
+ BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING,
/* include old and deleted files */
- BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING,
+ BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING,
&flagsOut);
if (fileId == 0)
@@ -2231,7 +2181,7 @@ void BackupQueries::CommandUndelete(const std::vector<std::string> &args, const
try
{
// Undelete object
- if(flagsOut & BackupProtocolClientListDirectory::Flags_File)
+ if(flagsOut & BackupProtocolListDirectory::Flags_File)
{
mrConnection.QueryUndeleteFile(parentId, fileId);
}
@@ -2296,10 +2246,10 @@ void BackupQueries::CommandDelete(const std::vector<std::string> &args,
fileId = FindFileID(storeDirEncoded, opts, &parentId, &fileName,
/* include files and directories */
- BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING,
+ BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING,
/* exclude old and deleted files */
- BackupProtocolClientListDirectory::Flags_OldVersion |
- BackupProtocolClientListDirectory::Flags_Deleted,
+ BackupProtocolListDirectory::Flags_OldVersion |
+ BackupProtocolListDirectory::Flags_Deleted,
&flagsOut);
if (fileId == 0)
@@ -2314,7 +2264,7 @@ void BackupQueries::CommandDelete(const std::vector<std::string> &args,
try
{
// Delete object
- if(flagsOut & BackupProtocolClientListDirectory::Flags_File)
+ if(flagsOut & BackupProtocolListDirectory::Flags_File)
{
mrConnection.QueryDeleteFile(parentId, fn);
}
diff --git a/bin/bbackupquery/BackupQueries.h b/bin/bbackupquery/BackupQueries.h
index 392aa428..62ff231d 100644
--- a/bin/bbackupquery/BackupQueries.h
+++ b/bin/bbackupquery/BackupQueries.h
@@ -10,16 +10,40 @@
#ifndef BACKUPQUERIES__H
#define BACKUPQUERIES__H
-#include <vector>
+#include <iostream>
#include <string>
+#include <vector>
#include "BoxTime.h"
#include "BoxBackupCompareParams.h"
+#include "BackupStoreDirectory.h"
class BackupProtocolClient;
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
@@ -38,8 +62,24 @@ public:
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(); }
+ };
- void DoCommand(const char *Command, bool isFromCommandLine);
+ void DoCommand(ParsedCommand& rCommand);
// Ready to stop?
bool Stop() {return mQuitNow;}
@@ -47,9 +87,11 @@ public:
// Return code?
int GetReturnCode() {return mReturnCode;}
-private:
- // Commands
+ 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);
@@ -64,11 +106,6 @@ private:
int64_t HardLimit, int32_t BlockSize, bool MachineReadable);
void CommandHelp(const std::vector<std::string> &args);
- // Implementations
- void List(int64_t DirID, const std::string &rListRoot, const bool *opts,
- bool FirstLevel);
-
-public:
class CompareParams : public BoxBackupCompareParams
{
public:
@@ -201,6 +238,24 @@ public:
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)
{
@@ -302,13 +357,16 @@ public:
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:
- enum {
+ typedef enum {
Command_OK = 0,
Compare_Same = 1,
Compare_Different,
@@ -317,17 +375,19 @@ public:
} Type;
};
-private:
-
- // Utility functions
+ // 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);
- int64_t GetCurrentDirectoryID();
std::string GetCurrentDirectoryName();
void SetReturnCode(int code) {mReturnCode = code;}
@@ -342,5 +402,36 @@ private:
int mReturnCode;
};
+typedef std::vector<std::string> (*CompletionHandler)
+ (BackupQueries::ParsedCommand& rCommand, const std::string& prefix,
+ BackupProtocolClient& rProtocol, const Configuration& rConfig,
+ BackupQueries& rQueries);
+
+std::vector<std::string> CompleteCommand(BackupQueries::ParsedCommand& rCommand,
+ const std::string& prefix, BackupProtocolClient& rProtocol,
+ const Configuration& rConfig, BackupQueries& rQueries);
+std::vector<std::string> CompleteOptions(BackupQueries::ParsedCommand& rCommand,
+ const std::string& prefix, BackupProtocolClient& 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/bin/bbackupquery/BoxBackupCompareParams.h b/bin/bbackupquery/BoxBackupCompareParams.h
index c58759a2..655df947 100644
--- a/bin/bbackupquery/BoxBackupCompareParams.h
+++ b/bin/bbackupquery/BoxBackupCompareParams.h
@@ -82,6 +82,11 @@ public:
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;
diff --git a/bin/bbackupquery/CommandCompletion.cpp b/bin/bbackupquery/CommandCompletion.cpp
new file mode 100644
index 00000000..93c4d3fd
--- /dev/null
+++ b/bin/bbackupquery/CommandCompletion.cpp
@@ -0,0 +1,602 @@
+// --------------------------------------------------------------------------
+//
+// 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, \
+ BackupProtocolClient& 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, BackupProtocolClient& 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", "rodIFtTash", 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?
+ else if(currentArg.empty() && *c == '-')
+ {
+ 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/bin/bbackupquery/bbackupquery.cpp b/bin/bbackupquery/bbackupquery.cpp
index 5aa7e97e..5493f49c 100644
--- a/bin/bbackupquery/bbackupquery.cpp
+++ b/bin/bbackupquery/bbackupquery.cpp
@@ -30,6 +30,7 @@
#include <readline.h>
#endif
#endif
+
#ifdef HAVE_READLINE_HISTORY
#ifdef HAVE_READLINE_HISTORY_H
#include <readline/history.h>
@@ -49,7 +50,7 @@
#include "SSLLib.h"
#include "BackupStoreConstants.h"
#include "BackupStoreException.h"
-#include "autogen_BackupProtocolClient.h"
+#include "autogen_BackupProtocol.h"
#include "BackupQueries.h"
#include "FdGetLine.h"
#include "BackupClientCryptoKeys.h"
@@ -60,20 +61,128 @@
void PrintUsageAndExit()
{
- printf("Usage: bbackupquery [-q*|v*|V|W<level>] [-w] "
+ std::ostringstream out;
+ out <<
+ "Usage: bbackupquery [options] [command]...\n"
+ "\n"
+ "Options:\n"
+ " -q Run more quietly, reduce verbosity level by one, can repeat\n"
+ " -Q Run at minimum verbosity, log nothing\n"
+ " -v Run more verbosely, increase verbosity level by one, can repeat\n"
+ " -V Run at maximum verbosity, log everything\n"
+ " -W <level> Set verbosity to error/warning/notice/info/trace/everything\n"
+ " -w Read/write mode, allow changes to store\n"
#ifdef WIN32
- "[-u] "
+ " -u Enable Unicode console, requires font change to Lucida Console\n"
+#endif
+#ifdef HAVE_LIBREADLINE
+ " -E Disable interactive command editing, may fix entering intl chars\n"
#endif
- "\n"
- "\t[-c config_file] [-o log_file] [-O log_file_level]\n"
- "\t[-l protocol_log_file] [commands]\n"
- "\n"
- "As many commands as you require.\n"
- "If commands are multiple words, remember to enclose the command in quotes.\n"
- "Remember to use the quit command unless you want to end up in interactive mode.\n");
+ " -c <file> Use the specified configuration file. If -c is omitted, the last\n"
+ " argument is the configuration file, or else the default \n"
+ " [" << BOX_GET_DEFAULT_BBACKUPD_CONFIG_FILE <<
+ "]\n"
+ " -o <file> Write logging output to specified file as well as console\n"
+ " -O <level> Set file verbosity to error/warning/notice/info/trace/everything\n"
+ " -l <file> Write protocol debugging logs to specified file\n"
+ "\n"
+ "Parameters: as many commands as you like. If commands are multiple words,\n"
+ "remember to enclose the command in quotes. Remember to use the quit command\n"
+ "unless you want to end up in interactive mode.\n";
+ printf("%s", out.str().c_str());
exit(1);
}
+#ifdef HAVE_LIBREADLINE
+static BackupProtocolClient* pProtocol;
+static const Configuration* pConfig;
+static BackupQueries* pQueries;
+static std::vector<std::string> completions;
+static std::auto_ptr<BackupQueries::ParsedCommand> sapCmd;
+
+char * completion_generator(const char *text, int state)
+{
+ if(state == 0)
+ {
+ completions.clear();
+
+ std::string partialCommand(rl_line_buffer, rl_point);
+ sapCmd.reset(new BackupQueries::ParsedCommand(partialCommand,
+ false));
+ int currentArg = sapCmd->mCompleteArgCount;
+
+ if(currentArg == 0) // incomplete command
+ {
+ completions = CompleteCommand(*sapCmd, text, *pProtocol,
+ *pConfig, *pQueries);
+ }
+ else if(sapCmd->mInOptions)
+ {
+ completions = CompleteOptions(*sapCmd, text, *pProtocol,
+ *pConfig, *pQueries);
+ }
+ else if(currentArg - 1 < MAX_COMPLETION_HANDLERS)
+ // currentArg must be at least 1 if we're here
+ {
+ CompletionHandler handler =
+ sapCmd->pSpec->complete[currentArg - 1];
+
+ if(handler != NULL)
+ {
+ completions = handler(*sapCmd, text, *pProtocol,
+ *pConfig, *pQueries);
+ }
+
+ if(std::string(text) == "")
+ {
+ // additional options are also allowed here
+ std::vector<std::string> addOpts =
+ CompleteOptions(*sapCmd, text,
+ *pProtocol, *pConfig,
+ *pQueries);
+
+ for(std::vector<std::string>::iterator
+ i = addOpts.begin();
+ i != addOpts.end(); i++)
+ {
+ completions.push_back(*i);
+ }
+ }
+ }
+ }
+
+ if(state < 0 || state >= (int) completions.size())
+ {
+ rl_attempted_completion_over = 1;
+ return NULL;
+ }
+
+ return strdup(completions[state].c_str());
+ // string must be allocated with malloc() and will be freed
+ // by rl_completion_matches().
+}
+
+#ifdef HAVE_RL_COMPLETION_MATCHES
+ #define RL_COMPLETION_MATCHES rl_completion_matches
+#elif defined HAVE_COMPLETION_MATCHES
+ #define RL_COMPLETION_MATCHES completion_matches
+#else
+ char* no_matches[] = {NULL};
+ char** bbackupquery_completion_dummy(const char *text,
+ char * (completion_generator)(const char *text, int state))
+ {
+ return no_matches;
+ }
+ #define RL_COMPLETION_MATCHES bbackupquery_completion_dummy
+#endif
+
+char ** bbackupquery_completion(const char *text, int start, int end)
+{
+ return RL_COMPLETION_MATCHES(text, completion_generator);
+}
+
+#endif // HAVE_LIBREADLINE
+
int main(int argc, const char *argv[])
{
int returnCode = 0;
@@ -103,13 +212,7 @@ int main(int argc, const char *argv[])
FILE *logFile = 0;
// Filename for configuration file?
- std::string configFilename;
-
- #ifdef WIN32
- configFilename = BOX_GET_DEFAULT_BBACKUPD_CONFIG_FILE;
- #else
- configFilename = BOX_FILE_BBACKUPD_DEFAULT_CONFIG;
- #endif
+ std::string configFilename = BOX_GET_DEFAULT_BBACKUPD_CONFIG_FILE;
// Flags
bool readWrite = false;
@@ -123,12 +226,21 @@ int main(int argc, const char *argv[])
#endif
#ifdef WIN32
- const char* validOpts = "qvVwuc:l:o:O:W:";
+ #define WIN32_OPTIONS "u"
bool unicodeConsole = false;
#else
- const char* validOpts = "qvVwc:l:o:O:W:";
+ #define WIN32_OPTIONS
#endif
+#ifdef HAVE_LIBREADLINE
+ #define READLINE_OPTIONS "E"
+ bool useReadline = true;
+#else
+ #define READLINE_OPTIONS
+#endif
+
+ const char* validOpts = "qvVwc:l:o:O:W:" WIN32_OPTIONS READLINE_OPTIONS;
+
std::string fileLogFile;
Log::Level fileLogLevel = Log::INVALID;
@@ -222,6 +334,12 @@ int main(int argc, const char *argv[])
unicodeConsole = true;
break;
#endif
+
+#ifdef HAVE_LIBREADLINE
+ case 'E':
+ useReadline = false;
+ break;
+#endif
case '?':
default:
@@ -317,7 +435,9 @@ int main(int argc, const char *argv[])
// 3. Make a protocol, and handshake
if(!quiet) BOX_INFO("Handshake with store...");
- BackupProtocolClient connection(socket);
+ std::auto_ptr<BackupProtocolClient>
+ apConnection(new BackupProtocolClient(socket));
+ BackupProtocolClient& connection(*(apConnection.get()));
connection.Handshake();
// logging?
@@ -330,15 +450,15 @@ int main(int argc, const char *argv[])
if(!quiet) BOX_INFO("Login to store...");
// Check the version of the server
{
- std::auto_ptr<BackupProtocolClientVersion> serverVersion(connection.QueryVersion(BACKUP_STORE_SERVER_VERSION));
+ std::auto_ptr<BackupProtocolVersion> serverVersion(connection.QueryVersion(BACKUP_STORE_SERVER_VERSION));
if(serverVersion->GetVersion() != BACKUP_STORE_SERVER_VERSION)
{
THROW_EXCEPTION(BackupStoreException, WrongServerVersion)
}
}
// Login -- if this fails, the Protocol will exception
- connection.QueryLogin(conf.GetKeyValueInt("AccountNumber"),
- (readWrite)?0:(BackupProtocolClientLogin::Flags_ReadOnly));
+ connection.QueryLogin(conf.GetKeyValueUint32("AccountNumber"),
+ (readWrite)?0:(BackupProtocolLogin::Flags_ReadOnly));
// 5. Tell user.
if(!quiet) printf("Login complete.\n\nType \"help\" for a list of commands.\n\n");
@@ -351,66 +471,102 @@ int main(int argc, const char *argv[])
int c = 0;
while(c < argc && !context.Stop())
{
- context.DoCommand(argv[c++], true);
+ BackupQueries::ParsedCommand cmd(argv[c++], true);
+ context.DoCommand(cmd);
}
}
// Get commands from input
#ifdef HAVE_LIBREADLINE
- // Must initialise the locale before using editline's readline(),
- // otherwise cannot enter international characters.
- if (setlocale(LC_ALL, "") == NULL)
+ if(useReadline)
{
- BOX_ERROR("Failed to initialise locale. International "
- "character support may not work.");
+ // Must initialise the locale before using editline's
+ // readline(), otherwise cannot enter international characters.
+ if (setlocale(LC_ALL, "") == NULL)
+ {
+ BOX_ERROR("Failed to initialise locale. International "
+ "character support may not work.");
+ }
+
+ #ifdef HAVE_READLINE_HISTORY
+ using_history();
+ #endif
+
+ /* Allow conditional parsing of the ~/.inputrc file. */
+ rl_readline_name = strdup("bbackupquery");
+
+ /* Tell the completer that we want a crack first. */
+ rl_attempted_completion_function = bbackupquery_completion;
+
+ pProtocol = &connection;
+ pConfig = &conf;
+ pQueries = &context;
}
-#ifdef HAVE_READLINE_HISTORY
- using_history();
+ std::string last_cmd;
#endif
- char *last_cmd = 0;
- while(!context.Stop())
+
+ std::auto_ptr<FdGetLine> apGetLine;
+ if(fileno(stdin) >= 0)
+ {
+ apGetLine.reset(new FdGetLine(fileno(stdin)));
+ }
+
+ while(!context.Stop() && fileno(stdin) >= 0)
{
- char *command = readline("query > ");
- if(command == NULL)
+ std::string cmd_str;
+
+ #ifdef HAVE_LIBREADLINE
+ if(useReadline)
{
- // Ctrl-D pressed -- terminate now
- break;
+ char *cmd_ptr = readline("query > ");
+
+ if(cmd_ptr == NULL)
+ {
+ // Ctrl-D pressed -- terminate now
+ break;
+ }
+
+ cmd_str = cmd_ptr;
+ free(cmd_ptr);
}
- context.DoCommand(command, false);
- if(last_cmd != 0 && ::strcmp(last_cmd, command) == 0)
+ else
+ #endif // HAVE_LIBREADLINE
{
- free(command);
+ printf("query > ");
+ fflush(stdout);
+
+ try
+ {
+ cmd_str = apGetLine->GetLine();
+ }
+ catch(CommonException &e)
+ {
+ if(e.GetSubType() == CommonException::GetLineEOF)
+ {
+ break;
+ }
+ throw;
+ }
}
- else
+
+ BackupQueries::ParsedCommand cmd_parsed(cmd_str, false);
+ if (cmd_parsed.IsEmpty())
{
-#ifdef HAVE_READLINE_HISTORY
- add_history(command);
-#else
- free(last_cmd);
-#endif
- last_cmd = command;
+ continue;
}
- }
-#ifndef HAVE_READLINE_HISTORY
- free(last_cmd);
- last_cmd = 0;
-#endif
-#else
- // Version for platforms which don't have readline by default
- if(fileno(stdin) >= 0)
- {
- FdGetLine getLine(fileno(stdin));
- while(!context.Stop())
+
+ context.DoCommand(cmd_parsed);
+
+ #ifdef HAVE_READLINE_HISTORY
+ if(last_cmd != cmd_str)
{
- printf("query > ");
- fflush(stdout);
- std::string command(getLine.GetLine());
- context.DoCommand(command.c_str(), false);
+ add_history(cmd_str.c_str());
+ last_cmd = cmd_str;
}
+ #endif // HAVE_READLINE_HISTORY
}
-#endif
// Done... stop nicely
if(!quiet) BOX_INFO("Logging off...");
diff --git a/bin/bbackupquery/documentation.txt b/bin/bbackupquery/documentation.txt
index 96eda158..214fe218 100644
--- a/bin/bbackupquery/documentation.txt
+++ b/bin/bbackupquery/documentation.txt
@@ -119,10 +119,13 @@ compare <store-dir-name> <local-dir-name>
This can be used for automated tests.
<
-> restore [-drif] <directory-name> <local-directory-name>
+> 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).
+ 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.