BadVPN – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 office 1 /**
2 * @file daemon.c
3 * @author Ambroz Bizjak <ambrop7@gmail.com>
4 *
5 * @section LICENSE
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * 3. Neither the name of the author nor the
15 * names of its contributors may be used to endorse or promote products
16 * derived from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 *
29 * @section DESCRIPTION
30 *
31 * Runs a program in the background, restarting it if it crashes.
32 * On deinitialization, sends SIGTERM to the daemon and waits for it to terminate
33 * (unless it's crashed at the time).
34 *
35 * Synopsis:
36 * daemon(list(string) cmd [, map options])
37 *
38 * Arguments:
39 * cmd - Command for the daemon. The first element is the full path
40 * to the executable, other elements are command line arguments (excluding
41 * the zeroth argument).
42 * options - Map of options:
43 * "keep_stdout":"true" - Start the program with the same stdout as the NCD process.
44 * "keep_stderr":"true" - Start the program with the same stderr as the NCD process.
45 * "do_setsid":"true" - Call setsid() in the child before exec. This is needed to
46 * start the 'agetty' program.
47 * "username":username_string - Start the process under the permissions of the
48 * specified user.
49 * "retry_time":milliseconds - Wait for this long after the daemon exits
50 * unexpectedtly before starting it again.
51 */
52  
53 #include <stdlib.h>
54 #include <stdio.h>
55 #include <string.h>
56  
57 #include <misc/cmdline.h>
58 #include <misc/strdup.h>
59 #include <system/BProcess.h>
60 #include <ncd/extra/NCDBProcessOpts.h>
61  
62 #include <ncd/module_common.h>
63  
64 #include <generated/blog_channel_ncd_daemon.h>
65  
66 #define DEFAULT_RETRY_TIME 10000
67  
68 #define STATE_RETRYING 1
69 #define STATE_RUNNING 2
70 #define STATE_RUNNING_DIE 3
71  
72 struct instance {
73 NCDModuleInst *i;
74 NCDValRef cmd_arg;
75 btime_t retry_time;
76 NCDBProcessOpts opts;
77 BTimer timer;
78 BProcess process;
79 int state;
80 };
81  
82 static int build_cmdline (NCDModuleInst *i, NCDValRef cmd_arg, char **exec, CmdLine *cl);
83 static void start_process (struct instance *o);
84 static void timer_handler (struct instance *o);
85 static void process_handler (struct instance *o, int normally, uint8_t normally_exit_status);
86 static void instance_free (struct instance *o);
87  
88 static int build_cmdline (NCDModuleInst *i, NCDValRef cmd_arg, char **exec, CmdLine *cl)
89 {
90 ASSERT(!NCDVal_IsInvalid(cmd_arg))
91  
92 if (!NCDVal_IsList(cmd_arg)) {
93 ModuleLog(i, BLOG_ERROR, "wrong type");
94 goto fail0;
95 }
96  
97 size_t count = NCDVal_ListCount(cmd_arg);
98  
99 // read exec
100 if (count == 0) {
101 ModuleLog(i, BLOG_ERROR, "missing executable name");
102 goto fail0;
103 }
104 NCDValRef exec_arg = NCDVal_ListGet(cmd_arg, 0);
105 if (!NCDVal_IsStringNoNulls(exec_arg)) {
106 ModuleLog(i, BLOG_ERROR, "wrong type");
107 goto fail0;
108 }
109 if (!(*exec = ncd_strdup(exec_arg))) {
110 ModuleLog(i, BLOG_ERROR, "ncd_strdup failed");
111 goto fail0;
112 }
113  
114 // start cmdline
115 if (!CmdLine_Init(cl)) {
116 ModuleLog(i, BLOG_ERROR, "CmdLine_Init failed");
117 goto fail1;
118 }
119  
120 // add header
121 if (!CmdLine_Append(cl, *exec)) {
122 ModuleLog(i, BLOG_ERROR, "CmdLine_Append failed");
123 goto fail2;
124 }
125  
126 // add additional arguments
127 for (size_t j = 1; j < count; j++) {
128 NCDValRef arg = NCDVal_ListGet(cmd_arg, j);
129  
130 if (!NCDVal_IsStringNoNulls(arg)) {
131 ModuleLog(i, BLOG_ERROR, "wrong type");
132 goto fail2;
133 }
134  
135 if (!CmdLine_AppendNoNullMr(cl, NCDVal_StringMemRef(arg))) {
136 ModuleLog(i, BLOG_ERROR, "CmdLine_AppendNoNullMr failed");
137 goto fail2;
138 }
139 }
140  
141 // finish
142 if (!CmdLine_Finish(cl)) {
143 ModuleLog(i, BLOG_ERROR, "CmdLine_Finish failed");
144 goto fail2;
145 }
146  
147 return 1;
148  
149 fail2:
150 CmdLine_Free(cl);
151 fail1:
152 free(*exec);
153 fail0:
154 return 0;
155 }
156  
157 static void start_process (struct instance *o)
158 {
159 // build cmdline
160 char *exec;
161 CmdLine cl;
162 if (!build_cmdline(o->i, o->cmd_arg, &exec, &cl)) {
163 goto fail;
164 }
165  
166 // start process
167 struct BProcess_params p_params = NCDBProcessOpts_GetParams(&o->opts);
168 int res = BProcess_Init2(&o->process, o->i->params->iparams->manager, (BProcess_handler)process_handler, o, exec, CmdLine_Get(&cl), p_params);
169 CmdLine_Free(&cl);
170 free(exec);
171  
172 if (!res) {
173 ModuleLog(o->i, BLOG_ERROR, "BProcess_Init2 failed");
174 goto fail;
175 }
176  
177 // set state running
178 o->state = STATE_RUNNING;
179 return;
180  
181 fail:
182 // start timer
183 BReactor_SetTimer(o->i->params->iparams->reactor, &o->timer);
184  
185 // set state retrying
186 o->state = STATE_RETRYING;
187 }
188  
189 static void timer_handler (struct instance *o)
190 {
191 ASSERT(o->state == STATE_RETRYING)
192  
193 ModuleLog(o->i, BLOG_INFO, "restarting after crash");
194  
195 start_process(o);
196 }
197  
198 static void process_handler (struct instance *o, int normally, uint8_t normally_exit_status)
199 {
200 ASSERT(o->state == STATE_RUNNING || o->state == STATE_RUNNING_DIE)
201  
202 // free process
203 BProcess_Free(&o->process);
204  
205 // if we were requested to die, die now
206 if (o->state == STATE_RUNNING_DIE) {
207 instance_free(o);
208 return;
209 }
210  
211 ModuleLog(o->i, BLOG_ERROR, "daemon crashed");
212  
213 // start timer
214 BReactor_SetTimer(o->i->params->iparams->reactor, &o->timer);
215  
216 // set state retrying
217 o->state = STATE_RETRYING;
218 }
219  
220 static int opts_func_unknown (void *user, NCDValRef key, NCDValRef val)
221 {
222 struct instance *o = user;
223  
224 if (NCDVal_IsString(key) && NCDVal_StringEquals(key, "retry_time")) {
225 if (!ncd_read_time(val, &o->retry_time)) {
226 ModuleLog(o->i, BLOG_ERROR, "retry_time: bad value");
227 return 0;
228 }
229 return 1;
230 }
231  
232 return 0;
233 }
234  
235 static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
236 {
237 struct instance *o = vo;
238 o->i = i;
239  
240 // read arguments
241 NCDValRef opts_arg = NCDVal_NewInvalid();
242 if (!NCDVal_ListRead(params->args, 1, &o->cmd_arg) &&
243 !NCDVal_ListRead(params->args, 2, &o->cmd_arg, &opts_arg)
244 ) {
245 ModuleLog(i, BLOG_ERROR, "wrong arity");
246 goto fail0;
247 }
248  
249 // init options
250 o->retry_time = DEFAULT_RETRY_TIME;
251 if (!NCDBProcessOpts_Init(&o->opts, opts_arg, opts_func_unknown, o, i, BLOG_CURRENT_CHANNEL)) {
252 goto fail0;
253 }
254  
255 // init timer
256 BTimer_Init(&o->timer, o->retry_time, (BTimer_handler)timer_handler, o);
257  
258 // signal up
259 NCDModuleInst_Backend_Up(i);
260  
261 // try starting process
262 start_process(o);
263 return;
264  
265 fail0:
266 NCDModuleInst_Backend_DeadError(i);
267 }
268  
269 static void instance_free (struct instance *o)
270 {
271 // free timer
272 BReactor_RemoveTimer(o->i->params->iparams->reactor, &o->timer);
273  
274 // free options
275 NCDBProcessOpts_Free(&o->opts);
276  
277 NCDModuleInst_Backend_Dead(o->i);
278 }
279  
280 static void func_die (void *vo)
281 {
282 struct instance *o = vo;
283 ASSERT(o->state != STATE_RUNNING_DIE)
284  
285 // if not running, die immediately
286 if (o->state == STATE_RETRYING) {
287 instance_free(o);
288 return;
289 }
290  
291 // request termination
292 BProcess_Terminate(&o->process);
293  
294 // set state running die
295 o->state = STATE_RUNNING_DIE;
296 }
297  
298 static struct NCDModule modules[] = {
299 {
300 .type = "daemon",
301 .func_new2 = func_new,
302 .func_die = func_die,
303 .alloc_size = sizeof(struct instance)
304 }, {
305 .type = NULL
306 }
307 };
308  
309 const struct NCDModuleGroup ncdmodule_daemon = {
310 .modules = modules
311 };