1 // Written in D programming language 2 /** 3 * Describes server attached to tty console. Specified delegate 4 * is called when SIGHUP signal is caught (linux only). 5 * 6 * See_Also: daemon 7 * 8 * Copyright: © 2014 DSoftOut 9 * License: Subject to the terms of the MIT license, as written in the included LICENSE file. 10 * Authors: NCrashed <ncrashed@gmail.com> 11 * 12 */ 13 module terminal; 14 15 import std.c.stdlib; 16 import std.stdio; 17 import std.conv; 18 import std.exception; 19 version (linux) 20 { 21 import std.c.linux.linux; 22 import core.sys.linux.errno; 23 } 24 import dlogg.log; 25 import util; 26 27 private 28 { 29 void delegate() savedListener, savedTermListener; 30 void delegate() savedRotateListener; 31 32 shared ILogger savedLogger; 33 34 string savedPidFile; 35 string savedLockFile; 36 37 extern (C) 38 { 39 version (linux) 40 { 41 // Signal trapping in Linux 42 alias void function(int) sighandler_t; 43 sighandler_t signal(int signum, sighandler_t handler); 44 int __libc_current_sigrtmin(); 45 char* strerror(int errnum); 46 47 void signal_handler_terminal(int sig) 48 { 49 if(sig == SIGABRT || sig == SIGTERM || sig == SIGQUIT || sig == SIGINT || sig == SIGQUIT) 50 { 51 savedLogger.logInfo(text("Signal ", to!string(sig), " caught...")); 52 savedTermListener(); 53 } else if(sig == SIGHUP) 54 { 55 savedLogger.logInfo(text("Signal ", to!string(sig), " caught...")); 56 savedListener(); 57 } else if(sig == SIGROTATE) 58 { 59 savedLogger.logInfo(text("User signal ", sig, " is caught!")); 60 savedRotateListener(); 61 } 62 } 63 } 64 } 65 version(linux) 66 { 67 private immutable int SIGROTATE; 68 static this() 69 { 70 SIGROTATE = __libc_current_sigrtmin + 10; 71 } 72 73 void dropRootPrivileges(int groupid, int userid) 74 { 75 if (getuid() == 0) 76 { 77 if(groupid < 0 || userid < 0) 78 { 79 savedLogger.logWarning("Running as root, but doesn't specified groupid and/or userid for" 80 " privileges lowing!"); 81 return; 82 } 83 84 savedLogger.logInfo("Running as root, dropping privileges..."); 85 // process is running as root, drop privileges 86 if (setgid(groupid) != 0) 87 { 88 savedLogger.logError(text("setgid: Unable to drop group privileges: ", strerror(errno).fromStringz)); 89 assert(false); 90 } 91 if (setuid(userid) != 0) 92 { 93 savedLogger.logError(text("setuid: Unable to drop user privileges: ", strerror(errno).fromStringz)); 94 assert(false); 95 } 96 } 97 } 98 } 99 } 100 101 /** 102 * Run application as casual process (attached to tty) with $(B progMain) main function and passes $(B args) into it. 103 * If daemon catches SIGHUP signal, $(B listener) delegate is called (available on linux only). 104 * 105 * If application receives some kind of terminating signal, the $(B termListener) is called. $(B termListener) should 106 * end $(B progMain) to be able to clearly shutdown the application. 107 * 108 * If application receives "real-time" signal $(B SIGROTATE) defined as SIGRTMIN+10, then $(B rotateListener) is called 109 * to handle 'logrotate' utility. 110 * 111 * Daemon writes log message into provided $(B logger). 112 * 113 * $(B groupid) and $(B userid) are used to low privileges with run as root. 114 */ 115 int runTerminal(shared ILogger logger, int delegate(string[]) progMain, string[] args, void delegate() listener, 116 void delegate() termListener, void delegate() rotateListener, int groupid = -1, int userid = -1) 117 { 118 savedLogger = logger; 119 120 // dropping root 121 dropRootPrivileges(groupid, userid); 122 123 version (linux) 124 { 125 void bindSignal(int sig, sighandler_t handler) 126 { 127 enforce(signal(sig, handler) != SIG_ERR, text("Cannot catch signal ", sig)); 128 } 129 130 savedTermListener = termListener; 131 bindSignal(SIGABRT, &signal_handler_terminal); 132 bindSignal(SIGTERM, &signal_handler_terminal); 133 bindSignal(SIGQUIT, &signal_handler_terminal); 134 bindSignal(SIGINT, &signal_handler_terminal); 135 bindSignal(SIGQUIT, &signal_handler_terminal); 136 137 savedListener = listener; 138 bindSignal(SIGHUP, &signal_handler_terminal); 139 140 savedRotateListener = rotateListener; 141 bindSignal(SIGROTATE, &signal_handler_terminal); 142 } 143 else 144 { 145 logger.logError("This platform doesn't support signals. Updating json-sql table by signal is disabled!"); 146 } 147 148 logger.logInfo("Server is starting in terminal mode..."); 149 return progMain(args); 150 }