source: box/trunk/bin/bbstoreaccounts/bbstoreaccounts.cpp @ 2897

Revision 2897, 18.8 KB checked in by chris, 14 months ago (diff)

Fix compile of bbstoreaccounts on MSVC.

  • Property svn:eol-style set to native
Line 
1// --------------------------------------------------------------------------
2//
3// File
4//              Name:    bbstoreaccounts
5//              Purpose: backup store administration tool
6//              Created: 2003/08/20
7//
8// --------------------------------------------------------------------------
9
10#include "Box.h"
11
12#include <limits.h>
13#include <stdio.h>
14
15#ifdef HAVE_UNISTD_H
16#       include <unistd.h>
17#endif
18
19#include <sys/types.h>
20
21#include <algorithm>
22#include <cstring>
23#include <iostream>
24#include <ostream>
25#include <vector>
26
27#include "BoxPortsAndFiles.h"
28#include "BackupStoreConfigVerify.h"
29#include "RaidFileController.h"
30#include "BackupStoreAccounts.h"
31#include "BackupStoreAccountDatabase.h"
32#include "MainHelper.h"
33#include "BackupStoreInfo.h"
34#include "StoreStructure.h"
35#include "NamedLock.h"
36#include "UnixUser.h"
37#include "BackupStoreCheck.h"
38#include "Utils.h"
39
40#include "MemLeakFindOn.h"
41
42#include <cstring>
43
44// max size of soft limit as percent of hard limit
45#define MAX_SOFT_LIMIT_SIZE             97
46
47bool sMachineReadableOutput = false;
48
49void CheckSoftHardLimits(int64_t SoftLimit, int64_t HardLimit)
50{
51        if(SoftLimit > HardLimit)
52        {
53                BOX_FATAL("Soft limit must be less than the hard limit.");
54                exit(1);
55        }
56        if(SoftLimit > ((HardLimit * MAX_SOFT_LIMIT_SIZE) / 100))
57        {
58                BOX_WARNING("We recommend setting the soft limit below " <<
59                        MAX_SOFT_LIMIT_SIZE << "% of the hard limit, or " <<
60                        HumanReadableSize((HardLimit * MAX_SOFT_LIMIT_SIZE)
61                                / 100) << " in this case.");
62        }
63}
64
65int BlockSizeOfDiscSet(int DiscSet)
66{
67        // Get controller, check disc set number
68        RaidFileController &controller(RaidFileController::GetController());
69        if(DiscSet < 0 || DiscSet >= controller.GetNumDiscSets())
70        {
71                BOX_FATAL("Disc set " << DiscSet << " does not exist.");
72                exit(1);
73        }
74       
75        // Return block size
76        return controller.GetDiscSet(DiscSet).GetBlockSize();
77}
78
79std::string BlockSizeToString(int64_t Blocks, int64_t MaxBlocks, int DiscSet)
80{
81        return FormatUsageBar(Blocks, Blocks * BlockSizeOfDiscSet(DiscSet),
82                MaxBlocks * BlockSizeOfDiscSet(DiscSet),
83                sMachineReadableOutput);
84}
85
86int64_t SizeStringToBlocks(const char *string, int DiscSet)
87{
88        // Find block size
89        int blockSize = BlockSizeOfDiscSet(DiscSet);
90       
91        // Get number
92        char *endptr = (char*)string;
93        int64_t number = strtol(string, &endptr, 0);
94        if(endptr == string || number == LONG_MIN || number == LONG_MAX)
95        {
96                BOX_FATAL("'" << string << "' is not a valid number.");
97                exit(1);
98        }
99       
100        // Check units
101        switch(*endptr)
102        {
103        case 'M':
104        case 'm':
105                // Units: Mb
106                return (number * 1024*1024) / blockSize;
107                break;
108               
109        case 'G':
110        case 'g':
111                // Units: Gb
112                return (number * 1024*1024*1024) / blockSize;
113                break;
114               
115        case 'B':
116        case 'b':
117                // Units: Blocks
118                // Easy! Just return the number specified.
119                return number;
120                break;
121       
122        default:
123                BOX_FATAL(string << " has an invalid units specifier "
124                        "(use B for blocks, M for MB, G for GB, eg 2GB)");
125                exit(1);
126                break;         
127        }
128}
129
130bool GetWriteLockOnAccount(NamedLock &rLock, const std::string rRootDir, int DiscSetNum)
131{
132        std::string writeLockFilename;
133        StoreStructure::MakeWriteLockFilename(rRootDir, DiscSetNum, writeLockFilename);
134
135        bool gotLock = false;
136        int triesLeft = 8;
137        do
138        {
139                gotLock = rLock.TryAndGetLock(writeLockFilename.c_str(), 0600 /* restrictive file permissions */);
140               
141                if(!gotLock)
142                {
143                        --triesLeft;
144                        ::sleep(1);
145                }
146        } while(!gotLock && triesLeft > 0);
147
148        if(!gotLock)
149        {
150                // Couldn't lock the account -- just stop now
151                BOX_ERROR("Failed to lock the account, did not change limits. "
152                        "Try again later.");
153        }
154
155        return gotLock;
156}
157
158int SetLimit(Configuration &rConfig, const std::string &rUsername, int32_t ID, const char *SoftLimitStr, const char *HardLimitStr)
159{
160        // Become the user specified in the config file?
161        std::auto_ptr<UnixUser> user;
162        if(!rUsername.empty())
163        {
164                // Username specified, change...
165                user.reset(new UnixUser(rUsername.c_str()));
166                user->ChangeProcessUser(true /* temporary */);
167                // Change will be undone at the end of this function
168        }
169
170        // Load in the account database
171        std::auto_ptr<BackupStoreAccountDatabase> db(BackupStoreAccountDatabase::Read(rConfig.GetKeyValue("AccountDatabase").c_str()));
172       
173        // Already exists?
174        if(!db->EntryExists(ID))
175        {
176                BOX_ERROR("Account " << BOX_FORMAT_ACCOUNT(ID) << 
177                        " does not exist.");
178                return 1;
179        }
180       
181        // Load it in
182        BackupStoreAccounts acc(*db);
183        std::string rootDir;
184        int discSet;
185        acc.GetAccountRoot(ID, rootDir, discSet);
186       
187        // Attempt to lock
188        NamedLock writeLock;
189        if(!GetWriteLockOnAccount(writeLock, rootDir, discSet))
190        {
191                // Failed to get lock
192                return 1;
193        }
194
195        // Load the info
196        std::auto_ptr<BackupStoreInfo> info(BackupStoreInfo::Load(ID, rootDir, discSet, false /* Read/Write */));
197
198        // Change the limits
199        int64_t softlimit = SizeStringToBlocks(SoftLimitStr, discSet);
200        int64_t hardlimit = SizeStringToBlocks(HardLimitStr, discSet);
201        CheckSoftHardLimits(softlimit, hardlimit);
202        info->ChangeLimits(softlimit, hardlimit);
203       
204        // Save
205        info->Save();
206
207        BOX_NOTICE("Limits on account " << BOX_FORMAT_ACCOUNT(ID) <<
208                " changed to " << softlimit << " soft, " <<
209                hardlimit << " hard.");
210
211        return 0;
212}
213
214int SetAccountName(Configuration &rConfig, const std::string &rUsername,
215        int32_t ID, const std::string& rNewAccountName)
216{
217        // Become the user specified in the config file?
218        std::auto_ptr<UnixUser> user;
219        if(!rUsername.empty())
220        {
221                // Username specified, change...
222                user.reset(new UnixUser(rUsername.c_str()));
223                user->ChangeProcessUser(true /* temporary */);
224                // Change will be undone at the end of this function
225        }
226
227        // Load in the account database
228        std::auto_ptr<BackupStoreAccountDatabase> db(BackupStoreAccountDatabase::Read(rConfig.GetKeyValue("AccountDatabase").c_str()));
229       
230        // Already exists?
231        if(!db->EntryExists(ID))
232        {
233                BOX_ERROR("Account " << BOX_FORMAT_ACCOUNT(ID) << 
234                        " does not exist.");
235                return 1;
236        }
237       
238        // Load it in
239        BackupStoreAccounts acc(*db);
240        std::string rootDir;
241        int discSet;
242        acc.GetAccountRoot(ID, rootDir, discSet);
243       
244        // Attempt to lock
245        NamedLock writeLock;
246        if(!GetWriteLockOnAccount(writeLock, rootDir, discSet))
247        {
248                // Failed to get lock
249                return 1;
250        }
251
252        // Load the info
253        std::auto_ptr<BackupStoreInfo> info(BackupStoreInfo::Load(ID,
254                rootDir, discSet, false /* Read/Write */));
255
256        info->SetAccountName(rNewAccountName);
257       
258        // Save
259        info->Save();
260
261        BOX_NOTICE("Account " << BOX_FORMAT_ACCOUNT(ID) <<
262                " name changed to " << rNewAccountName);
263
264        return 0;
265}
266
267
268int AccountInfo(Configuration &rConfig, int32_t ID)
269{
270        // Load in the account database
271        std::auto_ptr<BackupStoreAccountDatabase> db(
272                BackupStoreAccountDatabase::Read(
273                        rConfig.GetKeyValue("AccountDatabase").c_str()));
274       
275        // Exists?
276        if(!db->EntryExists(ID))
277        {
278                BOX_ERROR("Account " << BOX_FORMAT_ACCOUNT(ID) << 
279                        " does not exist.");
280                return 1;
281        }
282       
283        // Load it in
284        BackupStoreAccounts acc(*db);
285        std::string rootDir;
286        int discSet;
287        acc.GetAccountRoot(ID, rootDir, discSet);
288        std::auto_ptr<BackupStoreInfo> info(BackupStoreInfo::Load(ID,
289                rootDir, discSet, true /* ReadOnly */));
290       
291        // Then print out lots of info
292        std::cout << FormatUsageLineStart("Account ID", sMachineReadableOutput) <<
293                BOX_FORMAT_ACCOUNT(ID) << std::endl;
294        std::cout << FormatUsageLineStart("Account Name", sMachineReadableOutput) <<
295                info->GetAccountName() << std::endl;
296        std::cout << FormatUsageLineStart("Last object ID", sMachineReadableOutput) <<
297                BOX_FORMAT_OBJECTID(info->GetLastObjectIDUsed()) << std::endl;
298        std::cout << FormatUsageLineStart("Used", sMachineReadableOutput) <<
299                BlockSizeToString(info->GetBlocksUsed(),
300                        info->GetBlocksHardLimit(), discSet) << std::endl;
301        std::cout << FormatUsageLineStart("Current files",
302                        sMachineReadableOutput) <<
303                BlockSizeToString(info->GetBlocksInCurrentFiles(),
304                        info->GetBlocksHardLimit(), discSet) << std::endl;
305        std::cout << FormatUsageLineStart("Old files", sMachineReadableOutput) <<
306                BlockSizeToString(info->GetBlocksInOldFiles(),
307                        info->GetBlocksHardLimit(), discSet) << std::endl;
308        std::cout << FormatUsageLineStart("Deleted files", sMachineReadableOutput) <<
309                BlockSizeToString(info->GetBlocksInDeletedFiles(),
310                        info->GetBlocksHardLimit(), discSet) << std::endl;
311        std::cout << FormatUsageLineStart("Directories", sMachineReadableOutput) <<
312                BlockSizeToString(info->GetBlocksInDirectories(),
313                        info->GetBlocksHardLimit(), discSet) << std::endl;
314        std::cout << FormatUsageLineStart("Soft limit", sMachineReadableOutput) <<
315                BlockSizeToString(info->GetBlocksSoftLimit(),
316                        info->GetBlocksHardLimit(), discSet) << std::endl;
317        std::cout << FormatUsageLineStart("Hard limit", sMachineReadableOutput) <<
318                BlockSizeToString(info->GetBlocksHardLimit(),
319                        info->GetBlocksHardLimit(), discSet) << std::endl;
320        std::cout << FormatUsageLineStart("Client store marker", sMachineReadableOutput) <<
321                info->GetLastObjectIDUsed() << std::endl;
322        std::cout << FormatUsageLineStart("Live Files", sMachineReadableOutput) <<
323                info->GetNumFiles() << std::endl;
324        std::cout << FormatUsageLineStart("Old Files", sMachineReadableOutput) <<
325                info->GetNumOldFiles() << std::endl;
326        std::cout << FormatUsageLineStart("Deleted Files", sMachineReadableOutput) <<
327                info->GetNumDeletedFiles() << std::endl;
328        std::cout << FormatUsageLineStart("Directories", sMachineReadableOutput) <<
329                info->GetNumDirectories() << std::endl;
330       
331        return 0;
332}
333
334int DeleteAccount(Configuration &rConfig, const std::string &rUsername, int32_t ID, bool AskForConfirmation)
335{
336        // Check user really wants to do this
337        if(AskForConfirmation)
338        {
339                BOX_WARNING("Really delete account " << 
340                        BOX_FORMAT_ACCOUNT(ID) << "? (type 'yes' to confirm)");
341                char response[256];
342                if(::fgets(response, sizeof(response), stdin) == 0 || ::strcmp(response, "yes\n") != 0)
343                {
344                        BOX_NOTICE("Deletion cancelled.");
345                        return 0;
346                }
347        }
348       
349        // Load in the account database
350        std::auto_ptr<BackupStoreAccountDatabase> db(BackupStoreAccountDatabase::Read(rConfig.GetKeyValue("AccountDatabase").c_str()));
351       
352        // Exists?
353        if(!db->EntryExists(ID))
354        {
355                BOX_ERROR("Account " << BOX_FORMAT_ACCOUNT(ID) << 
356                        " does not exist.");
357                return 1;
358        }
359       
360        // Get info from the database
361        BackupStoreAccounts acc(*db);
362        std::string rootDir;
363        int discSetNum;
364        acc.GetAccountRoot(ID, rootDir, discSetNum);
365       
366        // Obtain a write lock, as the daemon user
367        NamedLock writeLock;
368        {
369                // Bbecome the user specified in the config file
370                std::auto_ptr<UnixUser> user;
371                if(!rUsername.empty())
372                {
373                        // Username specified, change...
374                        user.reset(new UnixUser(rUsername.c_str()));
375                        user->ChangeProcessUser(true /* temporary */);
376                        // Change will be undone at the end of this function
377                }
378       
379                // Get a write lock
380                if(!GetWriteLockOnAccount(writeLock, rootDir, discSetNum))
381                {
382                        // Failed to get lock
383                        return 1;
384                }
385               
386                // Back to original user, but write is maintained
387        }
388
389        // Delete from account database
390        db->DeleteEntry(ID);
391       
392        // Write back to disc
393        db->Write();
394       
395        // Remove the store files...
396
397        // First, become the user specified in the config file
398        std::auto_ptr<UnixUser> user;
399        if(!rUsername.empty())
400        {
401                // Username specified, change...
402                user.reset(new UnixUser(rUsername.c_str()));
403                user->ChangeProcessUser(true /* temporary */);
404                // Change will be undone at the end of this function
405        }
406       
407        // Secondly, work out which directories need wiping
408        std::vector<std::string> toDelete;
409        RaidFileController &rcontroller(RaidFileController::GetController());
410        RaidFileDiscSet discSet(rcontroller.GetDiscSet(discSetNum));
411        for(RaidFileDiscSet::const_iterator i(discSet.begin()); i != discSet.end(); ++i)
412        {
413                if(std::find(toDelete.begin(), toDelete.end(), *i) == toDelete.end())
414                {
415                        toDelete.push_back((*i) + DIRECTORY_SEPARATOR + rootDir);
416                }
417        }
418
419        int retcode = 0;
420
421        // Thirdly, delete the directories...
422        for(std::vector<std::string>::const_iterator d(toDelete.begin()); d != toDelete.end(); ++d)
423        {
424                BOX_NOTICE("Deleting store directory " << (*d) << "...");
425                // Just use the rm command to delete the files
426                std::string cmd("rm -rf ");
427                cmd += *d;
428                // Run command
429                if(::system(cmd.c_str()) != 0)
430                {
431                        BOX_ERROR("Failed to delete files in " << (*d) <<
432                                ", delete them manually.");
433                        retcode = 1;
434                }
435        }
436       
437        // Success!
438        return retcode;
439}
440
441int CheckAccount(Configuration &rConfig, const std::string &rUsername, int32_t ID, bool FixErrors, bool Quiet)
442{
443        // Load in the account database
444        std::auto_ptr<BackupStoreAccountDatabase> db(BackupStoreAccountDatabase::Read(rConfig.GetKeyValue("AccountDatabase").c_str()));
445       
446        // Exists?
447        if(!db->EntryExists(ID))
448        {
449                BOX_ERROR("Account " << BOX_FORMAT_ACCOUNT(ID) << 
450                        " does not exist.");
451                return 1;
452        }
453       
454        // Get info from the database
455        BackupStoreAccounts acc(*db);
456        std::string rootDir;
457        int discSetNum;
458        acc.GetAccountRoot(ID, rootDir, discSetNum);
459       
460        // Become the right user
461        std::auto_ptr<UnixUser> user;
462        if(!rUsername.empty())
463        {
464                // Username specified, change...
465                user.reset(new UnixUser(rUsername.c_str()));
466                user->ChangeProcessUser(true /* temporary */);
467                // Change will be undone at the end of this function
468        }
469
470        // Check it
471        BackupStoreCheck check(rootDir, discSetNum, ID, FixErrors, Quiet);
472        check.Check();
473       
474        return check.ErrorsFound()?1:0;
475}
476
477int CreateAccount(Configuration &rConfig, const std::string &rUsername, int32_t ID, int32_t DiscNumber, int32_t SoftLimit, int32_t HardLimit)
478{
479        // Load in the account database
480        std::auto_ptr<BackupStoreAccountDatabase> db(BackupStoreAccountDatabase::Read(rConfig.GetKeyValue("AccountDatabase").c_str()));
481       
482        // Already exists?
483        if(db->EntryExists(ID))
484        {
485                BOX_ERROR("Account " << BOX_FORMAT_ACCOUNT(ID) << 
486                        " already exists.");
487                return 1;
488        }
489       
490        // Create it.
491        BackupStoreAccounts acc(*db);
492        acc.Create(ID, DiscNumber, SoftLimit, HardLimit, rUsername);
493       
494        BOX_NOTICE("Account " << BOX_FORMAT_ACCOUNT(ID) << " created.");
495
496        return 0;
497}
498
499void PrintUsageAndExit()
500{
501        printf(
502"Usage: bbstoreaccounts [-c config_file] action account_id [args]\n"
503"Account ID is integer specified in hex\n"
504"\n"
505"Commands (and arguments):\n"
506"  create <account> <discnum> <softlimit> <hardlimit>\n"
507"        Creates the specified account number (in hex with no 0x) on the\n"
508"        specified raidfile disc set number (see raidfile.conf for valid\n"
509"        set numbers) with the specified soft and hard limits (in blocks\n"
510"        if suffixed with B, MB with M, GB with G)\n"
511"  info [-m] <account>\n"
512"        Prints information about the specified account including number\n"
513"        of blocks used. The -m option enable machine-readable output.\n"
514"  setlimit <accounts> <softlimit> <hardlimit>\n"
515"        Changes the limits of the account as specified. Numbers are\n"
516"        interpreted as for the 'create' command (suffixed with B, M or G)\n"
517"  delete <account> [yes]\n"
518"        Deletes the specified account. Prompts for confirmation unless\n"
519"        the optional 'yes' parameter is provided.\n"
520"  check <account> [fix] [quiet]\n"
521"        Checks the specified account for errors. If the 'fix' option is\n"
522"        provided, any errors discovered that can be fixed automatically\n"
523"        will be fixed. If the 'quiet' option is provided, less output is\n"
524"        produced.\n"
525"  name <account> <new name>\n"
526"        Changes the \"name\" of the account to the specified string.\n"
527"        The name is purely cosmetic and intended to make it easier to\n"
528"        identify your accounts.\n"
529        );
530        exit(2);
531}
532
533int main(int argc, const char *argv[])
534{
535        MAINHELPER_SETUP_MEMORY_LEAK_EXIT_REPORT("bbstoreaccounts.memleaks",
536                "bbstoreaccounts")
537
538        MAINHELPER_START
539
540        Logging::SetProgramName("bbstoreaccounts");
541
542        // Filename for configuration file?
543        std::string configFilename = BOX_GET_DEFAULT_BBACKUPD_CONFIG_FILE;
544        int logLevel = Log::EVERYTHING;
545       
546        // See if there's another entry on the command line
547        int c;
548        while((c = getopt(argc, (char * const *)argv, "c:W:m")) != -1)
549        {
550                switch(c)
551                {
552                case 'c':
553                        // store argument
554                        configFilename = optarg;
555                        break;
556               
557                case 'W':
558                        logLevel = Logging::GetNamedLevel(optarg);
559                        if(logLevel == Log::INVALID)
560                        {
561                                BOX_FATAL("Invalid logging level: " << optarg);
562                                return 2;
563                        }
564                        break;
565
566                case 'm':
567                        // enable machine readable output
568                        sMachineReadableOutput = true;
569                        break;
570
571                case '?':
572                default:
573                        PrintUsageAndExit();
574                }
575        }
576
577        Logging::FilterConsole((Log::Level) logLevel);
578        Logging::FilterSyslog (Log::NOTHING);
579
580        // Adjust arguments
581        argc -= optind;
582        argv += optind;
583
584        // Read in the configuration file
585        std::string errs;
586        std::auto_ptr<Configuration> config(
587                Configuration::LoadAndVerify
588                        (configFilename, &BackupConfigFileVerify, errs));
589
590        if(config.get() == 0 || !errs.empty())
591        {
592                BOX_ERROR("Invalid configuration file " << configFilename <<
593                        ":" << errs);
594        }
595       
596        // Get the user under which the daemon runs
597        std::string username;
598        {
599                const Configuration &rserverConfig(config->GetSubConfiguration("Server"));
600                if(rserverConfig.KeyExists("User"))
601                {
602                        username = rserverConfig.GetKeyValue("User");
603                }
604        }
605       
606        // Initialise the raid file controller
607        RaidFileController &rcontroller(RaidFileController::GetController());
608        rcontroller.Initialise(config->GetKeyValue("RaidFileConf").c_str());
609
610        // Then... check we have two arguments
611        if(argc < 2)
612        {
613                PrintUsageAndExit();
614        }
615       
616        // Get the id
617        int32_t id;
618        if(::sscanf(argv[1], "%x", &id) != 1)
619        {
620                PrintUsageAndExit();
621        }
622       
623        // Now do the command.
624        if(::strcmp(argv[0], "create") == 0)
625        {
626                // which disc?
627                int32_t discnum;
628                int32_t softlimit;
629                int32_t hardlimit;
630                if(argc < 5
631                        || ::sscanf(argv[2], "%d", &discnum) != 1)
632                {
633                        BOX_ERROR("create requires raid file disc number, "
634                                "soft and hard limits.");
635                        return 1;
636                }
637               
638                // Decode limits
639                softlimit = SizeStringToBlocks(argv[3], discnum);
640                hardlimit = SizeStringToBlocks(argv[4], discnum);
641                CheckSoftHardLimits(softlimit, hardlimit);
642       
643                // Create the account...
644                return CreateAccount(*config, username, id, discnum, softlimit, hardlimit);
645        }
646        else if(::strcmp(argv[0], "info") == 0)
647        {
648                // Print information on this account
649                return AccountInfo(*config, id);
650        }
651        else if(::strcmp(argv[0], "setlimit") == 0)
652        {
653                // Change the limits on this account
654                if(argc < 4)
655                {
656                        BOX_ERROR("setlimit requires soft and hard limits.");
657                        return 1;
658                }
659               
660                return SetLimit(*config, username, id, argv[2], argv[3]);
661        }
662        else if(::strcmp(argv[0], "name") == 0)
663        {
664                // Change the limits on this account
665                if(argc != 3)
666                {
667                        BOX_ERROR("name command requires a new name.");
668                        return 1;
669                }
670               
671                return SetAccountName(*config, username, id, argv[2]);
672        }
673        else if(::strcmp(argv[0], "delete") == 0)
674        {
675                // Delete an account
676                bool askForConfirmation = true;
677                if(argc >= 3 && (::strcmp(argv[2], "yes") == 0))
678                {
679                        askForConfirmation = false;
680                }
681                return DeleteAccount(*config, username, id, askForConfirmation);
682        }
683        else if(::strcmp(argv[0], "check") == 0)
684        {
685                bool fixErrors = false;
686                bool quiet = false;
687               
688                // Look at other options
689                for(int o = 2; o < argc; ++o)
690                {
691                        if(::strcmp(argv[o], "fix") == 0)
692                        {
693                                fixErrors = true;
694                        }
695                        else if(::strcmp(argv[o], "quiet") == 0)
696                        {
697                                quiet = true;
698                        }
699                        else
700                        {
701                                BOX_ERROR("Unknown option " << argv[o] << ".");
702                                return 2;
703                        }
704                }
705       
706                // Check the account
707                return CheckAccount(*config, username, id, fixErrors, quiet);
708        }
709        else
710        {
711                BOX_ERROR("Unknown command '" << argv[0] << "'.");
712                return 1;
713        }
714
715        return 0;
716       
717        MAINHELPER_END
718}
719
720
Note: See TracBrowser for help on using the repository browser.