LogCabin
|
00001 /* Copyright (c) 2012 Stanford University 00002 * Copyright (c) 2015 Diego Ongaro 00003 * 00004 * Permission to use, copy, modify, and distribute this software for any 00005 * purpose with or without fee is hereby granted, provided that the above 00006 * copyright notice and this permission notice appear in all copies. 00007 * 00008 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR(S) DISCLAIM ALL WARRANTIES 00009 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 00010 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL AUTHORS BE LIABLE FOR 00011 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 00012 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 00013 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 00014 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 00015 */ 00016 00017 #include <cassert> 00018 #include <getopt.h> 00019 #include <iostream> 00020 #include <iterator> 00021 #include <sstream> 00022 00023 #include <LogCabin/Client.h> 00024 #include <LogCabin/Debug.h> 00025 #include <LogCabin/Util.h> 00026 00027 namespace { 00028 00029 using LogCabin::Client::Cluster; 00030 using LogCabin::Client::Tree; 00031 using LogCabin::Client::Util::parseNonNegativeDuration; 00032 00033 enum class Command { 00034 MKDIR, 00035 LIST, 00036 DUMP, 00037 RMDIR, 00038 WRITE, 00039 READ, 00040 REMOVE, 00041 }; 00042 00043 /** 00044 * Parses argv for the main function. 00045 */ 00046 class OptionParser { 00047 public: 00048 OptionParser(int& argc, char**& argv) 00049 : argc(argc) 00050 , argv(argv) 00051 , cluster("logcabin:5254") 00052 , command() 00053 , condition() 00054 , dir() 00055 , logPolicy("") 00056 , path() 00057 , timeout(parseNonNegativeDuration("0s")) 00058 { 00059 while (true) { 00060 static struct option longOptions[] = { 00061 {"cluster", required_argument, NULL, 'c'}, 00062 {"dir", required_argument, NULL, 'd'}, 00063 {"help", no_argument, NULL, 'h'}, 00064 {"condition", required_argument, NULL, 'p'}, 00065 {"quiet", no_argument, NULL, 'q'}, 00066 {"timeout", required_argument, NULL, 't'}, 00067 {"verbose", no_argument, NULL, 'v'}, 00068 {"verbosity", required_argument, NULL, 256}, 00069 {0, 0, 0, 0} 00070 }; 00071 int c = getopt_long(argc, argv, "c:d:p:t:hqv", longOptions, NULL); 00072 00073 // Detect the end of the options. 00074 if (c == -1) 00075 break; 00076 00077 switch (c) { 00078 case 'c': 00079 cluster = optarg; 00080 break; 00081 case 'd': 00082 dir = optarg; 00083 break; 00084 case 'h': 00085 usage(); 00086 exit(0); 00087 case 'p': { 00088 std::istringstream stream(optarg); 00089 std::string path; 00090 std::string value; 00091 std::getline(stream, path, ':'); 00092 std::getline(stream, value); 00093 condition = {path, value}; 00094 break; 00095 } 00096 case 'q': 00097 logPolicy = "WARNING"; 00098 break; 00099 case 't': 00100 timeout = parseNonNegativeDuration(optarg); 00101 break; 00102 case 'v': 00103 logPolicy = "VERBOSE"; 00104 break; 00105 case 256: 00106 logPolicy = optarg; 00107 break; 00108 case '?': 00109 default: 00110 // getopt_long already printed an error message. 00111 usage(); 00112 exit(1); 00113 } 00114 } 00115 00116 // Additional command line arguments are required. 00117 if (optind == argc) { 00118 usage(); 00119 exit(1); 00120 } 00121 00122 std::string cmdStr = argv[optind]; 00123 ++optind; 00124 if (cmdStr == "mkdir") { 00125 command = Command::MKDIR; 00126 } else if (cmdStr == "list" || cmdStr == "ls") { 00127 command = Command::LIST; 00128 } else if (cmdStr == "dump") { 00129 command = Command::DUMP; 00130 path = "/"; 00131 } else if (cmdStr == "rmdir" || cmdStr == "removeDir") { 00132 command = Command::RMDIR; 00133 } else if (cmdStr == "write" || cmdStr == "create" || 00134 cmdStr == "set") { 00135 command = Command::WRITE; 00136 } else if (cmdStr == "read" || cmdStr == "get") { 00137 command = Command::READ; 00138 } else if (cmdStr == "remove" || cmdStr == "rm" || 00139 cmdStr == "removeFile") { 00140 command = Command::REMOVE; 00141 } else { 00142 std::cout << "Unknown command: " << cmdStr << std::endl; 00143 usage(); 00144 exit(1); 00145 } 00146 00147 if (optind < argc) { 00148 path = argv[optind]; 00149 ++optind; 00150 } 00151 00152 if (path.empty()) { 00153 std::cout << "No path given" << std::endl; 00154 usage(); 00155 exit(1); 00156 } 00157 if (optind < argc) { 00158 std::cout << "Unexpected positional argument: " << argv[optind] 00159 << std::endl; 00160 usage(); 00161 exit(1); 00162 } 00163 } 00164 00165 void usage() { 00166 std::cout << "Run various operations on a LogCabin replicated state " 00167 << "machine." 00168 << std::endl 00169 << std::endl 00170 << "This program was released in LogCabin v1.0.0." 00171 << std::endl; 00172 std::cout << std::endl; 00173 00174 std::cout << "Usage: " << argv[0] << " [options] <command> [<args>]" 00175 << std::endl; 00176 std::cout << std::endl; 00177 00178 std::cout << "Commands:" << std::endl; 00179 std::cout 00180 << " mkdir <path> If no directory exists at <path>, create it." 00181 << std::endl 00182 << " list <path> List keys within directory at <path>. " 00183 << "Alias: ls." 00184 << std::endl 00185 << " dump [<path>] Recursively print keys and values within " 00186 << "directory at <path>." 00187 << std::endl 00188 << " Defaults to printing all keys and values " 00189 << "from root of tree." 00190 << std::endl 00191 << " rmdir <path> Recursively remove directory at <path>, if " 00192 << "any." 00193 << std::endl 00194 << " Alias: removedir." 00195 << std::endl 00196 << " write <path> Set/create value of file at <path> to " 00197 << "stdin." 00198 << std::endl 00199 << " Alias: create, set." 00200 << std::endl 00201 << " read <path> Print value of file at <path>. Alias: get." 00202 << std::endl 00203 << " remove <path> Remove file at <path>, if any. Alias: rm, " 00204 << "removefile." 00205 << std::endl 00206 << std::endl; 00207 00208 std::cout << "Options:" << std::endl; 00209 std::cout 00210 << " -c <addresses>, --cluster=<addresses> " 00211 << "Network addresses of the LogCabin" 00212 << std::endl 00213 << " " 00214 << "servers, comma-separated" 00215 << std::endl 00216 << " " 00217 << "[default: logcabin:5254]" 00218 << std::endl 00219 00220 << " -d <path>, --dir=<path> " 00221 << "Set working directory [default: /]" 00222 << std::endl 00223 00224 << " -h, --help " 00225 << "Print this usage information" 00226 << std::endl 00227 00228 << " -p <pred>, --condition=<pred> " 00229 << "Set predicate on the operation of the" 00230 << std::endl 00231 << " " 00232 << "form <path>:<value>, indicating that the key" 00233 << std::endl 00234 << " " 00235 << "at <path> must have the given value." 00236 << std::endl 00237 00238 << " -q, --quiet " 00239 << "Same as --verbosity=WARNING" 00240 << std::endl 00241 00242 << " -t <time>, --timeout=<time> " 00243 << "Set timeout for the operation" 00244 << std::endl 00245 << " " 00246 << "(0 means wait forever) [default: 0s]" 00247 << std::endl 00248 00249 << " -v, --verbose " 00250 << "Same as --verbosity=VERBOSE (added in v1.1.0)" 00251 << std::endl 00252 00253 << " --verbosity=<policy> " 00254 << "Set which log messages are shown." 00255 << std::endl 00256 << " " 00257 << "Comma-separated LEVEL or PATTERN@LEVEL rules." 00258 << std::endl 00259 << " " 00260 << "Levels: SILENT ERROR WARNING NOTICE VERBOSE." 00261 << std::endl 00262 << " " 00263 << "Patterns match filename prefixes or suffixes." 00264 << std::endl 00265 << " " 00266 << "Example: Client@NOTICE,Test.cc@SILENT,VERBOSE." 00267 << std::endl 00268 << " " 00269 << "(added in v1.1.0)" 00270 << std::endl; 00271 } 00272 00273 int& argc; 00274 char**& argv; 00275 std::string cluster; 00276 Command command; 00277 std::pair<std::string, std::string> condition; 00278 std::string dir; 00279 std::string logPolicy; 00280 std::string path; 00281 uint64_t timeout; 00282 }; 00283 00284 /** 00285 * Depth-first search tree traversal, dumping out contents of all files 00286 */ 00287 void 00288 dumpTree(const Tree& tree, std::string path) 00289 { 00290 std::cout << path << std::endl; 00291 std::vector<std::string> children = tree.listDirectoryEx(path); 00292 for (auto it = children.begin(); it != children.end(); ++it) { 00293 std::string child = path + *it; 00294 if (*child.rbegin() == '/') { // directory 00295 dumpTree(tree, child); 00296 } else { // file 00297 std::cout << child << ": " << std::endl; 00298 std::cout << " " << tree.readEx(child) << std::endl; 00299 } 00300 } 00301 } 00302 00303 std::string 00304 readStdin() 00305 { 00306 std::cin >> std::noskipws; 00307 std::istream_iterator<char> it(std::cin); 00308 std::istream_iterator<char> end; 00309 std::string results(it, end); 00310 return results; 00311 } 00312 00313 } // anonymous namespace 00314 00315 int 00316 main(int argc, char** argv) 00317 { 00318 try { 00319 OptionParser options(argc, argv); 00320 00321 LogCabin::Client::Debug::setLogPolicy( 00322 LogCabin::Client::Debug::logPolicyFromString( 00323 options.logPolicy)); 00324 00325 Cluster cluster(options.cluster); 00326 Tree tree = cluster.getTree(); 00327 00328 if (options.timeout > 0) { 00329 tree.setTimeout(options.timeout); 00330 } 00331 00332 if (!options.dir.empty()) { 00333 tree.setWorkingDirectoryEx(options.dir); 00334 } 00335 00336 if (!options.condition.first.empty()) { 00337 tree.setConditionEx(options.condition.first, 00338 options.condition.second); 00339 } 00340 00341 std::string& path = options.path; 00342 switch (options.command) { 00343 case Command::MKDIR: 00344 tree.makeDirectoryEx(path); 00345 break; 00346 case Command::LIST: { 00347 std::vector<std::string> keys = tree.listDirectoryEx(path); 00348 for (auto it = keys.begin(); it != keys.end(); ++it) 00349 std::cout << *it << std::endl; 00350 break; 00351 } 00352 case Command::DUMP: { 00353 if (path.empty() || path.at(path.size() - 1) != '/') 00354 path.append("/"); 00355 dumpTree(tree, path); 00356 break; 00357 } 00358 case Command::RMDIR: 00359 tree.removeDirectoryEx(path); 00360 break; 00361 case Command::WRITE: 00362 tree.writeEx(path, readStdin()); 00363 break; 00364 case Command::READ: { 00365 std::string contents = tree.readEx(path); 00366 std::cout << contents; 00367 if (contents.empty() || 00368 contents.at(contents.size() - 1) != '\n') { 00369 std::cout << std::endl; 00370 } else { 00371 std::cout.flush(); 00372 } 00373 break; 00374 } 00375 case Command::REMOVE: 00376 tree.removeFileEx(path); 00377 break; 00378 } 00379 return 0; 00380 00381 } catch (const LogCabin::Client::Exception& e) { 00382 std::cerr << "Exiting due to LogCabin::Client::Exception: " 00383 << e.what() 00384 << std::endl; 00385 exit(1); 00386 } 00387 }