source: box/trunk/bin/bbackupquery/bbackupquery.cpp @ 3010

Revision 3010, 13.3 KB checked in by chris, 8 months ago (diff)

Only advertise -E option if readline support is built in.

  • Property svn:eol-style set to native
Line 
1// --------------------------------------------------------------------------
2//
3// File
4//              Name:    bbackupquery.cpp
5//              Purpose: Backup query utility
6//              Created: 2003/10/10
7//
8// --------------------------------------------------------------------------
9
10#include "Box.h"
11
12#ifdef HAVE_UNISTD_H
13        #include <unistd.h>
14#endif
15
16#include <errno.h>
17#include <cstdio>
18#include <cstdlib>
19
20#ifdef HAVE_SYS_TYPES_H
21        #include <sys/types.h>
22#endif
23
24#ifdef HAVE_LIBREADLINE
25        #ifdef HAVE_READLINE_READLINE_H
26                #include <readline/readline.h>
27        #elif defined(HAVE_EDITLINE_READLINE_H)
28                #include <editline/readline.h>
29        #elif defined(HAVE_READLINE_H)
30                #include <readline.h>
31        #endif
32#endif
33
34#ifdef HAVE_READLINE_HISTORY
35        #ifdef HAVE_READLINE_HISTORY_H
36                #include <readline/history.h>
37        #elif defined(HAVE_HISTORY_H)
38                #include <history.h>
39        #endif
40#endif
41
42#include <cstdlib>
43
44#include "MainHelper.h"
45#include "BoxPortsAndFiles.h"
46#include "BackupDaemonConfigVerify.h"
47#include "SocketStreamTLS.h"
48#include "Socket.h"
49#include "TLSContext.h"
50#include "SSLLib.h"
51#include "BackupStoreConstants.h"
52#include "BackupStoreException.h"
53#include "autogen_BackupProtocol.h"
54#include "BackupQueries.h"
55#include "FdGetLine.h"
56#include "BackupClientCryptoKeys.h"
57#include "BannerText.h"
58#include "Logging.h"
59
60#include "MemLeakFindOn.h"
61
62void PrintUsageAndExit()
63{
64        std::ostringstream out;
65        out << 
66                "Usage: bbackupquery [options] [command]...\n"
67                "\n"
68                "Options:\n"
69                "  -q         Run more quietly, reduce verbosity level by one, can repeat\n"
70                "  -Q         Run at minimum verbosity, log nothing\n"
71                "  -v         Run more verbosely, increase verbosity level by one, can repeat\n"
72                "  -V         Run at maximum verbosity, log everything\n"
73                "  -W <level> Set verbosity to error/warning/notice/info/trace/everything\n"
74                "  -w         Read/write mode, allow changes to store\n"
75#ifdef WIN32
76                "  -u         Enable Unicode console, requires font change to Lucida Console\n"
77#endif
78#ifdef HAVE_LIBREADLINE
79                "  -E         Disable interactive command editing, may fix entering intl chars\n"
80#endif
81                "  -c <file>  Use the specified configuration file. If -c is omitted, the last\n"
82                "             argument is the configuration file, or else the default \n"
83                "             [" << BOX_GET_DEFAULT_BBACKUPD_CONFIG_FILE << 
84                "]\n"
85                "  -o <file>  Write logging output to specified file as well as console\n"
86                "  -O <level> Set file verbosity to error/warning/notice/info/trace/everything\n"
87                "  -l <file>  Write protocol debugging logs to specified file\n"
88                "\n"
89                "Parameters: as many commands as you like. If commands are multiple words,\n"
90                "remember to enclose the command in quotes. Remember to use the quit command\n"
91                "unless you want to end up in interactive mode.\n";
92        printf("%s", out.str().c_str());
93        exit(1);
94}
95
96#ifdef HAVE_LIBREADLINE
97static BackupProtocolClient* pProtocol;
98static const Configuration* pConfig;
99static BackupQueries* pQueries;
100static std::vector<std::string> completions;
101static std::auto_ptr<BackupQueries::ParsedCommand> sapCmd;
102
103char * completion_generator(const char *text, int state)
104{
105        if(state == 0)
106        {
107                completions.clear();
108
109                std::string partialCommand(rl_line_buffer, rl_point);
110                sapCmd.reset(new BackupQueries::ParsedCommand(partialCommand,
111                        false));
112
113                if(sapCmd->mArgCount == 0) // incomplete command
114                {
115                        completions = CompleteCommand(*sapCmd, text, *pProtocol,
116                                *pConfig, *pQueries);
117                }
118                else if(sapCmd->mInOptions)
119                {
120                        completions = CompleteOptions(*sapCmd, text, *pProtocol,
121                                *pConfig, *pQueries);
122                }
123                else if(sapCmd->mArgCount - 1 < MAX_COMPLETION_HANDLERS)
124                // sapCmd->mArgCount must be at least 1 if we're here
125                {
126                        CompletionHandler handler =
127                                sapCmd->pSpec->complete[sapCmd->mArgCount - 1];
128                        if(handler != NULL)
129                        {
130                                completions = handler(*sapCmd, text, *pProtocol,
131                                        *pConfig, *pQueries);
132                        }
133
134                        if(std::string(text) == "")
135                        {
136                                // additional options are also allowed here
137                                std::vector<std::string> addOpts =
138                                        CompleteOptions(*sapCmd, text,
139                                                *pProtocol, *pConfig,
140                                                *pQueries);
141
142                                for(std::vector<std::string>::iterator
143                                        i =  addOpts.begin();
144                                        i != addOpts.end(); i++)
145                                {
146                                        completions.push_back(*i);
147                                }
148                        }
149                }
150        }
151
152        if(state < 0 || state >= (int) completions.size())
153        {
154                rl_attempted_completion_over = 1;
155                return NULL;
156        }
157
158        return strdup(completions[state].c_str());
159        // string must be allocated with malloc() and will be freed
160        // by rl_completion_matches().
161}
162
163#ifdef HAVE_RL_COMPLETION_MATCHES
164        #define RL_COMPLETION_MATCHES rl_completion_matches
165#elif defined HAVE_COMPLETION_MATCHES
166        #define RL_COMPLETION_MATCHES completion_matches
167#else
168        char* no_matches[] = {NULL};
169        char** bbackupquery_completion_dummy(const char *text, 
170                char * (completion_generator)(const char *text, int state))
171        {
172                return no_matches;
173        }
174        #define RL_COMPLETION_MATCHES bbackupquery_completion_dummy
175#endif
176
177char ** bbackupquery_completion(const char *text, int start, int end)
178{
179        return RL_COMPLETION_MATCHES(text, completion_generator);
180}
181
182#endif // HAVE_LIBREADLINE
183
184int main(int argc, const char *argv[])
185{
186        int returnCode = 0;
187
188        MAINHELPER_SETUP_MEMORY_LEAK_EXIT_REPORT("bbackupquery.memleaks",
189                "bbackupquery")
190        MAINHELPER_START
191
192#ifdef WIN32
193        WSADATA info;
194       
195        // Under Win32 we must initialise the Winsock library
196        // before using it.
197       
198        if (WSAStartup(0x0101, &info) == SOCKET_ERROR) 
199        {
200                // throw error? perhaps give it its own id in the future
201                THROW_EXCEPTION(BackupStoreException, Internal)
202        }
203#endif
204
205        // Really don't want trace statements happening, even in debug mode
206        #ifndef BOX_RELEASE_BUILD
207                BoxDebugTraceOn = false;
208        #endif
209       
210        FILE *logFile = 0;
211
212        // Filename for configuration file?
213        std::string configFilename = BOX_GET_DEFAULT_BBACKUPD_CONFIG_FILE;
214       
215        // Flags
216        bool readWrite = false;
217
218        Logging::SetProgramName("bbackupquery");
219
220        #ifdef BOX_RELEASE_BUILD
221        int masterLevel = Log::NOTICE; // need an int to do math with
222        #else
223        int masterLevel = Log::INFO; // need an int to do math with
224        #endif
225
226#ifdef WIN32
227        #define WIN32_OPTIONS "u"
228        bool unicodeConsole = false;
229#else
230        #define WIN32_OPTIONS
231#endif
232
233#ifdef HAVE_LIBREADLINE
234        #define READLINE_OPTIONS "E"
235        bool useReadline = true;
236#else
237        #define READLINE_OPTIONS
238#endif
239
240        const char* validOpts = "qvVwc:l:o:O:W:" WIN32_OPTIONS READLINE_OPTIONS;
241
242        std::string fileLogFile;
243        Log::Level fileLogLevel = Log::INVALID;
244
245        // See if there's another entry on the command line
246        int c;
247        while((c = getopt(argc, (char * const *)argv, validOpts)) != -1)
248        {
249                switch(c)
250                {
251                case 'q':
252                        {
253                                if(masterLevel == Log::NOTHING)
254                                {
255                                        BOX_FATAL("Too many '-q': "
256                                                "Cannot reduce logging "
257                                                "level any more");
258                                        return 2;
259                                }
260                                masterLevel--;
261                        }
262                        break;
263
264                case 'v':
265                        {
266                                if(masterLevel == Log::EVERYTHING)
267                                {
268                                        BOX_FATAL("Too many '-v': "
269                                                "Cannot increase logging "
270                                                "level any more");
271                                        return 2;
272                                }
273                                masterLevel++;
274                        }
275                        break;
276
277                case 'V':
278                        {
279                                masterLevel = Log::EVERYTHING;
280                        }
281                        break;
282
283                case 'W':
284                        {
285                                masterLevel = Logging::GetNamedLevel(optarg);
286                                if (masterLevel == Log::INVALID)
287                                {
288                                        BOX_FATAL("Invalid logging level");
289                                        return 2;
290                                }
291                        }
292                        break;
293
294                case 'w':
295                        // Read/write mode
296                        readWrite = true;
297                        break;
298               
299                case 'c':
300                        // store argument
301                        configFilename = optarg;
302                        break;
303               
304                case 'l':
305                        // open log file
306                        logFile = ::fopen(optarg, "w");
307                        if(logFile == 0)
308                        {
309                                BOX_LOG_SYS_ERROR("Failed to open log file "
310                                        "'" << optarg << "'");
311                        }
312                        break;
313
314                case 'o':
315                        fileLogFile = optarg;
316                        fileLogLevel = Log::EVERYTHING;
317                        break;
318
319                case 'O':
320                        {
321                                fileLogLevel = Logging::GetNamedLevel(optarg);
322                                if (fileLogLevel == Log::INVALID)
323                                {
324                                        BOX_FATAL("Invalid logging level");
325                                        return 2;
326                                }
327                        }
328                        break;
329
330#ifdef WIN32
331                case 'u':
332                        unicodeConsole = true;
333                        break;
334#endif
335
336#ifdef HAVE_LIBREADLINE
337                case 'E':
338                        useReadline = false;
339                        break;
340#endif
341               
342                case '?':
343                default:
344                        PrintUsageAndExit();
345                }
346        }
347        // Adjust arguments
348        argc -= optind;
349        argv += optind;
350       
351        Logging::SetGlobalLevel((Log::Level)masterLevel);
352
353        std::auto_ptr<FileLogger> fileLogger;
354        if (fileLogLevel != Log::INVALID)
355        {
356                fileLogger.reset(new FileLogger(fileLogFile, fileLogLevel));
357        }
358
359        bool quiet = false;
360        if (masterLevel < Log::NOTICE)
361        {
362                // Quiet mode
363                quiet = true;
364        }
365
366        // Print banner?
367        if(!quiet)
368        {
369                const char *banner = BANNER_TEXT("Backup Query Tool");
370                BOX_NOTICE(banner);
371        }
372
373#ifdef WIN32
374        if (unicodeConsole)
375        {
376                if (!SetConsoleCP(CP_UTF8))
377                {
378                        BOX_ERROR("Failed to set input codepage: " <<
379                                GetErrorMessage(GetLastError()));
380                }
381
382                if (!SetConsoleOutputCP(CP_UTF8))
383                {
384                        BOX_ERROR("Failed to set output codepage: " <<
385                                GetErrorMessage(GetLastError()));
386                }
387
388                // enable input of Unicode characters
389                if (_fileno(stdin) != -1 &&
390                        _setmode(_fileno(stdin), _O_TEXT) == -1)
391                {
392                        perror("Failed to set the console input to "
393                                "binary mode");
394                }
395        }
396#endif // WIN32
397
398        // Read in the configuration file
399        if(!quiet) BOX_INFO("Using configuration file " << configFilename);
400
401        std::string errs;
402        std::auto_ptr<Configuration> config(
403                Configuration::LoadAndVerify
404                        (configFilename, &BackupDaemonConfigVerify, errs));
405
406        if(config.get() == 0 || !errs.empty())
407        {
408                BOX_FATAL("Invalid configuration file: " << errs);
409                return 1;
410        }
411        // Easier coding
412        const Configuration &conf(*config);
413       
414        // Setup and connect
415        // 1. TLS context
416        SSLLib::Initialise();
417        // Read in the certificates creating a TLS context
418        TLSContext tlsContext;
419        std::string certFile(conf.GetKeyValue("CertificateFile"));
420        std::string keyFile(conf.GetKeyValue("PrivateKeyFile"));
421        std::string caFile(conf.GetKeyValue("TrustedCAsFile"));
422        tlsContext.Initialise(false /* as client */, certFile.c_str(), keyFile.c_str(), caFile.c_str());
423       
424        // Initialise keys
425        BackupClientCryptoKeys_Setup(conf.GetKeyValue("KeysFile").c_str());
426
427        // 2. Connect to server
428        if(!quiet) BOX_INFO("Connecting to store...");
429        SocketStreamTLS socket;
430        socket.Open(tlsContext, Socket::TypeINET,
431                conf.GetKeyValue("StoreHostname").c_str(),
432                conf.GetKeyValueInt("StorePort"));
433       
434        // 3. Make a protocol, and handshake
435        if(!quiet) BOX_INFO("Handshake with store...");
436        std::auto_ptr<BackupProtocolClient>
437                apConnection(new BackupProtocolClient(socket));
438        BackupProtocolClient& connection(*(apConnection.get()));
439        connection.Handshake();
440       
441        // logging?
442        if(logFile != 0)
443        {
444                connection.SetLogToFile(logFile);
445        }
446       
447        // 4. Log in to server
448        if(!quiet) BOX_INFO("Login to store...");
449        // Check the version of the server
450        {
451                std::auto_ptr<BackupProtocolVersion> serverVersion(connection.QueryVersion(BACKUP_STORE_SERVER_VERSION));
452                if(serverVersion->GetVersion() != BACKUP_STORE_SERVER_VERSION)
453                {
454                        THROW_EXCEPTION(BackupStoreException, WrongServerVersion)
455                }
456        }
457        // Login -- if this fails, the Protocol will exception
458        connection.QueryLogin(conf.GetKeyValueUint32("AccountNumber"),
459                (readWrite)?0:(BackupProtocolLogin::Flags_ReadOnly));
460
461        // 5. Tell user.
462        if(!quiet) printf("Login complete.\n\nType \"help\" for a list of commands.\n\n");
463       
464        // Set up a context for our work
465        BackupQueries context(connection, conf, readWrite);
466       
467        // Start running commands... first from the command line
468        {
469                int c = 0;
470                while(c < argc && !context.Stop())
471                {
472                        BackupQueries::ParsedCommand cmd(argv[c++], true);
473                        context.DoCommand(cmd);
474                }
475        }
476       
477        // Get commands from input
478
479#ifdef HAVE_LIBREADLINE
480        if (useReadline)
481        {
482#else
483        if (false)
484        {
485#endif
486                #ifdef HAVE_LIBREADLINE
487                // Must initialise the locale before using editline's
488                // readline(), otherwise cannot enter international characters.
489                if (setlocale(LC_ALL, "") == NULL)
490                {
491                        BOX_ERROR("Failed to initialise locale. International "
492                                "character support may not work.");
493                }
494
495                #ifdef HAVE_READLINE_HISTORY
496                        using_history();
497                #endif
498
499                /* Allow conditional parsing of the ~/.inputrc file. */
500                rl_readline_name = strdup("bbackupquery");
501
502                /* Tell the completer that we want a crack first. */
503                rl_attempted_completion_function = bbackupquery_completion;
504               
505                pProtocol = &connection;
506                pConfig = &conf;
507                pQueries = &context;
508
509                char *last_cmd = 0;
510                while(!context.Stop())
511                {
512                        char *command = readline("query > ");
513                       
514                        if(command == NULL)
515                        {
516                                // Ctrl-D pressed -- terminate now
517                                break;
518                        }
519                       
520                        BackupQueries::ParsedCommand cmd(command, false);
521                        context.DoCommand(cmd);
522
523                        if(last_cmd != 0 && ::strcmp(last_cmd, command) == 0)
524                        {
525                                free(command);
526                        }
527                        else
528                        {
529                                #ifdef HAVE_READLINE_HISTORY
530                                        add_history(command);
531                                #else
532                                        free(last_cmd);
533                                #endif
534                                last_cmd = command;
535                        }
536                }
537                #ifndef HAVE_READLINE_HISTORY
538                        free(last_cmd);
539                        last_cmd = 0;
540                #endif
541                #endif // HAVE_READLINE
542        }
543        else // !HAVE_LIBREADLINE || !useReadline
544        {
545                // Version for platforms which don't have readline by default
546                if(fileno(stdin) >= 0)
547                {
548                        FdGetLine getLine(fileno(stdin));
549                        while(!context.Stop())
550                        {
551                                printf("query > ");
552                                fflush(stdout);
553                                std::string command(getLine.GetLine());
554                                BackupQueries::ParsedCommand cmd(command,
555                                        false);
556                                context.DoCommand(cmd);
557                        }
558                }
559        }
560       
561        // Done... stop nicely
562        if(!quiet) BOX_INFO("Logging off...");
563        connection.QueryFinished();
564        if(!quiet) BOX_INFO("Session finished.");
565       
566        // Return code
567        returnCode = context.GetReturnCode();
568       
569        // Close log file?
570        if(logFile)
571        {
572                ::fclose(logFile);
573        }
574       
575        // Let everything be cleaned up on exit.
576       
577#ifdef WIN32
578        // Clean up our sockets
579        // FIXME we should do this, but I get an abort() when I try
580        // WSACleanup();
581#endif
582
583        MAINHELPER_END
584       
585        return returnCode;
586}
587
Note: See TracBrowser for help on using the repository browser.