1 // This file is written in D programming language 2 /** 3 * Platform independent parts of the library. Defines common signals 4 * that safe to catch, utilities for describing daemons and some 5 * utitility templates for duck typing. 6 * 7 * Copyright: © 2013-2014 Anton Gushcha 8 * License: Subject to the terms of the MIT license, as written in the included LICENSE file. 9 * Authors: NCrashed <ncrashed@gmail.com> 10 */ 11 module daemonize.daemon; 12 13 import daemonize.keymap; 14 import std.traits; 15 import std.typetuple; 16 17 static if( __VERSION__ < 2066 ) private enum nogc; 18 19 /** 20 * Native signals that can be hooked. There arn't all 21 * linux native signals due safety. 22 * 23 * Most not listed signals are already binded by druntime (sigusr1 and 24 * sigusr2 are occupied by garbage collector) and rebinding 25 * cause a program hanging or ignoring all signal callbacks. 26 * 27 * You can define your own signals by $(B customSignal) function. 28 * These signals are mapped to realtime signals in linux. 29 * 30 * Note: Signal enum is based on string to map signals on winapi events. 31 */ 32 enum Signal : string 33 { 34 // Linux native requests 35 Abort = "Abort", 36 Terminate = "Terminate", 37 Quit = "Quit", 38 Interrupt = "Interrupt", 39 HangUp = "HangUp", 40 41 // Windows native requests 42 Stop = "Stop", 43 Continue = "Continue", 44 Pause = "Pause", 45 Shutdown = "Shutdown", 46 Interrogate = "Interrogate", 47 NetBindAdd = "NetBindAdd", 48 NetBindDisable = "NetBindDisable", 49 NetBindEnable = "NetBindEnable", 50 NetBindRemove = "NetBindRemove", 51 ParamChange = "ParamChange", 52 53 } 54 55 /** 56 * Creating your own custom signal. Theese signals are binded to 57 * realtime signals in linux and to winapi events in Windows. 58 * 59 * Example: 60 * -------- 61 * enum LogRotatingSignal = "LogRotate".customSignal; 62 * -------- 63 */ 64 @nogc Signal customSignal(string name) @safe pure nothrow 65 { 66 return cast(Signal)name; 67 } 68 69 /** 70 * Signal OR composition. 71 * 72 * If you'd like to hook several signals by one handler, you 73 * can use the template in place of signal in $(B KeyValueList) 74 * signal map. 75 * 76 * In that case the handler should also accept a Signal value 77 * as it second parameter. 78 */ 79 template Composition(Signals...) 80 { 81 alias signals = Signals; 82 83 template opEqual(T...) 84 { 85 static if(T.length > 0 && isComposition!(T[0])) 86 { 87 enum opEqual = [signals] == [T[0].signals]; 88 } 89 else enum opEqual = false; 90 } 91 } 92 93 /// Checks if $(B T) is a composition of signals 94 template isComposition(alias T) 95 { 96 static if(__traits(compiles, T.signals)) 97 { 98 private template isSignal(T...) 99 { 100 static if(is(T[0])) 101 { 102 enum isSignal = is(T[0] : Signal); 103 } 104 else 105 { 106 enum isSignal = is(typeof(T[0]) : Signal); 107 } 108 } 109 110 enum isComposition = allSatisfy!(isSignal, T.signals); 111 } 112 else enum isComposition = false; 113 } 114 115 /** 116 * Template for describing daemon in the package. 117 * 118 * To describe new daemon you should set unique name 119 * and signal -> callbacks mapping. 120 * 121 * $(B name) is used to name default pid and lock files 122 * of the daemon and also it is a service name in Windows. 123 * 124 * $(B pSignalMap) is a $(B KeyValueList) template where 125 * keys are $(B Signal) values and values are delegates of type: 126 * ---------- 127 * bool delegate(shared ILogger) 128 * ---------- 129 * If the delegate returns $(B false), daemon terminates. 130 * 131 * Example: 132 * ----------- 133 * alias daemon = Daemon!( 134 * "DaemonizeExample1", 135 * 136 * KeyValueList!( 137 * Signal.Terminate, (logger) 138 * { 139 * logger.logInfo("Exiting..."); 140 * return false; // returning false will terminate daemon 141 * }, 142 * Signal.HangUp, (logger) 143 * { 144 * logger.logInfo("Hello World!"); 145 * return true; // continue execution 146 * } 147 * ), 148 * 149 * (logger, shouldExit) 150 * { 151 * // will stop the daemon in 5 minutes 152 * auto time = Clock.currSystemTick + cast(TickDuration)5.dur!"minutes"; 153 * bool timeout = false; 154 * while(!shouldExit() && time > Clock.currSystemTick) { } 155 * 156 * logger.logInfo("Exiting main function!"); 157 * 158 * return 0; 159 * } 160 * ); 161 * ----------- 162 */ 163 template Daemon( 164 string name, 165 alias pSignalMap, 166 alias pMainFunc) 167 { 168 enum daemonName = name; 169 alias signalMap = pSignalMap; 170 alias mainFunc = pMainFunc; 171 } 172 173 /// Duck typing $(B Daemon) description 174 template isDaemon(alias T) 175 { 176 static if(__traits(compiles, T.daemonName) && __traits(compiles, T.signalMap) 177 && __traits(compiles, T.mainFunc)) 178 enum isDaemon = is(typeof(T.daemonName) == string); 179 else 180 enum isDaemon = false; 181 } 182 183 /** 184 * Truncated description of daemon for use with $(B sendSignal) function. 185 * You need to pass a daemon $(B name) and a list of signals to $(B Signals) 186 * expression list. 187 * 188 * Example: 189 * -------- 190 * // Full description of daemon 191 * alias daemon = Daemon!( 192 * "DaemonizeExample2", 193 * 194 * KeyValueList!( 195 * Signal.Terminate, (logger) 196 * { 197 * logger.logInfo("Exiting..."); 198 * return false; 199 * }, 200 * Signal.HangUp, (logger) 201 * { 202 * logger.logInfo("Hello World!"); 203 * return true; 204 * }, 205 * RotateLogSignal, (logger) 206 * { 207 * logger.logInfo("Rotating log!"); 208 * logger.reload; 209 * return true; 210 * }, 211 * DoSomethingSignal, (logger) 212 * { 213 * logger.logInfo("Doing something..."); 214 * return true; 215 * } 216 * ), 217 * (logger, shouldExit) 218 * { 219 * // some code 220 * } 221 * ); 222 * 223 * // Truncated description for client 224 * alias daemon = DaemonClient!( 225 * "DaemonizeExample2", 226 * Signal.Terminate, 227 * Signal.HangUp, 228 * RotateLogSignal, 229 * DoSomethingSignal 230 * ); 231 * ---------------- 232 */ 233 template DaemonClient( 234 string name, 235 Signals...) 236 { 237 private template isSignal(T...) 238 { 239 enum isSignal = is(typeof(T[0]) == Signal) || isComposition!(T[0]); 240 } 241 242 static assert(allSatisfy!(isSignal, Signals), "All values of Signals parameter have to be of Signal type!"); 243 244 enum daemonName = name; 245 alias signals = Signals; 246 } 247 248 /// Duck typing of $(B DaemonClient) 249 template isDaemonClient(alias T) 250 { 251 private template isSignal(T...) 252 { 253 enum isSignal = is(typeof(T[0]) == Signal) || isComposition!(T[0]); 254 } 255 256 static if(__traits(compiles, T.daemonName) && __traits(compiles, T.signals)) 257 enum isDaemonClient = is(typeof(T.daemonName) == string) && allSatisfy!(isSignal, T.signals); 258 else 259 enum isDaemonClient = false; 260 } 261 unittest 262 { 263 alias TestClient = DaemonClient!( 264 "DaemonizeExample2", 265 Signal.Terminate, 266 Signal.HangUp); 267 static assert(isDaemonClient!TestClient); 268 }