LogCabin
|
00001 /* Copyright (c) 2012 Stanford University 00002 * Copyright (c) 2014 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 <getopt.h> 00018 #include <unistd.h> 00019 00020 #include <iostream> 00021 #include <string> 00022 00023 #include "Core/Debug.h" 00024 #include "Core/StringUtil.h" 00025 #include "Core/ThreadId.h" 00026 #include "Core/Util.h" 00027 #include "Server/Globals.h" 00028 #include "Server/RaftConsensus.h" 00029 00030 namespace { 00031 00032 /** 00033 * Parses argv for the main function. 00034 */ 00035 class OptionParser { 00036 public: 00037 OptionParser(int& argc, char**& argv) 00038 : argc(argc) 00039 , argv(argv) 00040 , bootstrap(false) 00041 , configFilename("logcabin.conf") 00042 , daemon(false) 00043 , debugLogFilename() // empty for default 00044 , pidFilename() // empty for none 00045 , testConfig(false) 00046 { 00047 while (true) { 00048 static struct option longOptions[] = { 00049 {"bootstrap", no_argument, NULL, 'b'}, 00050 {"config", required_argument, NULL, 'c'}, 00051 {"daemon", no_argument, NULL, 'd'}, 00052 {"help", no_argument, NULL, 'h'}, 00053 {"log", required_argument, NULL, 'l'}, 00054 {"pidfile", required_argument, NULL, 'p'}, 00055 {"test", no_argument, NULL, 't'}, 00056 {0, 0, 0, 0} 00057 }; 00058 int c = getopt_long(argc, argv, "bc:dhl:p:t", longOptions, NULL); 00059 00060 // Detect the end of the options. 00061 if (c == -1) 00062 break; 00063 00064 switch (c) { 00065 case 'h': 00066 usage(); 00067 exit(0); 00068 case 'b': 00069 bootstrap = true; 00070 break; 00071 case 'c': 00072 configFilename = optarg; 00073 break; 00074 case 'd': 00075 daemon = true; 00076 break; 00077 case 'l': 00078 debugLogFilename = optarg; 00079 break; 00080 case 'p': 00081 pidFilename = optarg; 00082 break; 00083 case 't': 00084 testConfig = true; 00085 break; 00086 case '?': 00087 default: 00088 // getopt_long already printed an error message. 00089 usage(); 00090 exit(1); 00091 } 00092 } 00093 00094 // We don't expect any additional command line arguments (not options). 00095 if (optind != argc) { 00096 usage(); 00097 exit(1); 00098 } 00099 } 00100 00101 void usage() { 00102 std::cout 00103 << "Runs a LogCabin server." 00104 << std::endl 00105 << std::endl 00106 << "This program was released in LogCabin v1.0.0." 00107 << std::endl 00108 << std::endl 00109 00110 << "Usage: " << argv[0] << " [options]" 00111 << std::endl 00112 << std::endl 00113 00114 << "Options:" 00115 << std::endl 00116 00117 << " --bootstrap " 00118 << "Write a cluster configuration into the very" 00119 << std::endl 00120 << " " 00121 << "first server's log and exit. This must only" 00122 << std::endl 00123 << " " 00124 << "be run once on a single server in each cluster." 00125 << std::endl 00126 00127 << " -c <file>, --config=<file> " 00128 << "Set the path to the configuration file" 00129 << std::endl 00130 << " " 00131 << "[default: logcabin.conf]" 00132 << std::endl 00133 00134 << " -d, --daemon " 00135 << "Detach and run in the background" 00136 << std::endl 00137 << " " 00138 << "(requires --log)" 00139 << std::endl 00140 00141 << " -h, --help " 00142 << "Print this usage information" 00143 << std::endl 00144 00145 << " -l <file>, --log=<file> " 00146 << "Write debug logs to <file> instead of stderr" 00147 << std::endl 00148 00149 << " -p <file>, --pidfile=<file> " 00150 << "Write process ID to <file>" 00151 << std::endl 00152 00153 << " -t, --test " 00154 << "Check the configuration file for basic errors" 00155 << std::endl 00156 << " " 00157 << "and exit" 00158 << std::endl 00159 << std::endl 00160 00161 << "Signals:" 00162 << std::endl 00163 00164 << " SIGUSR1 " 00165 << "Dump ServerStats to debug log (experimental)" 00166 << std::endl 00167 00168 << " SIGUSR2 " 00169 << "Reopen the debug log file" 00170 << std::endl; 00171 } 00172 00173 int& argc; 00174 char**& argv; 00175 bool bootstrap; 00176 std::string configFilename; 00177 bool daemon; 00178 std::string debugLogFilename; 00179 std::string pidFilename; 00180 bool testConfig; 00181 }; 00182 00183 /** 00184 * RAII-style class to manage a file containing the process ID. 00185 */ 00186 class PidFile { 00187 public: 00188 explicit PidFile(const std::string& filename) 00189 : filename(filename) 00190 , written(-1) 00191 { 00192 } 00193 00194 ~PidFile() { 00195 removeFile(); 00196 } 00197 00198 void writePid(int pid) { 00199 if (filename.empty()) 00200 return; 00201 FILE* file = fopen(filename.c_str(), "w"); 00202 if (file == NULL) { 00203 PANIC("Could not open %s for writing process ID: %s", 00204 filename.c_str(), 00205 strerror(errno)); 00206 } 00207 std::string pidString = 00208 LogCabin::Core::StringUtil::format("%d\n", pid); 00209 size_t bytesWritten = 00210 fwrite(pidString.c_str(), 1, pidString.size(), file); 00211 if (bytesWritten != pidString.size()) { 00212 PANIC("Could not write process ID %s to pidfile %s: %s", 00213 pidString.c_str(), filename.c_str(), 00214 strerror(errno)); 00215 } 00216 int r = fclose(file); 00217 if (r != 0) { 00218 PANIC("Could not close pidfile %s: %s", 00219 filename.c_str(), 00220 strerror(errno)); 00221 } 00222 NOTICE("Wrote PID %d to %s", 00223 pid, filename.c_str()); 00224 written = pid; 00225 } 00226 00227 void removeFile() { 00228 if (written < 0) 00229 return; 00230 FILE* file = fopen(filename.c_str(), "r"); 00231 if (file == NULL) { 00232 WARNING("Could not open %s for reading process ID prior to " 00233 "removal: %s", 00234 filename.c_str(), 00235 strerror(errno)); 00236 return; 00237 } 00238 char readbuf[10]; 00239 memset(readbuf, 0, sizeof(readbuf)); 00240 size_t bytesRead = fread(readbuf, 1, sizeof(readbuf), file); 00241 if (bytesRead == 0) { 00242 WARNING("PID could not be read from pidfile: " 00243 "will not remove file %s", 00244 filename.c_str()); 00245 fclose(file); 00246 return; 00247 } 00248 int pidRead = atoi(readbuf); 00249 if (pidRead != written) { 00250 WARNING("PID read from pidfile (%d) does not match PID written " 00251 "earlier (%d): will not remove file %s", 00252 pidRead, written, filename.c_str()); 00253 fclose(file); 00254 return; 00255 } 00256 int r = unlink(filename.c_str()); 00257 if (r != 0) { 00258 WARNING("Could not unlink %s: %s", 00259 filename.c_str(), strerror(errno)); 00260 fclose(file); 00261 return; 00262 } 00263 written = -1; 00264 fclose(file); 00265 NOTICE("Removed pidfile %s", filename.c_str()); 00266 } 00267 00268 std::string filename; 00269 int written; 00270 }; 00271 00272 } // anonymous namespace 00273 00274 int 00275 main(int argc, char** argv) 00276 { 00277 using namespace LogCabin; 00278 00279 try { 00280 Core::ThreadId::setName("evloop"); 00281 00282 // Parse command line args. 00283 OptionParser options(argc, argv); 00284 00285 if (options.testConfig) { 00286 Server::Globals globals; 00287 globals.config.readFile(options.configFilename.c_str()); 00288 // The following settings are required, and Config::read() throws 00289 // an exception with an OK error message if they aren't found: 00290 globals.config.read<uint64_t>("serverId"); 00291 globals.config.read<std::string>("listenAddresses"); 00292 return 0; 00293 } 00294 00295 // Set debug log file 00296 if (!options.debugLogFilename.empty()) { 00297 std::string error = 00298 Core::Debug::setLogFilename(options.debugLogFilename); 00299 if (!error.empty()) { 00300 ERROR("Failed to set debug log file: %s", 00301 error.c_str()); 00302 } 00303 } 00304 00305 NOTICE("Using config file %s", options.configFilename.c_str()); 00306 00307 // Detach as daemon 00308 if (options.daemon) { 00309 if (options.debugLogFilename.empty()) { 00310 ERROR("Refusing to run as daemon without a log file " 00311 "(use /dev/null if you insist)"); 00312 } 00313 NOTICE("Detaching"); 00314 bool chdir = false; // leave the current working directory in case 00315 // the user has specified relative paths for 00316 // the config file, etc 00317 bool close = true; // close stdin, stdout, stderr 00318 if (daemon(!chdir, !close) != 0) { 00319 PANIC("Call to daemon() failed: %s", strerror(errno)); 00320 } 00321 int pid = getpid(); 00322 Core::Debug::processName = Core::StringUtil::format("%d", pid); 00323 NOTICE("Detached as daemon with pid %d", pid); 00324 } 00325 00326 // Write PID file, removed upon destruction 00327 PidFile pidFile(options.pidFilename); 00328 pidFile.writePid(getpid()); 00329 00330 { 00331 // Initialize and run Globals. 00332 Server::Globals globals; 00333 globals.config.readFile(options.configFilename.c_str()); 00334 00335 // Set debug log policy. 00336 // A few log messages above already got through; oh well. 00337 Core::Debug::setLogPolicy( 00338 Core::Debug::logPolicyFromString( 00339 globals.config.read<std::string>("logPolicy", "NOTICE"))); 00340 00341 NOTICE("Config file settings:\n" 00342 "# begin config\n" 00343 "%s" 00344 "# end config", 00345 Core::StringUtil::toString(globals.config).c_str()); 00346 globals.init(); 00347 if (options.bootstrap) { 00348 globals.raft->bootstrapConfiguration(); 00349 NOTICE("Done bootstrapping configuration. Exiting."); 00350 } else { 00351 globals.leaveSignalsBlocked(); 00352 globals.run(); 00353 } 00354 } 00355 00356 google::protobuf::ShutdownProtobufLibrary(); 00357 return 0; 00358 00359 } catch (const Core::Config::Exception& e) { 00360 ERROR("Fatal exception from config file: %s", 00361 e.what()); 00362 } 00363 }