Hi everyone! Welcome back to Inside Bitcoin Code.
Today we are going to finish our study of the AppInit()
function; in case you missed the first part, you can find it here.
OK then, fasten your seatbelts and let’s go!
AppInit()
: step-by-step
After what was discussed in the previous episode, the program check is the daemon was passed as a command line argument:
if (args.GetBoolArg("-daemon", DEFAULT_DAEMON) || args.GetBoolArg("-daemonwait", DEFAULT_DAEMONWAIT))
The GetBoolArg()
function returns the requested command line argument or the default value in case it was not defined; if at least one of the conditions is satisfied, the code enters the IF block1.
In case the condition resolves to true, the program enters a block of code that runs only in case the HAVE_DECL_FORK
is defined; if it isn’t, the function returns with an initialization error,ù that says that the daemon is not supported by the operating system:
return InitError(Untranslated("-daemon is not supported on this operating system"));
On the contrary, in case the HAVE_DECL_FORK
is defined the program first logs to the standard output (usually a console) that the PACKAGE_NAME
(Bitcoin Core) is starting
tfm::format(std::cout, PACKAGE_NAME " starting\n");
and then enters a switch
block. This kind of syntax is very similar to an if
clause, and allows the program to switch between different cases:
switch (fork_daemon(1, 0, daemon_ep)) {
case 0:
case -1:
default:
}
In this case, if fork_daemon(1, 0, daemon_ep)
is equal to 0, the program enters the case 0
block, if it is equal to -1 it enters the case -1
block; finally, if the function is neither 0 or -1, the program enters the default
case.
fork_daemon()
function creates a custom implementation of the daemon; it returns:
0 if successful, and in child process
>0 if successful, and in parent process.
-1 in case of error (in parent process).
For case 0:
the daemon is a child process; in case the command line argument “-daemonwait
” was not defined the daemon sends a success token immediately to the parent process.
case 0: // Child: continue.
// If -daemonwait is not enabled, immediately send a success token the parent.
if (!args.GetBoolArg("-daemonwait", DEFAULT_DAEMONWAIT)) {
daemon_ep.TokenWrite(1);
daemon_ep.Close();
}
break;
For case -1:
an initialization error is returned.
case -1: // Error happened.
return InitError(Untranslated(strprintf("fork_daemon() failed: %s", SysErrorString(errno))));
For default:
the process is a parent, the token is read and evaluated; in case it evaluates to TRUE the process exits with success, on the contrary with a failure.
default: { // Parent: wait and exit.
int token = daemon_ep.TokenRead();
if (token) { // Success
exit(EXIT_SUCCESS);
} else { // fRet = false or token read error (premature exit).
tfm::format(std::cerr, "Error during initialization - check debug.log for details\n");
exit(EXIT_FAILURE);
}
Once the daemonization is completed, the code moves forward to the end of the try
block; here, the data directory is locked in order to allow for only a single Bitcoin process to access the data until the program exits. If it fails, the function returns FALSE, otherwise two conditions are checked:
If the chain initializes correctly with the function
AppInitInterfaces()
If the main initialization is done correctly with the function
AppInitMain()
If both conditions evaluates to TRUE, fRet = true
.
// Lock data directory after daemonization
if (!AppInitLockDataDirectory())
{
// If locking the data directory failed, exit immediately
return false;
}
fRet = AppInitInterfaces(node) && AppInitMain(node);
The try
blocks concludes with the catch
blocks; if one of the functions launches an exception, the error is caught by the catch
blocks. There are two blocks:
catch(const std::exception& e)
: this block catches a specific type of exception, which is the standard one.catch(…)
: catches all type of exceptions.
In case one of the two blocks is entered, an exception il logged.
catch (const std::exception& e) {
PrintExceptionContinue(&e, "AppInit()");
} catch (...) {
PrintExceptionContinue(nullptr, "AppInit()");
}
Finally, the opened connection is checked and if everything is ok, a token is sent to the parent process and the pipe is closed.
In the end, fRet
is returned as TRUE in case everything worked smoothly:
#if HAVE_DECL_FORK
if (daemon_ep.IsOpen()) {
// Signal initialization status to parent, then close pipe.
daemon_ep.TokenWrite(fRet);
daemon_ep.Close();
}
#endif
return fRet;
Aaaaaaand we are done with the AppInit()
function! Huge work, but I think it was worth it.
I hope that everything I explained is clear. Otherwise, please leave a comment on this post so that I can try to help you out!
See you soon!
Tuma