From 439b7d966cfc819ae76f65d60b5850a226f45933 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Adrien=20B=C3=A9raud?= <adrien.beraud@savoirfairelinux.com>
Date: Sat, 19 Dec 2015 19:40:20 -0500
Subject: [PATCH] dhtnode: daemonize with the new -d argument

Running dhtnode with -d will make it detach and fork.
Add an argument to -v to log to a file instead of stdio.
---
 tools/dhtnode.cpp    | 357 ++++++++++++++++++++++---------------------
 tools/tools_common.h |  84 +++++++++-
 2 files changed, 264 insertions(+), 177 deletions(-)

diff --git a/tools/dhtnode.cpp b/tools/dhtnode.cpp
index 66808cb9..097a8f05 100644
--- a/tools/dhtnode.cpp
+++ b/tools/dhtnode.cpp
@@ -24,6 +24,7 @@ extern "C" {
 }
 
 #include <set>
+#include <thread> // std::this_thread::sleep_for
 
 using namespace dht;
 
@@ -68,9 +69,177 @@ void print_help() {
               << std::endl;
 }
 
+void cmd_loop(DhtRunner& dht, dht_params& params)
+{
+    print_node_info(dht, params);
+    std::cout << " (type 'h' or 'help' for a list of possible commands)" << std::endl << std::endl;
+
+    // using the GNU History API
+    using_history();
+
+    while (true)
+    {
+        // using the GNU Readline API
+        std::string line = readLine();
+        if (!line.empty() && line[0] == '\0')
+            break;
+
+        std::istringstream iss(line);
+        std::string op, idstr, value;
+        iss >> op >> idstr;
+
+        if (op == "x" || op == "q" || op == "exit" || op == "quit") {
+            break;
+        } else if (op == "h" || op == "help") {
+            print_help();
+            continue;
+        } else if (op == "ll") {
+            print_node_info(dht, params);
+            unsigned good4, dubious4, cached4, incoming4;
+            unsigned good6, dubious6, cached6, incoming6;
+            dht.getNodesStats(AF_INET, &good4, &dubious4, &cached4, &incoming4);
+            dht.getNodesStats(AF_INET6, &good6, &dubious6, &cached6, &incoming6);
+            std::cout << "IPv4 nodes : " << good4 << " good, " << dubious4 << " dubious, " << incoming4 << " incoming." << std::endl;
+            std::cout << "IPv6 nodes : " << good6 << " good, " << dubious6 << " dubious, " << incoming6 << " incoming." << std::endl;
+            continue;
+        } else if (op == "lr") {
+            std::cout << "IPv4 routing table:" << std::endl;
+            std::cout << dht.getRoutingTablesLog(AF_INET) << std::endl;
+            std::cout << "IPv6 routing table:" << std::endl;
+            std::cout << dht.getRoutingTablesLog(AF_INET6) << std::endl;
+            continue;
+        } else if (op == "ld") {
+            std::cout << dht.getStorageLog() << std::endl;
+            continue;
+        } else if (op == "ls") {
+            std::cout << "Searches:" << std::endl;
+            std::cout << dht.getSearchesLog() << std::endl;
+            continue;
+        } else if (op == "la")  {
+            std::cout << "Reported public addresses:" << std::endl;
+            auto addrs = dht.getPublicAddressStr();
+            for (const auto& addr : addrs)
+                std::cout << addr << std::endl;
+            continue;
+        } else if (op == "b") {
+            try {
+                auto addr = splitPort(idstr);
+                if (not addr.first.empty() and addr.second.empty()){
+                    std::stringstream ss;
+                    ss << DHT_DEFAULT_PORT;
+                    addr.second = ss.str();
+                }
+                dht.bootstrap(addr.first.c_str(), addr.second.c_str());
+            } catch (const std::exception& e) {
+                std::cerr << e.what() << std::endl;
+            }
+            continue;
+        } else if (op == "log") {
+            params.log = !params.log;
+            if (params.log)
+                enableLogging(dht);
+            else
+                disableLogging(dht);
+            continue;
+        }
+
+        if (op.empty())
+            continue;
+
+        dht::InfoHash id {idstr};
+        static const std::set<std::string> VALID_OPS {"g", "l", "p", "s", "e", "a"};
+        if (VALID_OPS.find(op) == VALID_OPS.cend()) {
+            std::cout << "Unknown command: " << op << std::endl;
+            std::cout << " (type 'h' or 'help' for a list of possible commands)" << std::endl;
+            continue;
+        }
+        static constexpr dht::InfoHash INVALID_ID {};
+        if (id == INVALID_ID) {
+            std::cout << "Syntax error: invalid InfoHash." << std::endl;
+            continue;
+        }
+
+        auto start = std::chrono::high_resolution_clock::now();
+        if (op == "g") {
+            dht.get(id, [start](std::shared_ptr<Value> value) {
+                auto now = std::chrono::high_resolution_clock::now();
+                std::cout << "Get: found value (after " << print_dt(now-start) << "s)" << std::endl;
+                std::cout << "\t" << *value << std::endl;
+                return true;
+            }, [start](bool ok) {
+                auto end = std::chrono::high_resolution_clock::now();
+                std::cout << "Get: " << (ok ? "completed" : "failure") << " (took " << print_dt(end-start) << "s)" << std::endl;
+            });
+        }
+        else if (op == "l") {
+            std::cout << id << std::endl;
+            dht.listen(id, [](std::shared_ptr<Value> value) {
+                std::cout << "Listen: found value:" << std::endl;
+                std::cout << "\t" << *value << std::endl;
+                return true;
+            });
+        }
+        else if (op == "p") {
+            std::string v;
+            iss >> v;
+            dht.put(id, dht::Value {
+                dht::ValueType::USER_DATA.id,
+                std::vector<uint8_t> {v.begin(), v.end()}
+            }, [start](bool ok) {
+                auto end = std::chrono::high_resolution_clock::now();
+                std::cout << "Put: " << (ok ? "success" : "failure") << " (took " << print_dt(end-start) << "s)" << std::endl;
+            });
+        }
+        else if (op == "s") {
+            if (not params.generate_identity) {
+                print_id_req();
+                continue;
+            }
+            std::string v;
+            iss >> v;
+            dht.putSigned(id, dht::Value {
+                dht::ValueType::USER_DATA.id,
+                std::vector<uint8_t> {v.begin(), v.end()}
+            }, [start](bool ok) {
+                auto end = std::chrono::high_resolution_clock::now();
+                std::cout << "Put signed: " << (ok ? "success" : "failure") << " (took " << print_dt(end-start) << "s)" << std::endl;
+            });
+        }
+        else if (op == "e") {
+            if (not params.generate_identity) {
+                print_id_req();
+                continue;
+            }
+            std::string tostr;
+            std::string v;
+            iss >> tostr >> v;
+            dht.putEncrypted(id, InfoHash(tostr), dht::Value {
+                dht::ValueType::USER_DATA.id,
+                std::vector<uint8_t> {v.begin(), v.end()}
+            }, [start](bool ok) {
+                auto end = std::chrono::high_resolution_clock::now();
+                std::cout << "Put encrypted: " << (ok ? "success" : "failure") << " (took " << print_dt(end-start) << "s)" << std::endl;
+            });
+        }
+        else if (op == "a") {
+            in_port_t port;
+            iss >> port;
+            dht.put(id, dht::Value {dht::IpServiceAnnouncement::TYPE.id, dht::IpServiceAnnouncement(port)}, [start](bool ok) {
+                auto end = std::chrono::high_resolution_clock::now();
+                std::cout << "Announce: " << (ok ? "success" : "failure") << " (took " << print_dt(end-start) << "s)" << std::endl;
+            });
+        }
+    }
+
+    std::cout << std::endl <<  "Stopping node..." << std::endl;
+}
+
 int
 main(int argc, char **argv)
 {
+    if (int rc = gnutls_global_init())  // TODO: remove with GnuTLS >= 3.3
+        throw std::runtime_error(std::string("Error initializing GnuTLS: ")+gnutls_strerror(rc));
+
     DhtRunner dht;
     try {
         auto params = parseArgs(argc, argv);
@@ -78,191 +247,39 @@ main(int argc, char **argv)
             print_usage();
             return 0;
         }
-
-        // TODO: remove with GnuTLS >= 3.3
-        int rc = gnutls_global_init();
-        if (rc != GNUTLS_E_SUCCESS)
-            throw std::runtime_error(std::string("Error initializing GnuTLS: ")+gnutls_strerror(rc));
+        if (params.daemonize) {
+            daemonize();
+        }
 
         dht::crypto::Identity crt {};
         if (params.generate_identity) {
             auto ca_tmp = dht::crypto::generateIdentity("DHT Node CA");
             crt = dht::crypto::generateIdentity("DHT Node", ca_tmp);
         }
-    
+
         dht.run(params.port, crt, true, params.is_bootstrap_node);
 
-        if (params.log)
-            enableLogging(dht);
+        if (params.log) {
+            if (not params.logfile.empty())
+                enableFileLogging(dht, params.logfile);
+            else
+                enableLogging(dht);
+        }
 
         if (not params.bootstrap.first.empty()) {
-            std::cout << "Bootstrap: " << params.bootstrap.first << ":" << params.bootstrap.second << std::endl;
+            //std::cout << "Bootstrap: " << params.bootstrap.first << ":" << params.bootstrap.second << std::endl;
             dht.bootstrap(params.bootstrap.first.c_str(), params.bootstrap.second.c_str());
         }
 
-        print_node_info(dht, params);
-        std::cout << " (type 'h' or 'help' for a list of possible commands)" << std::endl << std::endl;
-
-        // using the GNU History API
-        using_history();
-
-        while (true)
-        {
-            // using the GNU Readline API
-            std::string line = readLine();
-            if (!line.empty() && line[0] == '\0')
-                break;
-
-            std::istringstream iss(line);
-            std::string op, idstr, value;
-            iss >> op >> idstr;
-
-            if (op == "x" || op == "q" || op == "exit" || op == "quit") {
-                break;
-            } else if (op == "h" || op == "help") {
-                print_help();
-                continue;
-            } else if (op == "ll") {
-                print_node_info(dht, params);
-                unsigned good4, dubious4, cached4, incoming4;
-                unsigned good6, dubious6, cached6, incoming6;
-                dht.getNodesStats(AF_INET, &good4, &dubious4, &cached4, &incoming4);
-                dht.getNodesStats(AF_INET6, &good6, &dubious6, &cached6, &incoming6);
-                std::cout << "IPv4 nodes : " << good4 << " good, " << dubious4 << " dubious, " << incoming4 << " incoming." << std::endl;
-                std::cout << "IPv6 nodes : " << good6 << " good, " << dubious6 << " dubious, " << incoming6 << " incoming." << std::endl;
-                continue;
-            } else if (op == "lr") {
-                std::cout << "IPv4 routing table:" << std::endl;
-                std::cout << dht.getRoutingTablesLog(AF_INET) << std::endl;
-                std::cout << "IPv6 routing table:" << std::endl;
-                std::cout << dht.getRoutingTablesLog(AF_INET6) << std::endl;
-                continue;
-            } else if (op == "ld") {
-                std::cout << dht.getStorageLog() << std::endl;
-                continue;
-            } else if (op == "ls") {
-                std::cout << "Searches:" << std::endl;
-                std::cout << dht.getSearchesLog() << std::endl;
-                continue;
-            } else if (op == "la")  {
-                std::cout << "Reported public addresses:" << std::endl;
-                auto addrs = dht.getPublicAddressStr();
-                for (const auto& addr : addrs)
-                    std::cout << addr << std::endl;
-                continue;
-            } else if (op == "b") {
-                try {
-                    auto addr = splitPort(idstr);
-                    if (not addr.first.empty() and addr.second.empty()){
-                        std::stringstream ss;
-                        ss << DHT_DEFAULT_PORT;
-                        addr.second = ss.str();
-                    }
-                    dht.bootstrap(addr.first.c_str(), addr.second.c_str());
-                } catch (const std::exception& e) {
-                    std::cout << e.what() << std::endl;
-                }
-                continue;
-            } else if (op == "log") {
-                params.log = !params.log;
-                if (params.log)
-                    enableLogging(dht);
-                else
-                    disableLogging(dht);
-                continue;
-            }
-
-            if (op.empty())
-                continue;
-
-            dht::InfoHash id {idstr};
-            static const std::set<std::string> VALID_OPS {"g", "l", "p", "s", "e", "a"};
-            if (VALID_OPS.find(op) == VALID_OPS.cend()) {
-                std::cout << "Unknown command: " << op << std::endl;
-                std::cout << " (type 'h' or 'help' for a list of possible commands)" << std::endl;
-                continue;
-            }
-            static constexpr dht::InfoHash INVALID_ID {};
-            if (id == INVALID_ID) {
-                std::cout << "Syntax error: invalid InfoHash." << std::endl;
-                continue;
-            }
-
-            auto start = std::chrono::high_resolution_clock::now();
-            if (op == "g") {
-                dht.get(id, [start](std::shared_ptr<Value> value) {
-                    auto now = std::chrono::high_resolution_clock::now();
-                    std::cout << "Get: found value (after " << print_dt(now-start) << "s)" << std::endl;
-                    std::cout << "\t" << *value << std::endl;
-                    return true;
-                }, [start](bool ok) {
-                    auto end = std::chrono::high_resolution_clock::now();
-                    std::cout << "Get: " << (ok ? "completed" : "failure") << " (took " << print_dt(end-start) << "s)" << std::endl;
-                });
-            }
-            else if (op == "l") {
-                std::cout << id << std::endl;
-                dht.listen(id, [](std::shared_ptr<Value> value) {
-                    std::cout << "Listen: found value:" << std::endl;
-                    std::cout << "\t" << *value << std::endl;
-                    return true;
-                });
-            }
-            else if (op == "p") {
-                std::string v;
-                iss >> v;
-                dht.put(id, dht::Value {
-                    dht::ValueType::USER_DATA.id,
-                    std::vector<uint8_t> {v.begin(), v.end()}
-                }, [start](bool ok) {
-                    auto end = std::chrono::high_resolution_clock::now();
-                    std::cout << "Put: " << (ok ? "success" : "failure") << " (took " << print_dt(end-start) << "s)" << std::endl;
-                });
-            }
-            else if (op == "s") {
-                if (not params.generate_identity) {
-                    print_id_req();
-                    continue;
-                }
-                std::string v;
-                iss >> v;
-                dht.putSigned(id, dht::Value {
-                    dht::ValueType::USER_DATA.id,
-                    std::vector<uint8_t> {v.begin(), v.end()}
-                }, [start](bool ok) {
-                    auto end = std::chrono::high_resolution_clock::now();
-                    std::cout << "Put signed: " << (ok ? "success" : "failure") << " (took " << print_dt(end-start) << "s)" << std::endl;
-                });
-            }
-            else if (op == "e") {
-                if (not params.generate_identity) {
-                    print_id_req();
-                    continue;
-                }
-                std::string tostr;
-                std::string v;
-                iss >> tostr >> v;
-                dht.putEncrypted(id, InfoHash(tostr), dht::Value {
-                    dht::ValueType::USER_DATA.id,
-                    std::vector<uint8_t> {v.begin(), v.end()}
-                }, [start](bool ok) {
-                    auto end = std::chrono::high_resolution_clock::now();
-                    std::cout << "Put encrypted: " << (ok ? "success" : "failure") << " (took " << print_dt(end-start) << "s)" << std::endl;
-                });
-            }
-            else if (op == "a") {
-                in_port_t port;
-                iss >> port;
-                dht.put(id, dht::Value {dht::IpServiceAnnouncement::TYPE.id, dht::IpServiceAnnouncement(port)}, [start](bool ok) {
-                    auto end = std::chrono::high_resolution_clock::now();
-                    std::cout << "Announce: " << (ok ? "success" : "failure") << " (took " << print_dt(end-start) << "s)" << std::endl;
-                });
-            }
+        if (params.daemonize) {
+            while (true)
+                std::this_thread::sleep_for(std::chrono::seconds(30));
+        } else {
+            cmd_loop(dht, params);
         }
 
-        std::cout << std::endl <<  "Stopping node..." << std::endl;
     } catch(const std::exception&e) {
-        std::cout << std::endl <<  e.what() << std::endl;
+        std::cerr << std::endl <<  e.what() << std::endl;
     }
 
     std::condition_variable cv;
@@ -276,7 +293,7 @@ main(int argc, char **argv)
         cv.notify_all();
     });
 
-    // wait for shutdown        
+    // wait for shutdown
     std::unique_lock<std::mutex> lk(m);
     cv.wait(lk, [&](){ return done.load(); });
 
diff --git a/tools/tools_common.h b/tools/tools_common.h
index b2dbf89c..c8fcab41 100644
--- a/tools/tools_common.h
+++ b/tools/tools_common.h
@@ -20,16 +20,22 @@
 
 // Common utility methods used by C++ OpenDHT tools.
 
+#include <opendht.h>
+#include <getopt.h>
+#include <readline/readline.h>
+#include <readline/history.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <signal.h>
+#include <unistd.h>
+
 #include <string>
 #include <vector>
 #include <chrono>
 #include <iostream>
 #include <sstream>
-
-#include <opendht.h>
-#include <getopt.h>
-#include <readline/readline.h>
-#include <readline/history.h>
+#include <fstream>
 
 /**
  * Terminal colors for logging
@@ -87,6 +93,19 @@ enableLogging(dht::DhtRunner& dht)
     );
 }
 
+void
+enableFileLogging(dht::DhtRunner& dht, const std::string& path)
+{
+    auto logfile = std::make_shared<std::fstream>();
+    logfile->open(path, std::ios::out);
+
+    dht.setLoggers(
+        [=](char const* m, va_list args){ printLog(*logfile, m, args); },
+        [=](char const* m, va_list args){ printLog(*logfile, m, args); },
+        [=](char const* m, va_list args){ printLog(*logfile, m, args); }
+    );
+}
+
 void
 disableLogging(dht::DhtRunner& dht)
 {
@@ -130,9 +149,11 @@ static const constexpr in_port_t DHT_DEFAULT_PORT = 4222;
 struct dht_params {
     bool help {false}; // print help and exit
     bool log {false};
+    std::string logfile {};
     in_port_t port {0};
     bool is_bootstrap_node {false};
     bool generate_identity {false};
+    bool daemonize {false};
     std::pair<std::string, std::string> bootstrap {};
 };
 
@@ -142,6 +163,7 @@ static const constexpr struct option long_options[] = {
    {"bootstrap",  optional_argument, nullptr, 'b'},
    {"identity",   no_argument      , nullptr, 'i'},
    {"verbose",    no_argument      , nullptr, 'v'},
+   {"daemonize",  no_argument      , nullptr, 'd'},
    {nullptr,      0,                 nullptr,  0}
 };
 
@@ -149,7 +171,7 @@ dht_params
 parseArgs(int argc, char **argv) {
     dht_params params;
     int opt;
-    while ((opt = getopt_long(argc, argv, ":hivp:b:", long_options, nullptr)) != -1) {
+    while ((opt = getopt_long(argc, argv, ":hidv:p:b:", long_options, nullptr)) != -1) {
         switch (opt) {
         case 'p': {
                 int port_arg = atoi(optarg);
@@ -162,7 +184,7 @@ parseArgs(int argc, char **argv) {
         case 'b':
             if (optarg) {
                 params.bootstrap = splitPort((optarg[0] == '=') ? optarg+1 : optarg);
-                if (not params.bootstrap.first.empty() and params.bootstrap.second.empty()){
+                if (not params.bootstrap.first.empty() and params.bootstrap.second.empty()) {
                     std::stringstream ss;
                     ss << DHT_DEFAULT_PORT;
                     params.bootstrap.second = ss.str();
@@ -175,16 +197,24 @@ parseArgs(int argc, char **argv) {
             params.help = true;
             break;
         case 'v':
+            if (optarg)
+                params.logfile = {optarg};
             params.log = true;
             break;
         case 'i':
             params.generate_identity = true;
             break;
+        case 'd':
+            params.daemonize = true;
+            break;
         case ':':
             switch (optopt) {
             case 'b':
                 params.is_bootstrap_node = true;
                 break;
+            case 'v':
+                params.log = true;
+                break;
             default:
                 std::cout << "option requires an argument -- '" << optopt << '\'' << std::endl;
                 break;
@@ -208,3 +238,43 @@ readLine(const char* prefix = PROMPT)
 
     return line_read ? std::string(line_read) : std::string("\0", 1);
 }
+
+void signal_handler(int sig)
+{
+    switch(sig) {
+    case SIGHUP:
+        break;
+    case SIGTERM:
+        exit(EXIT_SUCCESS);
+        break;
+    }
+}
+
+void daemonize()
+{
+    pid_t pid = fork();
+    if (pid < 0) exit(EXIT_FAILURE);
+    if (pid > 0) exit(EXIT_SUCCESS);
+
+    umask(0);
+
+    pid_t sid = setsid();
+    if (sid < 0) {
+        exit(EXIT_FAILURE);
+    }
+
+    if ((chdir("/")) < 0) {
+        exit(EXIT_FAILURE);
+    }
+
+    close(STDIN_FILENO);
+    close(STDOUT_FILENO);
+    close(STDERR_FILENO);
+
+    signal(SIGCHLD,SIG_IGN); /* ignore child */
+    signal(SIGTSTP,SIG_IGN); /* ignore tty signals */
+    signal(SIGTTOU,SIG_IGN);
+    signal(SIGTTIN,SIG_IGN);
+    signal(SIGHUP,signal_handler); /* catch hangup signal */
+    signal(SIGTERM,signal_handler); /* catch kill signal */
+}
-- 
GitLab