// -*- c-basic-offset: 4; tab-width: 8; indent-tabs-mode: t -*-
// Copyright (c) 2001-2007 International Computer Science Institute
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software")
// to deal in the Software without restriction, subject to the conditions
// listed in the XORP LICENSE file. These conditions include: you must
// preserve this copyright notice, and you cannot mention the copyright
// holders in advertising related to the Software without their permission.
// The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This
// notice is a summary of the XORP LICENSE file; the license in that file is
// legally binding.
#ident "$XORP: xorp/rtrmgr/xorpsh_main.cc,v 1.66 2007/02/16 22:47:27 pavlin Exp $"
#include "rtrmgr_module.h"
#include "libxorp/xorp.h"
#include "libxorp/xlog.h"
#include "libxorp/debug.h"
#include "libxorp/utils.hh"
#ifdef HOST_OS_WINDOWS
#include "libxorp/win_io.h"
#endif
#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif
#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif
#ifdef HAVE_PWD_H
#include <pwd.h>
#endif
#include <signal.h>
#include "libcomm/comm_api.h"
#include "cli.hh"
#include "op_commands.hh"
#include "slave_conf_tree.hh"
#include "template_commands.hh"
#include "template_tree.hh"
#include "template_tree_node.hh"
#include "util.hh"
#include "xorpsh_main.hh"
#ifdef HOST_OS_WINDOWS
#define FILENO(x) ((HANDLE)_get_osfhandle(_fileno(x)))
#else
#define FILENO(x) fileno(x)
#endif
//
// Default values
//
static bool default_verbose = false;
//
// Local state
//
static bool is_interrupted = false;
static bool is_user_exited = false;
static bool verbose = default_verbose;
static void signal_handler(int signal_value);
static void exit_handler(CliClient*);
static void
announce_waiting()
{
fprintf(stderr, "Waiting for xorp_rtrmgr...\n");
}
static bool
wait_for_xrl_router_ready(EventLoop& eventloop, XrlRouter& xrl_router)
{
XorpTimer announcer = eventloop.new_oneoff_after_ms(
3 * 1000, callback(&announce_waiting)
);
while (xrl_router.ready() == false) {
eventloop.run();
if (xrl_router.failed()) {
XLOG_ERROR("XrlRouter failed. No Finder?");
return false;
break;
}
}
return true;
}
// the following two functions are an ugly hack to cause the C code in
// the parser to call methods on the right version of the TemplateTree
void
add_cmd_adaptor(char *cmd, TemplateTree* tt) throw (ParseError)
{
tt->add_cmd(cmd);
}
void
add_cmd_action_adaptor(const string& cmd, const list<string>& action,
TemplateTree* tt) throw (ParseError)
{
tt->add_cmd_action(cmd, action);
}
// ----------------------------------------------------------------------------
// XorpShell implementation
XorpShell::XorpShell(EventLoop& eventloop,
const string& IPCname,
const string& xorp_root_dir,
const string& config_template_dir,
const string& xrl_targets_dir,
bool verbose) throw (InitError)
: XrlStdRouter(eventloop, IPCname.c_str()),
_eventloop(eventloop),
_xrl_router(*this),
_xclient(_eventloop, _xrl_router),
_rtrmgr_client(&_xrl_router),
_mmgr(_eventloop),
_is_connected_to_finder(false),
_tt(NULL),
_ct(NULL),
_ocl(NULL),
_cli_node(AF_INET, XORP_MODULE_CLI, _eventloop),
_router_cli(NULL),
_xorp_root_dir(xorp_root_dir),
_verbose(verbose),
_ipc_name(IPCname),
_got_config(false),
_got_modules(false),
_mode(MODE_INITIALIZING),
_xorpsh_interface(&_xrl_router, *this)
{
string error_msg;
//
// Print various information
//
XLOG_TRACE(_verbose, "XORP root directory := %s\n",
xorp_root_dir.c_str());
XLOG_TRACE(_verbose, "Templates directory := %s\n",
config_template_dir.c_str());
XLOG_TRACE(_verbose, "Xrl targets directory := %s\n",
xrl_targets_dir.c_str());
XLOG_TRACE(_verbose, "Print verbose information := %s\n",
_verbose ? "true" : "false");
// Read the router config template files
_tt = new TemplateTree(xorp_root_dir, _verbose);
if (!_tt->load_template_tree(config_template_dir, error_msg)) {
xorp_throw(InitError, error_msg);
}
debug_msg("%s", _tt->tree_str().c_str());
// Read the router operational template files
try {
_ocl = new OpCommandList(config_template_dir.c_str(), _tt, _mmgr);
} catch (const InitError& e) {
xorp_throw(InitError, e.why());
}
}
XorpShell::~XorpShell()
{
if (_ct != NULL)
delete _ct;
if (_tt != NULL)
delete _tt;
if (_ocl != NULL)
delete _ocl;
if (_router_cli != NULL)
delete _router_cli;
#ifndef HOST_OS_WINDOWS
// Close the opened file descriptors
size_t i;
for (i = 0; i < sizeof(_fddesc) / sizeof(_fddesc[0]); i++) {
if (_fddesc[i].is_valid()) {
close(_fddesc[i]);
_fddesc[i].clear();
}
}
#endif // ! HOST_OS_WINDOWS
}
void
XorpShell::run(const string& commands)
{
bool success;
string error_msg;
XorpFd xorpsh_input_fd;
XorpFd xorpsh_output_fd;
XorpFd xorpsh_write_commands_fd;
if (commands.empty()) {
// Accept commands from the stdin
xorpsh_input_fd = FILENO(stdin);
xorpsh_output_fd = FILENO(stdout);
}
#ifndef HOST_OS_WINDOWS
else {
//
// Create an internal pipe to pass the commands to the CLI
//
int pipedesc[2];
if (pipe(pipedesc) != 0) {
error_msg = c_format("Cannot create an internal pipe: %s",
strerror(errno));
xorp_throw(InitError, error_msg);
}
// xorpsh_write_commands_fd = pipedesc[1];
_fddesc[0] = xorpsh_input_fd = pipedesc[0];
_fddesc[1] = xorpsh_write_commands_fd = pipedesc[1];
xorpsh_output_fd = FILENO(stdout);
}
#endif // ! HOST_OS_WINDOWS
// Signal handlers so we can clean up when we're killed
signal(SIGTERM, signal_handler);
signal(SIGINT, signal_handler);
#ifdef SIGPIPE
signal(SIGPIPE, signal_handler);
#endif
// Set the callback when the CLI exits (e.g., after Ctrl-D)
_cli_node.set_cli_client_delete_callback(callback(exit_handler));
_is_connected_to_finder = false;
if (wait_for_xrl_router_ready(_eventloop, _xrl_router) == false) {
// RtrMgr contains finder
error_msg = c_format("Failed to connect to the router manager");
xorp_throw(InitError, error_msg);
}
_is_connected_to_finder = true;
#ifdef HOST_OS_WINDOWS
const uint32_t uid = 0;
#else
const uint32_t uid = getuid();
#endif
success = _rtrmgr_client.send_register_client("rtrmgr", uid, _ipc_name,
callback(this,
&XorpShell::register_done));
if (! success) {
error_msg = c_format("Failed to send a registration request to the "
"router manager");
xorp_throw(InitError, error_msg);
}
_mode = MODE_AUTHENTICATING;
while (_authfile.empty()) {
_eventloop.run();
}
XLOG_TRACE(_verbose, "Registry with rtrmgr successful: token is %s\n",
_authfile.c_str());
#ifdef NO_XORPSH_AUTHENTICATION
// XXX: a hack to access the rtrmgr on a remote machine
_authtoken = _authfile;
#else // ! NO_XORPSH_AUTHENTICATION
FILE* file = fopen(_authfile.c_str(), "r");
if (file == NULL) {
XLOG_FATAL("Failed to open authfile %s", _authfile.c_str());
}
char buf[256];
memset(buf, 0, sizeof(buf));
if (fgets(buf, sizeof(buf) - 1, file) == 0) {
fclose(file);
XLOG_FATAL("Failed to read authfile %s", _authfile.c_str());
}
fclose(file);
#ifdef HOST_OS_WINDOWS
(void)DeleteFileA(_authfile.c_str()); // XXX
#else
if (unlink(_authfile.c_str()) != 0) {
XLOG_WARNING("xorpsh is unable to unlink temporary file %s",
_authfile.c_str());
}
#endif // ! HOST_OS_WINDOWS
_authtoken = buf;
#endif // ! NO_XORPSH_AUTHENTICATION
XLOG_TRACE(_verbose, "authtoken = >%s<\n", _authtoken.c_str());
_xrl_generic_done = false;
success = _rtrmgr_client.send_authenticate_client("rtrmgr", uid, _ipc_name,
_authtoken,
callback(this,
&XorpShell::generic_done));
if (! success) {
error_msg = c_format("Failed to send an authentication request to the "
"router manager");
xorp_throw(InitError, error_msg);
}
while (!_xrl_generic_done) {
_eventloop.run();
}
_mode = MODE_INITIALIZING;
_configuration = "";
_got_config = false;
//
// We wait now to receive the configuration and list of running
// modules from the rtrmgr. We don't have to ask for these - we
// just get them automatically as a result of authenticating.
//
XLOG_TRACE(_verbose, "Waiting to receive configuration from rtrmgr...");
while (_got_config == false) {
_eventloop.run();
}
XLOG_TRACE(_verbose,
"==========================================================\n");
XLOG_TRACE(_verbose, "\n%s", _configuration.c_str());
XLOG_TRACE(_verbose,
"==========================================================\n");
try {
_ct = new SlaveConfigTree(_configuration, _tt, _xclient, _clientid,
_verbose);
_ocl->set_slave_config_tree(_ct);
// Start up the CLI
_cli_node.enable();
_router_cli = new RouterCLI(*this, _cli_node, xorpsh_input_fd,
xorpsh_output_fd, _verbose);
} catch (const InitError& e) {
error_msg = c_format("Shutting down due to a parse error: %s",
e.why().c_str());
_xrl_generic_done = false;
success = _rtrmgr_client.send_unregister_client(
"rtrmgr",
_authtoken,
callback(this, &XorpShell::generic_done));
_mode = MODE_SHUTDOWN;
// Run the event loop to cause the unregister to be sent
while (success && ! _xrl_generic_done) {
_eventloop.run();
}
xorp_throw(InitError, error_msg);
}
//
// Write the commands to one end of the pipe
//
if (xorpsh_write_commands_fd.is_valid()) {
string modified_commands = commands;
if (! modified_commands.empty()) {
if (modified_commands[modified_commands.size() - 1] != '\n')
modified_commands += "\n";
#ifdef HOST_OS_WINDOWS
DWORD written;
WriteFile(xorpsh_write_commands_fd, modified_commands.c_str(),
modified_commands.size(), &written, NULL);
CloseHandle(xorpsh_write_commands_fd);
#else // ! HOST_OS_WINDOWS
write(xorpsh_write_commands_fd, modified_commands.c_str(),
modified_commands.size());
close(xorpsh_write_commands_fd);
#endif // ! HOST_OS_WINDOWS
xorpsh_write_commands_fd.clear();
}
}
_mode = MODE_IDLE;
while ((! is_interrupted) && (! is_user_exited)) {
_eventloop.run();
}
while (! done()) {
_eventloop.run();
}
_xrl_generic_done = false;
success = _rtrmgr_client.send_unregister_client(
"rtrmgr",
_authtoken,
callback(this, &XorpShell::generic_done));
_mode = MODE_SHUTDOWN;
// Run the event loop to cause the unregister to be sent
while (success && ! _xrl_generic_done) {
_eventloop.run();
}
}
bool
XorpShell::done() const
{
return (_xrl_generic_done && _ocl->done() && _router_cli->done());
}
void
XorpShell::register_done(const XrlError& e, const string* file,
const uint32_t* pid, const uint32_t* clientid)
{
if (e == XrlError::OKAY()) {
_authfile = *file;
_rtrmgr_pid = *pid;
_clientid = *clientid;
XLOG_TRACE(_verbose, "rtrmgr PID=%u\n", XORP_UINT_CAST(_rtrmgr_pid));
return;
} else {
fprintf(stderr, "Failed to register with router manager process\n");
fprintf(stderr, "%s\n", e.str().c_str());
exit(1);
}
}
void
XorpShell::generic_done(const XrlError& e)
{
if (e == XrlError::OKAY()) {
_xrl_generic_done = true;
return;
}
if ((e == XrlError::COMMAND_FAILED()) && (e.note() == "AUTH_FAIL")) {
fprintf(stderr, "Authentication Failure\n");
} else {
fprintf(stderr, "XRL failed\n");
fprintf(stderr, "%s\n", e.str().c_str());
}
exit(1);
}
#if 0
bool
XorpShell::request_config()
{
return (_rtrmgr_client.send_get_running_config("rtrmgr", _authtoken,
callback(this, &XorpShell::receive_config))
== true);
}
void
XorpShell::receive_config(const XrlError& e, const bool* ready,
const string* config)
{
if (e == XrlError::OKAY()) {
if (*ready) {
_configuration = *config;
_got_config = true;
return;
} else {
/* the rtrmgr is not ready to pass us a config - typically
this is because it is in the process of reconfiguring */
_repeat_request_timer =
eventloop().new_oneoff_after_ms(1000,
callback(this, &XorpShell::request_config));
return;
}
}
if ((e == XrlError::COMMAND_FAILED()) && (e.note() == "AUTH_FAIL")) {
fprintf(stderr, "Authentication Failure\n");
} else {
fprintf(stderr, "Failed to register with router manager process\n");
fprintf(stderr, "%s\n", e.str().c_str());
}
exit(1);
}
#endif // 0
bool
XorpShell::lock_config(LOCK_CALLBACK cb)
{
// Lock for 60 seconds - this should be sufficient
return (_rtrmgr_client.send_lock_config("rtrmgr", _authtoken, 60000, cb)
== true);
}
bool
XorpShell::commit_changes(const string& deltas, const string& deletions,
GENERIC_CALLBACK cb, CallBack final_cb)
{
_commit_callback = final_cb;
if (_rtrmgr_client.send_apply_config_change("rtrmgr", _authtoken,
_ipc_name, deltas, deletions,
cb)
!= true) {
return (false);
}
return (true);
}
void
XorpShell::config_saved_done(bool success, const string& error_msg)
{
// Call unlock_config. The callback from unlock will finally clear
// things up.
_ct->save_phase4(success, error_msg, _config_save_callback, this);
}
void
XorpShell::commit_done(bool success, const string& error_msg)
{
// Call unlock_config. The callback from unlock will finally clear
// things up.
_ct->commit_phase4(success, error_msg, _commit_callback, this);
}
bool
XorpShell::unlock_config(GENERIC_CALLBACK cb)
{
return (_rtrmgr_client.send_unlock_config("rtrmgr", _authtoken, cb)
== true);
}
bool
XorpShell::enter_config_mode(bool exclusive, GENERIC_CALLBACK cb)
{
return (_rtrmgr_client.send_enter_config_mode("rtrmgr", _authtoken,
exclusive, cb)
== true);
}
bool
XorpShell::leave_config_mode(GENERIC_CALLBACK cb)
{
return (_rtrmgr_client.send_leave_config_mode("rtrmgr", _authtoken, cb)
== true);
}
bool
XorpShell::get_config_users(GET_USERS_CALLBACK cb)
{
return (_rtrmgr_client.send_get_config_users("rtrmgr", _authtoken, cb)
== true);
}
void
XorpShell::new_config_user(uid_t user_id)
{
_router_cli->new_config_user(user_id);
}
bool
XorpShell::save_to_file(const string& filename, GENERIC_CALLBACK cb,
CallBack final_cb)
{
_config_save_callback = final_cb;
LOCK_CALLBACK locked_cb = callback(this,
&XorpShell::save_lock_achieved,
filename,
cb);
if (_rtrmgr_client.send_lock_config("rtrmgr", _authtoken, 60000, locked_cb)
!= true) {
return (false);
}
return (true);
}
void
XorpShell::save_lock_achieved(const XrlError& e, const bool* locked,
const uint32_t* /* lock_holder */,
const string filename,
GENERIC_CALLBACK cb)
{
string error_msg;
if (!locked || (e != XrlError::OKAY())) {
error_msg = c_format("Failed to get configuration lock");
_config_save_callback->dispatch(false, error_msg);
return;
}
if (_rtrmgr_client.send_save_config("rtrmgr", _authtoken, _ipc_name,
filename, cb)
!= true) {
error_msg = c_format("Failed to send configuration. No Finder?");
_config_save_callback->dispatch(false, error_msg);
return;
}
}
bool
XorpShell::load_from_file(const string& filename, GENERIC_CALLBACK cb,
CallBack final_cb)
{
_commit_callback = final_cb;
LOCK_CALLBACK locked_cb = callback(this,
&XorpShell::load_lock_achieved,
filename,
cb);
if (_rtrmgr_client.send_lock_config("rtrmgr", _authtoken, 60000, locked_cb)
!= true) {
return (false);
}
return (true);
}
void
XorpShell::load_lock_achieved(const XrlError& e, const bool* locked,
const uint32_t* /* lock_holder */,
const string filename,
GENERIC_CALLBACK cb)
{
string error_msg;
if (!locked || (e != XrlError::OKAY())) {
error_msg = c_format("Failed to get configuration lock");
_commit_callback->dispatch(false, error_msg);
return;
}
if (_rtrmgr_client.send_load_config("rtrmgr", _authtoken, _ipc_name,
filename, cb)
!= true) {
error_msg = c_format("Failed to load the configuration. No Finder?");
_commit_callback->dispatch(false, error_msg);
return;
}
}
void
XorpShell::config_changed(uid_t user_id, const string& deltas,
const string& deletions)
{
#if 0
if (_mode == MODE_COMMITTING) {
// This is the response back to our own request
return;
}
#endif
if (_mode == MODE_INITIALIZING) {
// We were just starting up
XLOG_ASSERT(deletions == "");
_configuration = deltas;
_got_config = true;
return;
}
string response;
if (!_ct->apply_deltas(user_id, deltas,
false /* this is not a provisional change */,
true /* preserve_node_id */,
response)) {
_router_cli->notify_user("WARNING: Failed to merge deltas "
"from rtrmgr\n",
/* urgent */ true);
_router_cli->notify_user(response, /* urgent */ true);
// XXX it's not clear we can continue if this happens
}
response == "";
if (!_ct->apply_deletions(user_id, deletions,
/* this is not a provisional change */ false,
response)) {
_router_cli->notify_user("WARNING: Failed to merge deletions "
"from rtrmgr\n",
/* urgent */ true);
_router_cli->notify_user(response, /* urgent */ true);
// XXX: it's not clear we can continue if this happens
}
if (_mode == MODE_LOADING) {
// No need to notify, as the change was caused by us.
return;
}
#ifdef HOST_OS_WINDOWS
string username("root");
#else // ! HOST_OS_WINDOWS
// Notify the user that the config changed
struct passwd *pwent = getpwuid(user_id);
string username;
if (pwent == NULL)
username = c_format("UID:%u", XORP_UINT_CAST(user_id));
else
username = pwent->pw_name;
#endif // ! HOST_OS_WINDOWS
if (_mode == MODE_COMMITTING) {
// This is the response back to our own request
return;
}
//
// Print the configuration change alert
//
string alert = "The configuration has been changed by user " +
username + "\n";
if (! deltas.empty()) {
alert += "DELTAS:\n";
SlaveConfigTree sct(deltas, _tt, _xclient, _clientid, false);
alert += sct.show_tree(false);
}
if (! deletions.empty()) {
alert += "DELETIONS:\n";
SlaveConfigTree sct(deletions, _tt, _xclient, _clientid, false);
alert += sct.show_tree(false);
}
_router_cli->notify_user(alert, true);
_router_cli->config_changed_by_other_user();
}
void
XorpShell::module_status_change(const string& module_name,
GenericModule::ModuleStatus status)
{
string error_msg;
debug_msg("Module status change: %s status %d\n",
module_name.c_str(), XORP_INT_CAST(status));
GenericModule *module = _mmgr.find_module(module_name);
if (module == NULL) {
module = _mmgr.new_module(module_name, error_msg);
}
XLOG_ASSERT(module != NULL);
module->new_status(status);
}
bool
XorpShell::get_rtrmgr_pid(PID_CALLBACK cb)
{
return (_rtrmgr_client.send_get_pid("rtrmgr", cb) == true);
}
/**
* Called when Finder connection is established.
*
* Note that this method overwrites an XrlRouter virtual method.
*/
void
XorpShell::finder_connect_event()
{
//
// XXX: nothing to do, because on startup we wait until the finder
// is ready.
//
}
/**
* Called when Finder disconnect occurs.
*
* Note that this method overwrites an XrlRouter virtual method.
*/
void
XorpShell::finder_disconnect_event()
{
if (_is_connected_to_finder)
fprintf(stderr, "Finder disconnected. No Finder?\n");
_is_connected_to_finder = false;
}
// ----------------------------------------------------------------------------
// main() and it's helpers
static void
signal_handler(int signal_value)
{
switch (signal_value) {
case SIGINT:
// Ignore Ctrl-C: it is used by the CLI to interrupt a command.
break;
#ifdef SIGPIPE
case SIGPIPE:
// Ignore SIGPIPE: it may be generated when executing commands
// specified on the command line.
break;
#endif
default:
// XXX: anything else we have intercepted will terminate us.
is_interrupted = true;
break;
}
}
static void
exit_handler(CliClient*)
{
is_user_exited = true;
}
static void
usage(const char *argv0)
{
fprintf(stderr, "Usage: %s [options]\n", xorp_basename(argv0));
fprintf(stderr, "Options:\n");
fprintf(stderr, " -c Specify command(s) to execute\n");
fprintf(stderr, " -h Display this information\n");
fprintf(stderr, " -v Print verbose information\n");
fprintf(stderr, " -t <dir> Specify templates directory\n");
fprintf(stderr, " -x <dir> Specify Xrl targets directory\n");
}
static void
display_defaults()
{
fprintf(stderr, "Defaults:\n");
fprintf(stderr, " Templates directory := %s\n",
xorp_template_dir().c_str());
fprintf(stderr, " Xrl targets directory := %s\n",
xorp_xrl_targets_dir().c_str());
fprintf(stderr, " Print verbose information := %s\n",
default_verbose ? "true" : "false");
}
int
main(int argc, char *argv[])
{
int errcode = 0;
string commands;
//
// Initialize and start xlog
//
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
xlog_init(argv[0], NULL);
xlog_set_verbose(XLOG_VERBOSE_LOW); // Least verbose messages
// XXX: verbosity of the error messages temporary increased
xlog_level_set_verbose(XLOG_LEVEL_ERROR, XLOG_VERBOSE_HIGH);
xlog_add_default_output();
xlog_start();
comm_init();
//
// Install the handler for unexpected exceptions
//
XorpUnexpectedHandler eh(xorp_unexpected_handler);
//
// Expand the default variables to include the XORP root path
//
xorp_path_init(argv[0]);
string template_dir = xorp_template_dir();
string xrl_targets_dir = xorp_xrl_targets_dir();
int c;
while ((c = getopt(argc, argv, "c:t:x:vh")) != EOF) {
switch(c) {
case 'c':
commands = optarg;
break;
case 't':
template_dir = optarg;
break;
case 'x':
xrl_targets_dir = optarg;
break;
case 'v':
verbose = true;
break;
case '?':
case 'h':
usage(argv[0]);
display_defaults();
errcode = 1;
goto cleanup;
}
}
//
// Initialize the IPC mechanism.
// As there can be multiple xorpsh instances, we need to generate a
// unique name for our xrl_router.
//
char hostname[MAXHOSTNAMELEN];
if (gethostname(hostname, sizeof(hostname)) < 0) {
#ifdef HOST_OS_WINDOWS
XLOG_FATAL("gethostname() failed: %s", win_strerror(WSAGetLastError()));
#else
XLOG_FATAL("gethostname() failed: %s", strerror(errno));
#endif
}
hostname[sizeof(hostname) - 1] = '\0';
try {
EventLoop eventloop;
string xname = "xorpsh" + c_format("-%d-%s", XORP_INT_CAST(getpid()),
hostname);
XorpShell xorpsh(eventloop, xname, xorp_binary_root_dir(),
template_dir, xrl_targets_dir, verbose);
xorpsh.run(commands);
} catch (const InitError& e) {
XLOG_ERROR("xorpsh exiting due to an init error: %s", e.why().c_str());
errcode = 1;
goto cleanup;
}
cleanup:
comm_exit();
//
// Gracefully stop and exit xlog
//
xlog_stop();
xlog_exit();
exit(errcode);
}
syntax highlighted by Code2HTML, v. 0.9.1