hastd.c revision 207348
1204076Spjd/*- 2204076Spjd * Copyright (c) 2009-2010 The FreeBSD Foundation 3204076Spjd * All rights reserved. 4204076Spjd * 5204076Spjd * This software was developed by Pawel Jakub Dawidek under sponsorship from 6204076Spjd * the FreeBSD Foundation. 7204076Spjd * 8204076Spjd * Redistribution and use in source and binary forms, with or without 9204076Spjd * modification, are permitted provided that the following conditions 10204076Spjd * are met: 11204076Spjd * 1. Redistributions of source code must retain the above copyright 12204076Spjd * notice, this list of conditions and the following disclaimer. 13204076Spjd * 2. Redistributions in binary form must reproduce the above copyright 14204076Spjd * notice, this list of conditions and the following disclaimer in the 15204076Spjd * documentation and/or other materials provided with the distribution. 16204076Spjd * 17204076Spjd * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND 18204076Spjd * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19204076Spjd * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20204076Spjd * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE 21204076Spjd * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22204076Spjd * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23204076Spjd * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24204076Spjd * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25204076Spjd * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26204076Spjd * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27204076Spjd * SUCH DAMAGE. 28204076Spjd */ 29204076Spjd 30204076Spjd#include <sys/cdefs.h> 31204076Spjd__FBSDID("$FreeBSD: head/sbin/hastd/hastd.c 207348 2010-04-28 22:41:06Z pjd $"); 32204076Spjd 33204076Spjd#include <sys/param.h> 34204076Spjd#include <sys/linker.h> 35204076Spjd#include <sys/module.h> 36204076Spjd#include <sys/wait.h> 37204076Spjd 38204076Spjd#include <assert.h> 39204076Spjd#include <err.h> 40204076Spjd#include <errno.h> 41204076Spjd#include <libutil.h> 42204076Spjd#include <signal.h> 43204076Spjd#include <stdbool.h> 44204076Spjd#include <stdio.h> 45204076Spjd#include <stdlib.h> 46204076Spjd#include <string.h> 47204076Spjd#include <sysexits.h> 48204076Spjd#include <unistd.h> 49204076Spjd 50204076Spjd#include <activemap.h> 51204076Spjd#include <pjdlog.h> 52204076Spjd 53204076Spjd#include "control.h" 54204076Spjd#include "hast.h" 55204076Spjd#include "hast_proto.h" 56204076Spjd#include "hastd.h" 57204076Spjd#include "subr.h" 58204076Spjd 59204076Spjd/* Path to configuration file. */ 60204076Spjdstatic const char *cfgpath = HAST_CONFIG; 61204076Spjd/* Hastd configuration. */ 62204076Spjdstatic struct hastd_config *cfg; 63204076Spjd/* Was SIGCHLD signal received? */ 64204076Spjdstatic bool sigchld_received = false; 65204076Spjd/* Was SIGHUP signal received? */ 66204076Spjdstatic bool sighup_received = false; 67204076Spjd/* Was SIGINT or SIGTERM signal received? */ 68204076Spjdbool sigexit_received = false; 69204076Spjd/* PID file handle. */ 70204076Spjdstruct pidfh *pfh; 71204076Spjd 72204076Spjdstatic void 73204076Spjdusage(void) 74204076Spjd{ 75204076Spjd 76204076Spjd errx(EX_USAGE, "[-dFh] [-c config] [-P pidfile]"); 77204076Spjd} 78204076Spjd 79204076Spjdstatic void 80204076Spjdsighandler(int sig) 81204076Spjd{ 82204076Spjd 83204076Spjd switch (sig) { 84204076Spjd case SIGCHLD: 85204076Spjd sigchld_received = true; 86204076Spjd break; 87204076Spjd case SIGHUP: 88204076Spjd sighup_received = true; 89204076Spjd break; 90204076Spjd default: 91204076Spjd assert(!"invalid condition"); 92204076Spjd } 93204076Spjd} 94204076Spjd 95204076Spjdstatic void 96204076Spjdg_gate_load(void) 97204076Spjd{ 98204076Spjd 99204076Spjd if (modfind("g_gate") == -1) { 100204076Spjd /* Not present in kernel, try loading it. */ 101204076Spjd if (kldload("geom_gate") == -1 || modfind("g_gate") == -1) { 102204076Spjd if (errno != EEXIST) { 103204076Spjd pjdlog_exit(EX_OSERR, 104204076Spjd "Unable to load geom_gate module"); 105204076Spjd } 106204076Spjd } 107204076Spjd } 108204076Spjd} 109204076Spjd 110204076Spjdstatic void 111204076Spjdchild_exit(void) 112204076Spjd{ 113204076Spjd struct hast_resource *res; 114204076Spjd int status; 115204076Spjd pid_t pid; 116204076Spjd 117204076Spjd while ((pid = wait3(&status, WNOHANG, NULL)) > 0) { 118204076Spjd /* Find resource related to the process that just exited. */ 119204076Spjd TAILQ_FOREACH(res, &cfg->hc_resources, hr_next) { 120204076Spjd if (pid == res->hr_workerpid) 121204076Spjd break; 122204076Spjd } 123204076Spjd if (res == NULL) { 124204076Spjd /* 125204076Spjd * This can happen when new connection arrives and we 126204076Spjd * cancel child responsible for the old one. 127204076Spjd */ 128204076Spjd continue; 129204076Spjd } 130204076Spjd pjdlog_prefix_set("[%s] (%s) ", res->hr_name, 131204076Spjd role2str(res->hr_role)); 132204076Spjd if (WEXITSTATUS(status) == 0) { 133204076Spjd pjdlog_debug(1, 134204076Spjd "Worker process exited gracefully (pid=%u).", 135204076Spjd (unsigned int)pid); 136204076Spjd } else { 137204076Spjd pjdlog_error("Worker process failed (pid=%u, status=%d).", 138204076Spjd (unsigned int)pid, WEXITSTATUS(status)); 139204076Spjd } 140206696Spjd proto_close(res->hr_ctrl); 141204076Spjd res->hr_workerpid = 0; 142204076Spjd if (res->hr_role == HAST_ROLE_PRIMARY) { 143207348Spjd if (WEXITSTATUS(status) == EX_TEMPFAIL) { 144207348Spjd sleep(1); 145207348Spjd pjdlog_info("Restarting worker process."); 146207348Spjd hastd_primary(res); 147207348Spjd } else { 148207348Spjd res->hr_role = HAST_ROLE_INIT; 149207348Spjd pjdlog_info("Changing resource role back to %s.", 150207348Spjd role2str(res->hr_role)); 151207348Spjd } 152204076Spjd } 153204076Spjd pjdlog_prefix_set("%s", ""); 154204076Spjd } 155204076Spjd} 156204076Spjd 157204076Spjdstatic void 158204076Spjdhastd_reload(void) 159204076Spjd{ 160204076Spjd 161204076Spjd /* TODO */ 162204076Spjd pjdlog_warning("Configuration reload is not implemented."); 163204076Spjd} 164204076Spjd 165204076Spjdstatic void 166204076Spjdlisten_accept(void) 167204076Spjd{ 168204076Spjd struct hast_resource *res; 169204076Spjd struct proto_conn *conn; 170204076Spjd struct nv *nvin, *nvout, *nverr; 171204076Spjd const char *resname; 172204076Spjd const unsigned char *token; 173204076Spjd char laddr[256], raddr[256]; 174204076Spjd size_t size; 175204076Spjd pid_t pid; 176204076Spjd int status; 177204076Spjd 178204076Spjd proto_local_address(cfg->hc_listenconn, laddr, sizeof(laddr)); 179204076Spjd pjdlog_debug(1, "Accepting connection to %s.", laddr); 180204076Spjd 181204076Spjd if (proto_accept(cfg->hc_listenconn, &conn) < 0) { 182204076Spjd pjdlog_errno(LOG_ERR, "Unable to accept connection %s", laddr); 183204076Spjd return; 184204076Spjd } 185204076Spjd 186204076Spjd proto_local_address(conn, laddr, sizeof(laddr)); 187204076Spjd proto_remote_address(conn, raddr, sizeof(raddr)); 188204076Spjd pjdlog_info("Connection from %s to %s.", laddr, raddr); 189204076Spjd 190204076Spjd nvin = nvout = nverr = NULL; 191204076Spjd 192204076Spjd /* 193204076Spjd * Before receiving any data see if remote host have access to any 194204076Spjd * resource. 195204076Spjd */ 196204076Spjd TAILQ_FOREACH(res, &cfg->hc_resources, hr_next) { 197204076Spjd if (proto_address_match(conn, res->hr_remoteaddr)) 198204076Spjd break; 199204076Spjd } 200204076Spjd if (res == NULL) { 201204076Spjd pjdlog_error("Client %s isn't known.", raddr); 202204076Spjd goto close; 203204076Spjd } 204204076Spjd /* Ok, remote host can access at least one resource. */ 205204076Spjd 206204076Spjd if (hast_proto_recv_hdr(conn, &nvin) < 0) { 207204076Spjd pjdlog_errno(LOG_ERR, "Unable to receive header from %s", 208204076Spjd raddr); 209204076Spjd goto close; 210204076Spjd } 211204076Spjd 212204076Spjd resname = nv_get_string(nvin, "resource"); 213204076Spjd if (resname == NULL) { 214204076Spjd pjdlog_error("No 'resource' field in the header received from %s.", 215204076Spjd raddr); 216204076Spjd goto close; 217204076Spjd } 218204076Spjd pjdlog_debug(2, "%s: resource=%s", raddr, resname); 219204076Spjd token = nv_get_uint8_array(nvin, &size, "token"); 220204076Spjd /* 221204076Spjd * NULL token means that this is first conection. 222204076Spjd */ 223204076Spjd if (token != NULL && size != sizeof(res->hr_token)) { 224204076Spjd pjdlog_error("Received token of invalid size from %s (expected %zu, got %zu).", 225204076Spjd raddr, sizeof(res->hr_token), size); 226204076Spjd goto close; 227204076Spjd } 228204076Spjd 229204076Spjd /* 230204076Spjd * From now on we want to send errors to the remote node. 231204076Spjd */ 232204076Spjd nverr = nv_alloc(); 233204076Spjd 234204076Spjd /* Find resource related to this connection. */ 235204076Spjd TAILQ_FOREACH(res, &cfg->hc_resources, hr_next) { 236204076Spjd if (strcmp(resname, res->hr_name) == 0) 237204076Spjd break; 238204076Spjd } 239204076Spjd /* Have we found the resource? */ 240204076Spjd if (res == NULL) { 241204076Spjd pjdlog_error("No resource '%s' as requested by %s.", 242204076Spjd resname, raddr); 243204076Spjd nv_add_stringf(nverr, "errmsg", "Resource not configured."); 244204076Spjd goto fail; 245204076Spjd } 246204076Spjd 247204076Spjd /* Now that we know resource name setup log prefix. */ 248204076Spjd pjdlog_prefix_set("[%s] (%s) ", res->hr_name, role2str(res->hr_role)); 249204076Spjd 250204076Spjd /* Does the remote host have access to this resource? */ 251204076Spjd if (!proto_address_match(conn, res->hr_remoteaddr)) { 252204076Spjd pjdlog_error("Client %s has no access to the resource.", raddr); 253204076Spjd nv_add_stringf(nverr, "errmsg", "No access to the resource."); 254204076Spjd goto fail; 255204076Spjd } 256204076Spjd /* Is the resource marked as secondary? */ 257204076Spjd if (res->hr_role != HAST_ROLE_SECONDARY) { 258204076Spjd pjdlog_error("We act as %s for the resource and not as %s as requested by %s.", 259204076Spjd role2str(res->hr_role), role2str(HAST_ROLE_SECONDARY), 260204076Spjd raddr); 261204076Spjd nv_add_stringf(nverr, "errmsg", 262204076Spjd "Remote node acts as %s for the resource and not as %s.", 263204076Spjd role2str(res->hr_role), role2str(HAST_ROLE_SECONDARY)); 264204076Spjd goto fail; 265204076Spjd } 266204076Spjd /* Does token (if exists) match? */ 267204076Spjd if (token != NULL && memcmp(token, res->hr_token, 268204076Spjd sizeof(res->hr_token)) != 0) { 269204076Spjd pjdlog_error("Token received from %s doesn't match.", raddr); 270204076Spjd nv_add_stringf(nverr, "errmsg", "Toke doesn't match."); 271204076Spjd goto fail; 272204076Spjd } 273204076Spjd /* 274204076Spjd * If there is no token, but we have half-open connection 275204076Spjd * (only remotein) or full connection (worker process is running) 276204076Spjd * we have to cancel those and accept the new connection. 277204076Spjd */ 278204076Spjd if (token == NULL) { 279204076Spjd assert(res->hr_remoteout == NULL); 280204076Spjd pjdlog_debug(1, "Initial connection from %s.", raddr); 281204076Spjd if (res->hr_workerpid != 0) { 282204076Spjd assert(res->hr_remotein == NULL); 283204076Spjd pjdlog_debug(1, 284204076Spjd "Worker process exists (pid=%u), stopping it.", 285204076Spjd (unsigned int)res->hr_workerpid); 286204076Spjd /* Stop child process. */ 287204076Spjd if (kill(res->hr_workerpid, SIGINT) < 0) { 288204076Spjd pjdlog_errno(LOG_ERR, 289204076Spjd "Unable to stop worker process (pid=%u)", 290204076Spjd (unsigned int)res->hr_workerpid); 291204076Spjd /* 292204076Spjd * Other than logging the problem we 293204076Spjd * ignore it - nothing smart to do. 294204076Spjd */ 295204076Spjd } 296204076Spjd /* Wait for it to exit. */ 297204076Spjd else if ((pid = waitpid(res->hr_workerpid, 298204076Spjd &status, 0)) != res->hr_workerpid) { 299204076Spjd pjdlog_errno(LOG_ERR, 300204076Spjd "Waiting for worker process (pid=%u) failed", 301204076Spjd (unsigned int)res->hr_workerpid); 302204076Spjd /* See above. */ 303207345Spjd } else if (WEXITSTATUS(status) != 0) { 304204076Spjd pjdlog_error("Worker process (pid=%u) exited ungracefully: status=%d.", 305207345Spjd (unsigned int)res->hr_workerpid, 306207345Spjd WEXITSTATUS(status)); 307204076Spjd /* See above. */ 308204076Spjd } else { 309204076Spjd pjdlog_debug(1, 310204076Spjd "Worker process (pid=%u) exited gracefully.", 311204076Spjd (unsigned int)res->hr_workerpid); 312204076Spjd } 313204076Spjd res->hr_workerpid = 0; 314204076Spjd } else if (res->hr_remotein != NULL) { 315204076Spjd char oaddr[256]; 316204076Spjd 317204076Spjd proto_remote_address(conn, oaddr, sizeof(oaddr)); 318204076Spjd pjdlog_debug(1, 319204076Spjd "Canceling half-open connection from %s on connection from %s.", 320204076Spjd oaddr, raddr); 321204076Spjd proto_close(res->hr_remotein); 322204076Spjd res->hr_remotein = NULL; 323204076Spjd } 324204076Spjd } 325204076Spjd 326204076Spjd /* 327204076Spjd * Checks and cleanups are done. 328204076Spjd */ 329204076Spjd 330204076Spjd if (token == NULL) { 331204076Spjd arc4random_buf(res->hr_token, sizeof(res->hr_token)); 332204076Spjd nvout = nv_alloc(); 333204076Spjd nv_add_uint8_array(nvout, res->hr_token, 334204076Spjd sizeof(res->hr_token), "token"); 335204076Spjd if (nv_error(nvout) != 0) { 336204076Spjd pjdlog_common(LOG_ERR, 0, nv_error(nvout), 337204076Spjd "Unable to prepare return header for %s", raddr); 338204076Spjd nv_add_stringf(nverr, "errmsg", 339204076Spjd "Remote node was unable to prepare return header: %s.", 340204076Spjd strerror(nv_error(nvout))); 341204076Spjd goto fail; 342204076Spjd } 343204076Spjd if (hast_proto_send(NULL, conn, nvout, NULL, 0) < 0) { 344204076Spjd int error = errno; 345204076Spjd 346204076Spjd pjdlog_errno(LOG_ERR, "Unable to send response to %s", 347204076Spjd raddr); 348204076Spjd nv_add_stringf(nverr, "errmsg", 349204076Spjd "Remote node was unable to send response: %s.", 350204076Spjd strerror(error)); 351204076Spjd goto fail; 352204076Spjd } 353204076Spjd res->hr_remotein = conn; 354204076Spjd pjdlog_debug(1, "Incoming connection from %s configured.", 355204076Spjd raddr); 356204076Spjd } else { 357204076Spjd res->hr_remoteout = conn; 358204076Spjd pjdlog_debug(1, "Outgoing connection to %s configured.", raddr); 359204076Spjd hastd_secondary(res, nvin); 360204076Spjd } 361204076Spjd nv_free(nvin); 362204076Spjd nv_free(nvout); 363204076Spjd nv_free(nverr); 364204076Spjd pjdlog_prefix_set("%s", ""); 365204076Spjd return; 366204076Spjdfail: 367204076Spjd if (nv_error(nverr) != 0) { 368204076Spjd pjdlog_common(LOG_ERR, 0, nv_error(nverr), 369204076Spjd "Unable to prepare error header for %s", raddr); 370204076Spjd goto close; 371204076Spjd } 372204076Spjd if (hast_proto_send(NULL, conn, nverr, NULL, 0) < 0) { 373204076Spjd pjdlog_errno(LOG_ERR, "Unable to send error to %s", raddr); 374204076Spjd goto close; 375204076Spjd } 376204076Spjdclose: 377204076Spjd if (nvin != NULL) 378204076Spjd nv_free(nvin); 379204076Spjd if (nvout != NULL) 380204076Spjd nv_free(nvout); 381204076Spjd if (nverr != NULL) 382204076Spjd nv_free(nverr); 383204076Spjd proto_close(conn); 384204076Spjd pjdlog_prefix_set("%s", ""); 385204076Spjd} 386204076Spjd 387204076Spjdstatic void 388204076Spjdmain_loop(void) 389204076Spjd{ 390204076Spjd fd_set rfds, wfds; 391204076Spjd int fd, maxfd, ret; 392204076Spjd 393204076Spjd for (;;) { 394204076Spjd if (sigchld_received) { 395204076Spjd sigchld_received = false; 396204076Spjd child_exit(); 397204076Spjd } 398204076Spjd if (sighup_received) { 399204076Spjd sighup_received = false; 400204076Spjd hastd_reload(); 401204076Spjd } 402204076Spjd 403204076Spjd maxfd = 0; 404204076Spjd FD_ZERO(&rfds); 405204076Spjd FD_ZERO(&wfds); 406204076Spjd 407204076Spjd /* Setup descriptors for select(2). */ 408204076Spjd#define SETUP_FD(conn) do { \ 409204076Spjd fd = proto_descriptor(conn); \ 410204076Spjd if (fd >= 0) { \ 411204076Spjd maxfd = fd > maxfd ? fd : maxfd; \ 412204076Spjd FD_SET(fd, &rfds); \ 413204076Spjd FD_SET(fd, &wfds); \ 414204076Spjd } \ 415204076Spjd} while (0) 416204076Spjd SETUP_FD(cfg->hc_controlconn); 417204076Spjd SETUP_FD(cfg->hc_listenconn); 418204076Spjd#undef SETUP_FD 419204076Spjd 420204076Spjd ret = select(maxfd + 1, &rfds, &wfds, NULL, NULL); 421204076Spjd if (ret == -1) { 422204076Spjd if (errno == EINTR) 423204076Spjd continue; 424204076Spjd KEEP_ERRNO((void)pidfile_remove(pfh)); 425204076Spjd pjdlog_exit(EX_OSERR, "select() failed"); 426204076Spjd } 427204076Spjd 428204076Spjd#define ISSET_FD(conn) \ 429204076Spjd (FD_ISSET((fd = proto_descriptor(conn)), &rfds) || FD_ISSET(fd, &wfds)) 430204076Spjd if (ISSET_FD(cfg->hc_controlconn)) 431204076Spjd control_handle(cfg); 432204076Spjd if (ISSET_FD(cfg->hc_listenconn)) 433204076Spjd listen_accept(); 434204076Spjd#undef ISSET_FD 435204076Spjd } 436204076Spjd} 437204076Spjd 438204076Spjdint 439204076Spjdmain(int argc, char *argv[]) 440204076Spjd{ 441204076Spjd const char *pidfile; 442204076Spjd pid_t otherpid; 443204076Spjd bool foreground; 444204076Spjd int debuglevel; 445204076Spjd 446204076Spjd g_gate_load(); 447204076Spjd 448204076Spjd foreground = false; 449204076Spjd debuglevel = 0; 450204076Spjd pidfile = HASTD_PIDFILE; 451204076Spjd 452204076Spjd for (;;) { 453204076Spjd int ch; 454204076Spjd 455204076Spjd ch = getopt(argc, argv, "c:dFhP:"); 456204076Spjd if (ch == -1) 457204076Spjd break; 458204076Spjd switch (ch) { 459204076Spjd case 'c': 460204076Spjd cfgpath = optarg; 461204076Spjd break; 462204076Spjd case 'd': 463204076Spjd debuglevel++; 464204076Spjd break; 465204076Spjd case 'F': 466204076Spjd foreground = true; 467204076Spjd break; 468204076Spjd case 'P': 469204076Spjd pidfile = optarg; 470204076Spjd break; 471204076Spjd case 'h': 472204076Spjd default: 473204076Spjd usage(); 474204076Spjd } 475204076Spjd } 476204076Spjd argc -= optind; 477204076Spjd argv += optind; 478204076Spjd 479204076Spjd pjdlog_debug_set(debuglevel); 480204076Spjd 481204076Spjd pfh = pidfile_open(pidfile, 0600, &otherpid); 482204076Spjd if (pfh == NULL) { 483204076Spjd if (errno == EEXIST) { 484204076Spjd pjdlog_exitx(EX_TEMPFAIL, 485204076Spjd "Another hastd is already running, pid: %jd.", 486204076Spjd (intmax_t)otherpid); 487204076Spjd } 488204076Spjd /* If we cannot create pidfile from other reasons, only warn. */ 489204076Spjd pjdlog_errno(LOG_WARNING, "Cannot open or create pidfile"); 490204076Spjd } 491204076Spjd 492204076Spjd cfg = yy_config_parse(cfgpath); 493204076Spjd assert(cfg != NULL); 494204076Spjd 495204076Spjd signal(SIGHUP, sighandler); 496204076Spjd signal(SIGCHLD, sighandler); 497204076Spjd 498204076Spjd /* Listen on control address. */ 499204076Spjd if (proto_server(cfg->hc_controladdr, &cfg->hc_controlconn) < 0) { 500204076Spjd KEEP_ERRNO((void)pidfile_remove(pfh)); 501204076Spjd pjdlog_exit(EX_OSERR, "Unable to listen on control address %s", 502204076Spjd cfg->hc_controladdr); 503204076Spjd } 504204076Spjd /* Listen for remote connections. */ 505204076Spjd if (proto_server(cfg->hc_listenaddr, &cfg->hc_listenconn) < 0) { 506204076Spjd KEEP_ERRNO((void)pidfile_remove(pfh)); 507204076Spjd pjdlog_exit(EX_OSERR, "Unable to listen on address %s", 508204076Spjd cfg->hc_listenaddr); 509204076Spjd } 510204076Spjd 511204076Spjd if (!foreground) { 512204076Spjd if (daemon(0, 0) < 0) { 513204076Spjd KEEP_ERRNO((void)pidfile_remove(pfh)); 514204076Spjd pjdlog_exit(EX_OSERR, "Unable to daemonize"); 515204076Spjd } 516204076Spjd 517204076Spjd /* Start logging to syslog. */ 518204076Spjd pjdlog_mode_set(PJDLOG_MODE_SYSLOG); 519204076Spjd 520204076Spjd /* Write PID to a file. */ 521204076Spjd if (pidfile_write(pfh) < 0) { 522204076Spjd pjdlog_errno(LOG_WARNING, 523204076Spjd "Unable to write PID to a file"); 524204076Spjd } 525204076Spjd } 526204076Spjd 527204076Spjd main_loop(); 528204076Spjd 529204076Spjd exit(0); 530204076Spjd} 531