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 IDaemonLogger)
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 }