diff options
Diffstat (limited to 'bin/bbackupquery')
-rw-r--r-- | bin/bbackupquery/BackupQueries.cpp | 688 | ||||
-rw-r--r-- | bin/bbackupquery/BackupQueries.h | 119 | ||||
-rw-r--r-- | bin/bbackupquery/BoxBackupCompareParams.h | 5 | ||||
-rw-r--r-- | bin/bbackupquery/CommandCompletion.cpp | 602 | ||||
-rw-r--r-- | bin/bbackupquery/bbackupquery.cpp | 282 | ||||
-rw-r--r-- | bin/bbackupquery/documentation.txt | 7 |
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. |