varnish-cache/bin/varnishd/mgt/mgt_child.c
1
/*-
2
 * Copyright (c) 2006 Verdens Gang AS
3
 * Copyright (c) 2006-2015 Varnish Software AS
4
 * All rights reserved.
5
 *
6
 * Author: Poul-Henning Kamp <phk@phk.freebsd.dk>
7
 *
8
 * Redistribution and use in source and binary forms, with or without
9
 * modification, are permitted provided that the following conditions
10
 * are met:
11
 * 1. Redistributions of source code must retain the above copyright
12
 *    notice, this list of conditions and the following disclaimer.
13
 * 2. Redistributions in binary form must reproduce the above copyright
14
 *    notice, this list of conditions and the following disclaimer in the
15
 *    documentation and/or other materials provided with the distribution.
16
 *
17
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20
 * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
21
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27
 * SUCH DAMAGE.
28
 *
29
 * The mechanics of handling the child process
30
 */
31
32
#include "config.h"
33
34
#include <sys/types.h>
35
#include <sys/wait.h>
36
37
#include <errno.h>
38
#include <fcntl.h>
39
#include <poll.h>
40
#include <signal.h>
41
#include <stdarg.h>
42
#include <stdio.h>
43
#include <string.h>
44
#include <syslog.h>
45
#include <unistd.h>
46
47
#include "mgt.h"
48
49
#include "vbm.h"
50
#include "vcli_serve.h"
51
#include "vev.h"
52
#include "vfil.h"
53
#include "vlu.h"
54
#include "vtim.h"
55
56
#include "common/heritage.h"
57
#include "common/vsmw.h"
58
59
static pid_t            child_pid = -1;
60
61
static struct vbitmap   *fd_map;
62
63
static int              child_cli_in = -1;
64
static int              child_cli_out = -1;
65
static int              child_output = -1;
66
67
static enum {
68
        CH_STOPPED = 0,
69
        CH_STARTING = 1,
70
        CH_RUNNING = 2,
71
        CH_STOPPING = 3,
72
        CH_DIED = 4
73
}                       child_state = CH_STOPPED;
74
75
static const char * const ch_state[] = {
76
        [CH_STOPPED] =  "stopped",
77
        [CH_STARTING] = "starting",
78
        [CH_RUNNING] =  "running",
79
        [CH_STOPPING] = "stopping",
80
        [CH_DIED] =     "died, (restarting)",
81
};
82
83
static struct vev       *ev_poker;
84
static struct vev       *ev_listen;
85
static struct vlu       *child_std_vlu;
86
87
static struct vsb *child_panic = NULL;
88
89
static void mgt_reap_child(void);
90
91
/*=====================================================================
92
 * Panic string evacuation and handling
93
 */
94
95
static void
96 6
mgt_panic_record(pid_t r)
97
{
98
        char time_str[30];
99
100 6
        if (child_panic != NULL)
101 0
                VSB_destroy(&child_panic);
102 6
        child_panic = VSB_new_auto();
103 6
        AN(child_panic);
104 6
        VTIM_format(VTIM_real(), time_str);
105 6
        VSB_printf(child_panic, "Panic at: %s\n", time_str);
106 6
        VSB_quote(child_panic, heritage.panic_str,
107 6
            strnlen(heritage.panic_str, heritage.panic_str_len),
108
            VSB_QUOTE_NONL);
109 6
        AZ(VSB_finish(child_panic));
110 6
        MGT_Complain(C_ERR, "Child (%jd) %s",
111
            (intmax_t)r, VSB_data(child_panic));
112 6
}
113
114
static void
115 6
mgt_panic_clear(void)
116
{
117 6
        VSB_destroy(&child_panic);
118 6
}
119
120
static void v_matchproto_(cli_func_t)
121 4
mch_cli_panic_show(struct cli *cli, const char * const *av, void *priv)
122
{
123
        (void)av;
124
        (void)priv;
125
126 4
        if (!child_panic) {
127 0
                VCLI_SetResult(cli, CLIS_CANT);
128 0
                VCLI_Out(cli,
129
                    "Child has not panicked or panic has been cleared");
130 0
                return;
131
        }
132
133 4
        VCLI_Out(cli, "%s\n", VSB_data(child_panic));
134
}
135
136
static void v_matchproto_(cli_func_t)
137 715
mch_cli_panic_clear(struct cli *cli, const char * const *av, void *priv)
138
{
139
        (void)priv;
140
141 715
        if (av[2] != NULL && strcmp(av[2], "-z")) {
142 0
                VCLI_SetResult(cli, CLIS_PARAM);
143 0
                VCLI_Out(cli, "Unknown parameter \"%s\".", av[2]);
144 0
                return;
145 715
        } else if (av[2] != NULL) {
146 2
                VSC_C_mgt->child_panic = 0;
147 2
                if (child_panic == NULL)
148 1
                        return;
149
        }
150 714
        if (child_panic == NULL) {
151 708
                VCLI_SetResult(cli, CLIS_CANT);
152 708
                VCLI_Out(cli, "No panic to clear");
153 708
                return;
154
        }
155 6
        mgt_panic_clear();
156
}
157
158
/*=====================================================================
159
 * Track the highest file descriptor the parent knows is being used.
160
 *
161
 * This allows the child process to clean/close only a small fraction
162
 * of the possible file descriptors after exec(2).
163
 *
164
 * This is likely to a bit on the low side, as libc and other libraries
165
 * has a tendency to cache file descriptors (syslog, resolver, etc.)
166
 * so we add a margin of 100 fds.
167
 */
168
169
static int              mgt_max_fd;
170
171
#define CLOSE_FD_UP_TO  (mgt_max_fd + 100)
172
173
void
174 2953
MCH_TrackHighFd(int fd)
175
{
176
        /*
177
         * Assert > 0, to catch bogus opens, we know where stdin goes
178
         * in the master process.
179
         */
180 2953
        assert(fd > 0);
181 2953
        if (fd > mgt_max_fd)
182 2224
                mgt_max_fd = fd;
183 2953
}
184
185
/*--------------------------------------------------------------------
186
 * Keep track of which filedescriptors the child should inherit and
187
 * which should be closed after fork()
188
 */
189
190
void
191 5696
MCH_Fd_Inherit(int fd, const char *what)
192
{
193
194 5696
        assert(fd >= 0);
195 5696
        if (fd_map == NULL)
196 727
                fd_map = vbit_new(128);
197 5696
        AN(fd_map);
198 5696
        if (what != NULL)
199 3585
                vbit_set(fd_map, fd);
200
        else
201 2111
                vbit_clr(fd_map, fd);
202 5696
}
203
204
/*=====================================================================
205
 * Listen to stdout+stderr from the child
206
 */
207
208
static int v_matchproto_(vlu_f)
209 1744
child_line(void *priv, const char *p)
210
{
211
        (void)priv;
212
213 1744
        MGT_Complain(C_INFO, "Child (%jd) said %s", (intmax_t)child_pid, p);
214 1744
        return (0);
215
}
216
217
/*--------------------------------------------------------------------
218
 * NB: Notice cleanup call from mgt_reap_child()
219
 */
220
221
static int v_matchproto_(vev_cb_f)
222 1736
child_listener(const struct vev *e, int what)
223
{
224
225 1736
        if ((what & ~VEV__RD) || VLU_Fd(child_std_vlu, child_output)) {
226 13
                ev_listen = NULL;
227 13
                if (e != NULL)
228 6
                        mgt_reap_child();
229 13
                return (1);
230
        }
231 1723
        return (0);
232
}
233
234
/*=====================================================================
235
 * Periodically poke the child, to see that it still lives
236
 */
237
238
static int v_matchproto_(vev_cb_f)
239 253
child_poker(const struct vev *e, int what)
240
{
241 253
        char *r = NULL;
242
        unsigned status;
243
244
        (void)e;
245
        (void)what;
246 253
        if (child_state != CH_RUNNING)
247 0
                return (1);
248 253
        if (child_pid < 0)
249 0
                return (0);
250 253
        if (mgt_cli_askchild(&status, &r, "ping\n") || strncmp("PONG ", r, 5)) {
251 0
                MGT_Complain(C_ERR, "Unexpected reply from ping: %u %s",
252
                    status, r);
253 0
                if (status != CLIS_COMMS)
254 0
                        MCH_Cli_Fail();
255
        }
256 253
        free(r);
257 253
        return 0;
258
}
259
260
/*=====================================================================
261
 * Launch the child process
262
 */
263
264
static void
265 703
mgt_launch_child(struct cli *cli)
266
{
267
        pid_t pid;
268
        unsigned u;
269
        char *p;
270
        struct vev *e;
271
        int i, cp[2];
272
273 703
        if (child_state != CH_STOPPED && child_state != CH_DIED)
274 1
                return;
275
276 703
        child_state = CH_STARTING;
277
278
        /* Open pipe for mgt->child CLI */
279 703
        AZ(pipe(cp));
280 703
        heritage.cli_in = cp[0];
281 703
        MCH_Fd_Inherit(heritage.cli_in, "cli_in");
282 703
        child_cli_out = cp[1];
283
284
        /* Open pipe for child->mgt CLI */
285 703
        AZ(pipe(cp));
286 703
        heritage.cli_out = cp[1];
287 703
        MCH_Fd_Inherit(heritage.cli_out, "cli_out");
288 703
        child_cli_in = cp[0];
289
290
        /*
291
         * Open pipe for child stdout/err
292
         * NB: not inherited, because we dup2() it to stdout/stderr in child
293
         */
294 703
        AZ(pipe(cp));
295 703
        heritage.std_fd = cp[1];
296 703
        child_output = cp[0];
297
298 703
        mgt_SHM_ChildNew();
299
300 703
        AN(heritage.param);
301 703
        AN(heritage.panic_str);
302 703
        if ((pid = fork()) < 0) {
303 0
                perror("Could not fork child");
304 0
                exit(1);                // XXX Harsh ?
305
        }
306 1388
        if (pid == 0) {
307
308
                /* Redirect stdin/out/err */
309 685
                VFIL_null_fd(STDIN_FILENO);
310 685
                assert(dup2(heritage.std_fd, STDOUT_FILENO) == STDOUT_FILENO);
311 685
                assert(dup2(heritage.std_fd, STDERR_FILENO) == STDERR_FILENO);
312
313
                /*
314
                 * Close all FDs the child shouldn't know about
315
                 *
316
                 * We cannot just close these filedescriptors, some random
317
                 * library routine might miss it later on and wantonly close
318
                 * a FD we use at that point in time. (See bug #1841).
319
                 * We close the FD and replace it with /dev/null instead,
320
                 * That prevents security leakage, and gives the library
321
                 * code a valid FD to close when it discovers the changed
322
                 * circumstances.
323
                 */
324 685
                closelog();
325
326 74022
                for (i = STDERR_FILENO + 1; i < CLOSE_FD_UP_TO; i++) {
327 73337
                        if (vbit_test(fd_map, i))
328 2782
                                continue;
329 70555
                        if (close(i) == 0)
330 6850
                                VFIL_null_fd(i);
331
                }
332
333 685
                mgt_ProcTitle("Child");
334
335 685
                heritage.cls = mgt_cls;
336 685
                heritage.ident = VSB_data(vident) + 1;
337
338 685
                VJ_subproc(JAIL_SUBPROC_WORKER);
339
340 685
                heritage.proc_vsmw = VSMW_New(heritage.vsm_fd, 0640, "_.index");
341 685
                AN(heritage.proc_vsmw);
342
343
                /*
344
                 * We pass these two params because child_main needs them
345
                 * Well before it has found its own param struct.
346
                 */
347 685
                child_main(mgt_param.sigsegv_handler,
348 685
                    mgt_param.wthread_stacksize);
349
350
                /*
351
                 * It would be natural to clean VSMW up here, but it is apt
352
                 * to fail in some scenarios because of the fall-back
353
                 * "rm -rf" in mgt_SHM_ChildDestroy() which is there to
354
                 * catch the cases were we don't get here.
355
                 */
356
                // VSMW_Destroy(&heritage.proc_vsmw);
357
358 679
                exit(0);
359
        }
360 703
        assert(pid > 1);
361 703
        MGT_Complain(C_DEBUG, "Child (%jd) Started", (intmax_t)pid);
362 703
        VSC_C_mgt->child_start++;
363
364
        /* Close stuff the child got */
365 703
        closefd(&heritage.std_fd);
366
367 703
        MCH_Fd_Inherit(heritage.cli_in, NULL);
368 703
        closefd(&heritage.cli_in);
369
370 703
        MCH_Fd_Inherit(heritage.cli_out, NULL);
371 703
        closefd(&heritage.cli_out);
372
373 703
        child_std_vlu = VLU_New(child_line, NULL, 0);
374 703
        AN(child_std_vlu);
375
376 703
        AZ(ev_listen);
377 703
        e = VEV_Alloc();
378 703
        XXXAN(e);
379 703
        e->fd = child_output;
380 703
        e->fd_flags = VEV__RD;
381 703
        e->name = "Child listener";
382 703
        e->callback = child_listener;
383 703
        AZ(VEV_Start(mgt_evb, e));
384 703
        ev_listen = e;
385 703
        AZ(ev_poker);
386 703
        if (mgt_param.ping_interval > 0) {
387 703
                e = VEV_Alloc();
388 703
                XXXAN(e);
389 703
                e->timeout = mgt_param.ping_interval;
390 703
                e->callback = child_poker;
391 703
                e->name = "child poker";
392 703
                AZ(VEV_Start(mgt_evb, e));
393 703
                ev_poker = e;
394
        }
395
396 703
        mgt_cli_start_child(child_cli_in, child_cli_out);
397 703
        child_pid = pid;
398
399 703
        if (mgt_push_vcls(cli, &u, &p)) {
400 1
                VCLI_SetResult(cli, u);
401 1
                MGT_Complain(C_ERR, "Child (%jd) Pushing vcls failed:\n%s",
402
                    (intmax_t)child_pid, p);
403 1
                free(p);
404 1
                MCH_Stop_Child();
405 1
                return;
406
        }
407
408 702
        if (mgt_cli_askchild(&u, &p, "start\n")) {
409 0
                VCLI_SetResult(cli, u);
410 0
                MGT_Complain(C_ERR, "Child (%jd) Acceptor start failed:\n%s",
411
                    (intmax_t)child_pid, p);
412 0
                free(p);
413 0
                MCH_Stop_Child();
414 0
                return;
415
        }
416
417 702
        free(p);
418 702
        child_state = CH_RUNNING;
419
}
420
421
/*=====================================================================
422
 * Cleanup when child dies.
423
 */
424
425
static int
426 1
kill_child(void)
427
{
428
        int i, error;
429
430 1
        VJ_master(JAIL_MASTER_KILL);
431 1
        if (MGT_FEATURE(FEATURE_NO_COREDUMP))
432 1
                i = kill(child_pid, SIGKILL);
433
        else
434 0
                i = kill(child_pid, SIGQUIT);
435 1
        error = errno;
436 1
        VJ_master(JAIL_MASTER_LOW);
437 1
        errno = error;
438 1
        return (i);
439
}
440
441
static void
442 703
mgt_reap_child(void)
443
{
444
        int i;
445 703
        int status = 0xffff;
446
        struct vsb *vsb;
447 703
        pid_t r = 0;
448
449 703
        assert(child_pid != -1);
450
451
        /*
452
         * Close the CLI connections
453
         * This signals orderly shut down to child
454
         */
455 703
        mgt_cli_stop_child();
456 703
        if (child_cli_out >= 0)
457 703
                closefd(&child_cli_out);
458 703
        if (child_cli_in >= 0)
459 703
                closefd(&child_cli_in);
460
461
        /* Stop the poker */
462 703
        if (ev_poker != NULL) {
463 703
                VEV_Stop(mgt_evb, ev_poker);
464 703
                free(ev_poker);
465 703
                ev_poker = NULL;
466
        }
467
468
        /* Stop the listener */
469 703
        if (ev_listen != NULL) {
470 697
                VEV_Stop(mgt_evb, ev_listen);
471 697
                free(ev_listen);
472 697
                ev_listen = NULL;
473
        }
474
475
        /* Compose obituary */
476 703
        vsb = VSB_new_auto();
477 703
        XXXAN(vsb);
478
479
        /* Wait for child to die */
480 1413
        for (i = 0; i < mgt_param.cli_timeout; i++) {
481 1413
                r = waitpid(child_pid, &status, WNOHANG);
482 1413
                if (r == child_pid)
483 703
                        break;
484 710
                (void)sleep(1);
485
        }
486 703
        if (r == 0) {
487 0
                VSB_printf(vsb, "Child (%jd) not dying, killing", (intmax_t)r);
488
489
                /* Kick it Jim... */
490 0
                (void)kill_child();
491 0
                r = waitpid(child_pid, &status, 0);
492
        }
493 703
        if (r != child_pid)
494 0
                fprintf(stderr, "WAIT 0x%jd\n", (intmax_t)r);
495 703
        assert(r == child_pid);
496
497 703
        VSB_printf(vsb, "Child (%jd) %s", (intmax_t)r,
498 703
            status ? "died" : "ended");
499 703
        if (WIFEXITED(status) && WEXITSTATUS(status)) {
500 6
                VSB_printf(vsb, " status=%d", WEXITSTATUS(status));
501 6
                exit_status |= 0x20;
502 6
                if (WEXITSTATUS(status) == 1)
503 0
                        VSC_C_mgt->child_exit++;
504
                else
505 6
                        VSC_C_mgt->child_stop++;
506
        }
507 703
        if (WIFSIGNALED(status)) {
508 1
                VSB_printf(vsb, " signal=%d", WTERMSIG(status));
509 1
                exit_status |= 0x40;
510 1
                VSC_C_mgt->child_died++;
511
        }
512
#ifdef WCOREDUMP
513 703
        if (WCOREDUMP(status)) {
514 0
                VSB_printf(vsb, " (core dumped)");
515 0
                exit_status |= 0x80;
516 0
                VSC_C_mgt->child_dump++;
517
        }
518
#endif
519 703
        AZ(VSB_finish(vsb));
520 703
        MGT_Complain(status ? C_ERR : C_INFO, "%s", VSB_data(vsb));
521 703
        VSB_destroy(&vsb);
522
523
        /* Dispose of shared memory but evacuate panic messages first */
524 703
        if (heritage.panic_str[0] != '\0') {
525 6
                mgt_panic_record(r);
526 6
                VSC_C_mgt->child_panic++;
527
        }
528
529 703
        mgt_SHM_ChildDestroy();
530
531 703
        if (child_state == CH_RUNNING)
532 6
                child_state = CH_DIED;
533
534
        /* Pick up any stuff lingering on stdout/stderr */
535 703
        (void)child_listener(NULL, VEV__RD);
536 703
        closefd(&child_output);
537 703
        VLU_Destroy(&child_std_vlu);
538
539 703
        child_pid = -1;
540
541 703
        MGT_Complain(C_DEBUG, "Child cleanup complete");
542
543
        /* XXX number of retries? interval? */
544 703
        for (i = 0; i < 3; i++) {
545 703
                if (MAC_reopen_sockets() == 0)
546 703
                        break;
547
                /* error already logged */
548 0
                (void)sleep(1);
549
        }
550 703
        if (i == 3) {
551
                /* We failed to reopen our listening sockets. No choice
552
                 * but to exit. */
553 0
                MGT_Complain(C_ERR,
554
                    "Could not reopen listening sockets. Exiting.");
555 0
                exit(1);
556
        }
557
558 703
        if (child_state == CH_DIED && mgt_param.auto_restart)
559 0
                mgt_launch_child(NULL);
560 703
        else if (child_state == CH_DIED)
561 6
                child_state = CH_STOPPED;
562 697
        else if (child_state == CH_STOPPING)
563 697
                child_state = CH_STOPPED;
564 703
}
565
566
/*=====================================================================
567
 * If CLI communications with the child process fails, there is nothing
568
 * for us to do but to drag it behind the barn and get it over with.
569
 *
570
 * The typical case is where the child process fails to return a reply
571
 * before the cli_timeout expires.  This invalidates the CLI pipes for
572
 * all future use, as we don't know if the child was just slow and the
573
 * result gets piped later on, or if the child is catatonic.
574
 */
575
576
void
577 1
MCH_Cli_Fail(void)
578
{
579
580 1
        if (child_state != CH_RUNNING && child_state != CH_STARTING)
581 0
                return;
582 1
        if (child_pid < 0)
583 0
                return;
584 1
        if (kill_child() == 0)
585 1
                MGT_Complain(C_ERR, "Child (%jd) not responding to CLI,"
586
                    " killed it.", (intmax_t)child_pid);
587
        else
588 0
                MGT_Complain(C_ERR, "Failed to kill child with PID %jd: %s",
589 0
                    (intmax_t)child_pid, strerror(errno));
590
}
591
592
/*=====================================================================
593
 * Controlled stop of child process
594
 *
595
 * Reaping the child asks for orderly shutdown
596
 */
597
598
void
599 1407
MCH_Stop_Child(void)
600
{
601
602 1407
        if (child_state != CH_RUNNING && child_state != CH_STARTING)
603 710
                return;
604
605 697
        child_state = CH_STOPPING;
606
607 697
        MGT_Complain(C_DEBUG, "Stopping Child");
608
609 697
        mgt_reap_child();
610
}
611
612
/*=====================================================================
613
 */
614
615
int
616 0
MCH_Start_Child(void)
617
{
618 0
        mgt_launch_child(NULL);
619 0
        if (child_state != CH_RUNNING)
620 0
                return (2);
621 0
        return(0);
622
}
623
624
/*====================================================================
625
 * Query if the child is running
626
 */
627
628
int
629 7968
MCH_Running(void)
630
{
631
632 7968
        return (child_pid > 0);
633
}
634
635
/*=====================================================================
636
 * CLI commands
637
 */
638
639
static void v_matchproto_(cli_func_t)
640 707
mch_cli_server_start(struct cli *cli, const char * const *av, void *priv)
641
{
642
643
        (void)av;
644
        (void)priv;
645 707
        if (child_state == CH_STOPPED) {
646 703
                if (mgt_has_vcl()) {
647 703
                        mgt_launch_child(cli);
648
                } else {
649 0
                        VCLI_SetResult(cli, CLIS_CANT);
650 0
                        VCLI_Out(cli, "No VCL available");
651
                }
652
        } else {
653 4
                VCLI_SetResult(cli, CLIS_CANT);
654 4
                VCLI_Out(cli, "Child in state %s", ch_state[child_state]);
655
        }
656 707
}
657
658
static void v_matchproto_(cli_func_t)
659 754
mch_cli_server_stop(struct cli *cli, const char * const *av, void *priv)
660
{
661
662
        (void)av;
663
        (void)priv;
664 754
        if (child_state == CH_RUNNING) {
665 694
                MCH_Stop_Child();
666
        } else {
667 60
                VCLI_SetResult(cli, CLIS_CANT);
668 60
                VCLI_Out(cli, "Child in state %s", ch_state[child_state]);
669
        }
670 754
}
671
672
static void v_matchproto_(cli_func_t)
673 1450
mch_cli_server_status(struct cli *cli, const char * const *av, void *priv)
674
{
675
        (void)av;
676
        (void)priv;
677 1450
        VCLI_Out(cli, "Child in state %s", ch_state[child_state]);
678 1450
}
679
680
static struct cli_proto cli_mch[] = {
681
        { CLICMD_SERVER_STATUS,         "", mch_cli_server_status },
682
        { CLICMD_SERVER_START,          "", mch_cli_server_start },
683
        { CLICMD_SERVER_STOP,           "", mch_cli_server_stop },
684
        { CLICMD_PANIC_SHOW,            "", mch_cli_panic_show },
685
        { CLICMD_PANIC_CLEAR,           "", mch_cli_panic_clear },
686
        { NULL }
687
};
688
689
/*=====================================================================
690
 * This thread is the master thread in the management process.
691
 * The relatively simple task is to start and stop the child process
692
 * and to reincarnate it in case of trouble.
693
 */
694
695
void
696 712
MCH_Init(void)
697
{
698
699 712
        VCLS_AddFunc(mgt_cls, MCF_AUTH, cli_mch);
700 712
}