hastd.c revision 218041
1157088Simp/*-
2157088Simp * Copyright (c) 2009-2010 The FreeBSD Foundation
3213496Scognet * Copyright (c) 2010-2011 Pawel Jakub Dawidek <pjd@FreeBSD.org>
4157088Simp * All rights reserved.
5157088Simp *
6157088Simp * This software was developed by Pawel Jakub Dawidek under sponsorship from
7157088Simp * the FreeBSD Foundation.
8157088Simp *
9157088Simp * Redistribution and use in source and binary forms, with or without
10157088Simp * modification, are permitted provided that the following conditions
11157088Simp * are met:
12157088Simp * 1. Redistributions of source code must retain the above copyright
13157088Simp *    notice, this list of conditions and the following disclaimer.
14185265Simp * 2. Redistributions in binary form must reproduce the above copyright
15185265Simp *    notice, this list of conditions and the following disclaimer in the
16185265Simp *    documentation and/or other materials provided with the distribution.
17185265Simp *
18185265Simp * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
19185265Simp * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20185265Simp * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21185265Simp * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
22185265Simp * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23185265Simp * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24185265Simp * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25157088Simp * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26157088Simp * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27261684Simp * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28261684Simp * SUCH DAMAGE.
29157088Simp */
30157088Simp
31157088Simp#include <sys/cdefs.h>
32157088Simp__FBSDID("$FreeBSD: head/sbin/hastd/hastd.c 218041 2011-01-28 21:48:15Z pjd $");
33157088Simp
34157088Simp#include <sys/param.h>
35213496Scognet#include <sys/linker.h>
36157088Simp#include <sys/module.h>
37157088Simp#include <sys/wait.h>
38157088Simp
39157088Simp#include <assert.h>
40157088Simp#include <err.h>
41157088Simp#include <errno.h>
42157088Simp#include <libutil.h>
43157088Simp#include <signal.h>
44157088Simp#include <stdbool.h>
45157088Simp#include <stdio.h>
46213496Scognet#include <stdlib.h>
47213496Scognet#include <string.h>
48157088Simp#include <sysexits.h>
49157088Simp#include <unistd.h>
50157088Simp
51157088Simp#include <activemap.h>
52261684Simp#include <pjdlog.h>
53261684Simp
54261684Simp#include "control.h"
55261684Simp#include "event.h"
56261684Simp#include "hast.h"
57261684Simp#include "hast_proto.h"
58157088Simp#include "hastd.h"
59157088Simp#include "hooks.h"
60157088Simp#include "subr.h"
61157088Simp
62157088Simp/* Path to configuration file. */
63157088Simpconst char *cfgpath = HAST_CONFIG;
64157088Simp/* Hastd configuration. */
65236215Simpstatic struct hastd_config *cfg;
66236215Simp/* Was SIGINT or SIGTERM signal received? */
67213496Scognetbool sigexit_received = false;
68213496Scognet/* PID file handle. */
69213496Scognetstruct pidfh *pfh;
70236215Simp
71236215Simp/* How often check for hooks running for too long. */
72157088Simp#define	REPORT_INTERVAL	5
73238788Sandrew
74157088Simpstatic void
75157088Simpusage(void)
76213496Scognet{
77157088Simp
78157088Simp	errx(EX_USAGE, "[-dFh] [-c config] [-P pidfile]");
79239188Simp}
80157088Simp
81157088Simpstatic void
82157088Simpg_gate_load(void)
83157088Simp{
84157088Simp
85157088Simp	if (modfind("g_gate") == -1) {
86213496Scognet		/* Not present in kernel, try loading it. */
87213496Scognet		if (kldload("geom_gate") == -1 || modfind("g_gate") == -1) {
88213496Scognet			if (errno != EEXIST) {
89213496Scognet				pjdlog_exit(EX_OSERR,
90157088Simp				    "Unable to load geom_gate module");
91239188Simp			}
92157088Simp		}
93157088Simp	}
94157088Simp}
95157088Simp
96157088Simpvoid
97157088Simpdescriptors_cleanup(struct hast_resource *res)
98157088Simp{
99239188Simp	struct hast_resource *tres;
100157088Simp
101157088Simp	TAILQ_FOREACH(tres, &cfg->hc_resources, hr_next) {
102157088Simp		if (tres == res) {
103157088Simp			PJDLOG_VERIFY(res->hr_role == HAST_ROLE_SECONDARY ||
104157088Simp			    (res->hr_remotein == NULL &&
105157088Simp			     res->hr_remoteout == NULL));
106157088Simp			continue;
107157088Simp		}
108157088Simp		if (tres->hr_remotein != NULL)
109239188Simp			proto_close(tres->hr_remotein);
110157088Simp		if (tres->hr_remoteout != NULL)
111157088Simp			proto_close(tres->hr_remoteout);
112157088Simp	}
113157088Simp	if (cfg->hc_controlin != NULL)
114157088Simp		proto_close(cfg->hc_controlin);
115157088Simp	proto_close(cfg->hc_controlconn);
116157088Simp	proto_close(cfg->hc_listenconn);
117157088Simp	(void)pidfile_close(pfh);
118157088Simp	hook_fini();
119238788Sandrew	pjdlog_fini();
120238788Sandrew}
121239188Simp
122238788Sandrewstatic void
123238788Sandrewchild_exit_log(unsigned int pid, int status)
124238788Sandrew{
125238788Sandrew
126238788Sandrew	if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
127238788Sandrew		pjdlog_debug(1, "Worker process exited gracefully (pid=%u).",
128238788Sandrew		    pid);
129238788Sandrew	} else if (WIFSIGNALED(status)) {
130238788Sandrew		pjdlog_error("Worker process killed (pid=%u, signal=%d).",
131157088Simp		    pid, WTERMSIG(status));
132157088Simp	} else {
133157088Simp		pjdlog_error("Worker process exited ungracefully (pid=%u, exitcode=%d).",
134157088Simp		    pid, WIFEXITED(status) ? WEXITSTATUS(status) : -1);
135157088Simp	}
136157088Simp}
137157088Simp
138157088Simpstatic void
139157088Simpchild_exit(void)
140157088Simp{
141157088Simp	struct hast_resource *res;
142157088Simp	int status;
143157088Simp	pid_t pid;
144157088Simp
145157088Simp	while ((pid = wait3(&status, WNOHANG, NULL)) > 0) {
146239188Simp		/* Find resource related to the process that just exited. */
147157088Simp		TAILQ_FOREACH(res, &cfg->hc_resources, hr_next) {
148157088Simp			if (pid == res->hr_workerpid)
149157088Simp				break;
150157088Simp		}
151213496Scognet		if (res == NULL) {
152239188Simp			/*
153213496Scognet			 * This can happen when new connection arrives and we
154213496Scognet			 * cancel child responsible for the old one or if this
155213496Scognet			 * was hook which we executed.
156157088Simp			 */
157157088Simp			hook_check_one(pid, status);
158213496Scognet			continue;
159213496Scognet		}
160157088Simp		pjdlog_prefix_set("[%s] (%s) ", res->hr_name,
161157088Simp		    role2str(res->hr_role));
162157088Simp		child_exit_log(pid, status);
163157088Simp		child_cleanup(res);
164238788Sandrew		if (res->hr_role == HAST_ROLE_PRIMARY) {
165157088Simp			/*
166157088Simp			 * Restart child process if it was killed by signal
167157088Simp			 * or exited because of temporary problem.
168213496Scognet			 */
169157088Simp			if (WIFSIGNALED(status) ||
170157088Simp			    (WIFEXITED(status) &&
171157088Simp			     WEXITSTATUS(status) == EX_TEMPFAIL)) {
172157088Simp				sleep(1);
173157088Simp				pjdlog_info("Restarting worker process.");
174234292Smarius				hastd_primary(res);
175236215Simp			} else {
176236215Simp				res->hr_role = HAST_ROLE_INIT;
177236215Simp				pjdlog_info("Changing resource role back to %s.",
178236215Simp				    role2str(res->hr_role));
179236215Simp			}
180213496Scognet		}
181157088Simp		pjdlog_prefix_set("%s", "");
182157088Simp	}
183157088Simp}
184157088Simp
185157088Simpstatic bool
186234292Smariusresource_needs_restart(const struct hast_resource *res0,
187236215Simp    const struct hast_resource *res1)
188236215Simp{
189236215Simp
190236215Simp	assert(strcmp(res0->hr_name, res1->hr_name) == 0);
191236215Simp
192236215Simp	if (strcmp(res0->hr_provname, res1->hr_provname) != 0)
193157088Simp		return (true);
194157088Simp	if (strcmp(res0->hr_localpath, res1->hr_localpath) != 0)
195239190Simp		return (true);
196239190Simp	if (res0->hr_role == HAST_ROLE_INIT ||
197239190Simp	    res0->hr_role == HAST_ROLE_SECONDARY) {
198239190Simp		if (strcmp(res0->hr_remoteaddr, res1->hr_remoteaddr) != 0)
199239190Simp			return (true);
200239190Simp		if (res0->hr_replication != res1->hr_replication)
201239190Simp			return (true);
202239190Simp		if (res0->hr_timeout != res1->hr_timeout)
203239190Simp			return (true);
204239190Simp		if (strcmp(res0->hr_exec, res1->hr_exec) != 0)
205239190Simp			return (true);
206239190Simp	}
207239190Simp	return (false);
208239190Simp}
209239190Simp
210239190Simpstatic bool
211239190Simpresource_needs_reload(const struct hast_resource *res0,
212239190Simp    const struct hast_resource *res1)
213239190Simp{
214239190Simp
215239190Simp	assert(strcmp(res0->hr_name, res1->hr_name) == 0);
216239190Simp	assert(strcmp(res0->hr_provname, res1->hr_provname) == 0);
217239190Simp	assert(strcmp(res0->hr_localpath, res1->hr_localpath) == 0);
218239190Simp
219239190Simp	if (res0->hr_role != HAST_ROLE_PRIMARY)
220239190Simp		return (false);
221239190Simp
222239190Simp	if (strcmp(res0->hr_remoteaddr, res1->hr_remoteaddr) != 0)
223239190Simp		return (true);
224239190Simp	if (res0->hr_replication != res1->hr_replication)
225239190Simp		return (true);
226239190Simp	if (res0->hr_timeout != res1->hr_timeout)
227239190Simp		return (true);
228239190Simp	if (strcmp(res0->hr_exec, res1->hr_exec) != 0)
229213496Scognet		return (true);
230157088Simp	return (false);
231157088Simp}
232157088Simp
233157088Simpstatic void
234157088Simpresource_reload(const struct hast_resource *res)
235248904Sian{
236213496Scognet	struct nv *nvin, *nvout;
237248904Sian	int error;
238248904Sian
239248904Sian	assert(res->hr_role == HAST_ROLE_PRIMARY);
240248904Sian
241248904Sian	nvout = nv_alloc();
242248904Sian	nv_add_uint8(nvout, HASTCTL_RELOAD, "cmd");
243248904Sian	nv_add_string(nvout, res->hr_remoteaddr, "remoteaddr");
244248904Sian	nv_add_int32(nvout, (int32_t)res->hr_replication, "replication");
245213496Scognet	nv_add_int32(nvout, (int32_t)res->hr_timeout, "timeout");
246213496Scognet	nv_add_string(nvout, res->hr_exec, "exec");
247157088Simp	if (nv_error(nvout) != 0) {
248157088Simp		nv_free(nvout);
249157088Simp		pjdlog_error("Unable to allocate header for reload message.");
250238788Sandrew		return;
251238788Sandrew	}
252238788Sandrew	if (hast_proto_send(res, res->hr_ctrl, nvout, NULL, 0) < 0) {
253238788Sandrew		pjdlog_errno(LOG_ERR, "Unable to send reload message");
254238788Sandrew		nv_free(nvout);
255238788Sandrew		return;
256238788Sandrew	}
257238788Sandrew	nv_free(nvout);
258238788Sandrew
259238788Sandrew	/* Receive response. */
260238788Sandrew	if (hast_proto_recv_hdr(res->hr_ctrl, &nvin) < 0) {
261238788Sandrew		pjdlog_errno(LOG_ERR, "Unable to receive reload reply");
262238788Sandrew		return;
263238788Sandrew	}
264238788Sandrew	error = nv_get_int16(nvin, "error");
265238788Sandrew	nv_free(nvin);
266238788Sandrew	if (error != 0) {
267238788Sandrew		pjdlog_common(LOG_ERR, 0, error, "Reload failed");
268238788Sandrew		return;
269238788Sandrew	}
270157088Simp}
271157088Simp
272157088Simpstatic void
273157088Simphastd_reload(void)
274157088Simp{
275157088Simp	struct hastd_config *newcfg;
276157088Simp	struct hast_resource *nres, *cres, *tres;
277157088Simp	uint8_t role;
278157088Simp
279157088Simp	pjdlog_info("Reloading configuration...");
280157088Simp
281157088Simp	newcfg = yy_config_parse(cfgpath, false);
282157088Simp	if (newcfg == NULL)
283157088Simp		goto failed;
284157088Simp
285157088Simp	/*
286157088Simp	 * Check if control address has changed.
287157088Simp	 */
288157088Simp	if (strcmp(cfg->hc_controladdr, newcfg->hc_controladdr) != 0) {
289157088Simp		if (proto_server(newcfg->hc_controladdr,
290157088Simp		    &newcfg->hc_controlconn) < 0) {
291157088Simp			pjdlog_errno(LOG_ERR,
292157088Simp			    "Unable to listen on control address %s",
293157088Simp			    newcfg->hc_controladdr);
294157088Simp			goto failed;
295157088Simp		}
296157088Simp	}
297157088Simp	/*
298234292Smarius	 * Check if listen address has changed.
299234292Smarius	 */
300213496Scognet	if (strcmp(cfg->hc_listenaddr, newcfg->hc_listenaddr) != 0) {
301213496Scognet		if (proto_server(newcfg->hc_listenaddr,
302213496Scognet		    &newcfg->hc_listenconn) < 0) {
303213496Scognet			pjdlog_errno(LOG_ERR, "Unable to listen on address %s",
304213496Scognet			    newcfg->hc_listenaddr);
305234281Smarius			goto failed;
306213496Scognet		}
307213496Scognet	}
308213496Scognet	/*
309213496Scognet	 * Only when both control and listen sockets are successfully
310234281Smarius	 * initialized switch them to new configuration.
311213496Scognet	 */
312213496Scognet	if (newcfg->hc_controlconn != NULL) {
313213496Scognet		pjdlog_info("Control socket changed from %s to %s.",
314213496Scognet		    cfg->hc_controladdr, newcfg->hc_controladdr);
315213496Scognet		proto_close(cfg->hc_controlconn);
316213496Scognet		cfg->hc_controlconn = newcfg->hc_controlconn;
317213496Scognet		newcfg->hc_controlconn = NULL;
318213496Scognet		strlcpy(cfg->hc_controladdr, newcfg->hc_controladdr,
319213496Scognet		    sizeof(cfg->hc_controladdr));
320213496Scognet	}
321298352Spfg	if (newcfg->hc_listenconn != NULL) {
322213496Scognet		pjdlog_info("Listen socket changed from %s to %s.",
323213496Scognet		    cfg->hc_listenaddr, newcfg->hc_listenaddr);
324213496Scognet		proto_close(cfg->hc_listenconn);
325213496Scognet		cfg->hc_listenconn = newcfg->hc_listenconn;
326213496Scognet		newcfg->hc_listenconn = NULL;
327213496Scognet		strlcpy(cfg->hc_listenaddr, newcfg->hc_listenaddr,
328213496Scognet		    sizeof(cfg->hc_listenaddr));
329234281Smarius	}
330213496Scognet
331213496Scognet	/*
332213496Scognet	 * Stop and remove resources that were removed from the configuration.
333213496Scognet	 */
334213496Scognet	TAILQ_FOREACH_SAFE(cres, &cfg->hc_resources, hr_next, tres) {
335213496Scognet		TAILQ_FOREACH(nres, &newcfg->hc_resources, hr_next) {
336213496Scognet			if (strcmp(cres->hr_name, nres->hr_name) == 0)
337213496Scognet				break;
338213496Scognet		}
339213496Scognet		if (nres == NULL) {
340213496Scognet			control_set_role(cres, HAST_ROLE_INIT);
341213496Scognet			TAILQ_REMOVE(&cfg->hc_resources, cres, hr_next);
342213496Scognet			pjdlog_info("Resource %s removed.", cres->hr_name);
343213496Scognet			free(cres);
344213496Scognet		}
345213496Scognet	}
346213496Scognet	/*
347213496Scognet	 * Move new resources to the current configuration.
348213496Scognet	 */
349213496Scognet	TAILQ_FOREACH_SAFE(nres, &newcfg->hc_resources, hr_next, tres) {
350213496Scognet		TAILQ_FOREACH(cres, &cfg->hc_resources, hr_next) {
351213496Scognet			if (strcmp(cres->hr_name, nres->hr_name) == 0)
352213496Scognet				break;
353213496Scognet		}
354213496Scognet		if (cres == NULL) {
355157088Simp			TAILQ_REMOVE(&newcfg->hc_resources, nres, hr_next);
356157088Simp			TAILQ_INSERT_TAIL(&cfg->hc_resources, nres, hr_next);
357157088Simp			pjdlog_info("Resource %s added.", nres->hr_name);
358157088Simp		}
359298352Spfg	}
360213496Scognet	/*
361213496Scognet	 * Deal with modified resources.
362157088Simp	 * Depending on what has changed exactly we might want to perform
363157088Simp	 * different actions.
364213496Scognet	 *
365157088Simp	 * We do full resource restart in the following situations:
366157088Simp	 * Resource role is INIT or SECONDARY.
367157088Simp	 * Resource role is PRIMARY and path to local component or provider
368157088Simp	 * name has changed.
369157088Simp	 * In case of PRIMARY, the worker process will be killed and restarted,
370157088Simp	 * which also means removing /dev/hast/<name> provider and
371157088Simp	 * recreating it.
372239530Shselasky	 *
373239530Shselasky	 * We do just reload (send SIGHUP to worker process) if we act as
374157088Simp	 * PRIMARY, but only if remote address, replication mode, timeout or
375157088Simp	 * execution path has changed. For those, there is no need to restart
376157088Simp	 * worker process.
377157088Simp	 * If PRIMARY receives SIGHUP, it will reconnect if remote address or
378157088Simp	 * replication mode has changed or simply set new timeout if only
379239530Shselasky	 * timeout has changed.
380239530Shselasky	 */
381234292Smarius	TAILQ_FOREACH_SAFE(nres, &newcfg->hc_resources, hr_next, tres) {
382157088Simp		TAILQ_FOREACH(cres, &cfg->hc_resources, hr_next) {
383157088Simp			if (strcmp(cres->hr_name, nres->hr_name) == 0)
384157088Simp				break;
385157088Simp		}
386157088Simp		assert(cres != NULL);
387157088Simp		if (resource_needs_restart(cres, nres)) {
388157088Simp			pjdlog_info("Resource %s configuration was modified, restarting it.",
389157088Simp			    cres->hr_name);
390157088Simp			role = cres->hr_role;
391157088Simp			control_set_role(cres, HAST_ROLE_INIT);
392239530Shselasky			TAILQ_REMOVE(&cfg->hc_resources, cres, hr_next);
393239530Shselasky			free(cres);
394234292Smarius			TAILQ_REMOVE(&newcfg->hc_resources, nres, hr_next);
395157088Simp			TAILQ_INSERT_TAIL(&cfg->hc_resources, nres, hr_next);
396157088Simp			control_set_role(nres, role);
397157088Simp		} else if (resource_needs_reload(cres, nres)) {
398157088Simp			pjdlog_info("Resource %s configuration was modified, reloading it.",
399157088Simp			    cres->hr_name);
400157088Simp			strlcpy(cres->hr_remoteaddr, nres->hr_remoteaddr,
401157088Simp			    sizeof(cres->hr_remoteaddr));
402157088Simp			cres->hr_replication = nres->hr_replication;
403213496Scognet			cres->hr_timeout = nres->hr_timeout;
404157088Simp			strlcpy(cres->hr_exec, nres->hr_exec,
405216227Skevlo			    sizeof(cres->hr_exec));
406157088Simp			if (cres->hr_workerpid != 0)
407213496Scognet				resource_reload(cres);
408213496Scognet		}
409213496Scognet	}
410213496Scognet
411234281Smarius	yy_config_free(newcfg);
412234281Smarius	pjdlog_info("Configuration reloaded successfully.");
413234281Smarius	return;
414234281Smariusfailed:
415213498Scognet	if (newcfg != NULL) {
416157088Simp		if (newcfg->hc_controlconn != NULL)
417157088Simp			proto_close(newcfg->hc_controlconn);
418157088Simp		if (newcfg->hc_listenconn != NULL)
419234281Smarius			proto_close(newcfg->hc_listenconn);
420157088Simp		yy_config_free(newcfg);
421213496Scognet	}
422213498Scognet	pjdlog_warning("Configuration not reloaded.");
423157088Simp}
424157088Simp
425157088Simpstatic void
426157088Simpterminate_workers(void)
427213496Scognet{
428157088Simp	struct hast_resource *res;
429180693Sstas
430157088Simp	pjdlog_info("Termination signal received, exiting.");
431213496Scognet	TAILQ_FOREACH(res, &cfg->hc_resources, hr_next) {
432213496Scognet		if (res->hr_workerpid == 0)
433213496Scognet			continue;
434180693Sstas		pjdlog_info("Terminating worker process (resource=%s, role=%s, pid=%u).",
435157088Simp		    res->hr_name, role2str(res->hr_role), res->hr_workerpid);
436180693Sstas		if (kill(res->hr_workerpid, SIGTERM) == 0)
437180693Sstas			continue;
438180693Sstas		pjdlog_errno(LOG_WARNING,
439157088Simp		    "Unable to send signal to worker process (resource=%s, role=%s, pid=%u).",
440213496Scognet		    res->hr_name, role2str(res->hr_role), res->hr_workerpid);
441213496Scognet	}
442180693Sstas}
443213496Scognet
444180693Sstasstatic void
445157088Simplisten_accept(void)
446180693Sstas{
447213496Scognet	struct hast_resource *res;
448180693Sstas	struct proto_conn *conn;
449213496Scognet	struct nv *nvin, *nvout, *nverr;
450180693Sstas	const char *resname;
451157088Simp	const unsigned char *token;
452180693Sstas	char laddr[256], raddr[256];
453157088Simp	size_t size;
454180693Sstas	pid_t pid;
455180693Sstas	int status;
456180693Sstas
457180693Sstas	proto_local_address(cfg->hc_listenconn, laddr, sizeof(laddr));
458180693Sstas	pjdlog_debug(1, "Accepting connection to %s.", laddr);
459180693Sstas
460180693Sstas	if (proto_accept(cfg->hc_listenconn, &conn) < 0) {
461180693Sstas		pjdlog_errno(LOG_ERR, "Unable to accept connection %s", laddr);
462180693Sstas		return;
463180693Sstas	}
464157088Simp
465213496Scognet	proto_local_address(conn, laddr, sizeof(laddr));
466213496Scognet	proto_remote_address(conn, raddr, sizeof(raddr));
467213496Scognet	pjdlog_info("Connection from %s to %s.", raddr, laddr);
468213496Scognet
469213496Scognet	/* Error in setting timeout is not critical, but why should it fail? */
470213496Scognet	if (proto_timeout(conn, HAST_TIMEOUT) < 0)
471213496Scognet		pjdlog_errno(LOG_WARNING, "Unable to set connection timeout");
472157088Simp
473213496Scognet	nvin = nvout = nverr = NULL;
474157088Simp
475157088Simp	/*
476235715Simp	 * Before receiving any data see if remote host have access to any
477235715Simp	 * resource.
478235715Simp	 */
479235715Simp	TAILQ_FOREACH(res, &cfg->hc_resources, hr_next) {
480235715Simp		if (proto_address_match(conn, res->hr_remoteaddr))
481235715Simp			break;
482235715Simp	}
483235715Simp	if (res == NULL) {
484235715Simp		pjdlog_error("Client %s isn't known.", raddr);
485298352Spfg		goto close;
486236215Simp	}
487235715Simp	/* Ok, remote host can access at least one resource. */
488235715Simp
489236215Simp	if (hast_proto_recv_hdr(conn, &nvin) < 0) {
490235715Simp		pjdlog_errno(LOG_ERR, "Unable to receive header from %s",
491236215Simp		    raddr);
492235715Simp		goto close;
493235715Simp	}
494235715Simp
495235715Simp	resname = nv_get_string(nvin, "resource");
496236215Simp	if (resname == NULL) {
497235715Simp		pjdlog_error("No 'resource' field in the header received from %s.",
498235715Simp		    raddr);
499235715Simp		goto close;
500235715Simp	}
501235715Simp	pjdlog_debug(2, "%s: resource=%s", raddr, resname);
502235715Simp	token = nv_get_uint8_array(nvin, &size, "token");
503235715Simp	/*
504236535Simp	 * NULL token means that this is first conection.
505235715Simp	 */
506235715Simp	if (token != NULL && size != sizeof(res->hr_token)) {
507298848Spfg		pjdlog_error("Received token of invalid size from %s (expected %zu, got %zu).",
508235715Simp		    raddr, sizeof(res->hr_token), size);
509235715Simp		goto close;
510235715Simp	}
511235715Simp
512235715Simp	/*
513235715Simp	 * From now on we want to send errors to the remote node.
514235715Simp	 */
515235715Simp	nverr = nv_alloc();
516235715Simp
517235715Simp	/* Find resource related to this connection. */
518235715Simp	TAILQ_FOREACH(res, &cfg->hc_resources, hr_next) {
519235715Simp		if (strcmp(resname, res->hr_name) == 0)
520235715Simp			break;
521235715Simp	}
522236215Simp	/* Have we found the resource? */
523236215Simp	if (res == NULL) {
524236215Simp		pjdlog_error("No resource '%s' as requested by %s.",
525235715Simp		    resname, raddr);
526235715Simp		nv_add_stringf(nverr, "errmsg", "Resource not configured.");
527236215Simp		goto fail;
528236215Simp	}
529157088Simp
530236215Simp	/* Now that we know resource name setup log prefix. */
531235715Simp	pjdlog_prefix_set("[%s] (%s) ", res->hr_name, role2str(res->hr_role));
532157088Simp
533213496Scognet	/* Does the remote host have access to this resource? */
534157088Simp	if (!proto_address_match(conn, res->hr_remoteaddr)) {
535261684Simp		pjdlog_error("Client %s has no access to the resource.", raddr);
536261684Simp		nv_add_stringf(nverr, "errmsg", "No access to the resource.");
537236215Simp		goto fail;
538235715Simp	}
539234292Smarius	/* Is the resource marked as secondary? */
540213496Scognet	if (res->hr_role != HAST_ROLE_SECONDARY) {
541213496Scognet		pjdlog_error("We act as %s for the resource and not as %s as requested by %s.",
542213496Scognet		    role2str(res->hr_role), role2str(HAST_ROLE_SECONDARY),
543239168Simp		    raddr);
544238788Sandrew		nv_add_stringf(nverr, "errmsg",
545238788Sandrew		    "Remote node acts as %s for the resource and not as %s.",
546238788Sandrew		    role2str(res->hr_role), role2str(HAST_ROLE_SECONDARY));
547238788Sandrew		goto fail;
548238788Sandrew	}
549238788Sandrew	/* Does token (if exists) match? */
550213496Scognet	if (token != NULL && memcmp(token, res->hr_token,
551157088Simp	    sizeof(res->hr_token)) != 0) {
552157088Simp		pjdlog_error("Token received from %s doesn't match.", raddr);
553239188Simp		nv_add_stringf(nverr, "errmsg", "Token doesn't match.");
554239188Simp		goto fail;
555239188Simp	}
556239188Simp	/*
557239188Simp	 * If there is no token, but we have half-open connection
558213496Scognet	 * (only remotein) or full connection (worker process is running)
559213496Scognet	 * we have to cancel those and accept the new connection.
560236658Simp	 */
561213496Scognet	if (token == NULL) {
562213496Scognet		assert(res->hr_remoteout == NULL);
563157088Simp		pjdlog_debug(1, "Initial connection from %s.", raddr);
564157088Simp		if (res->hr_workerpid != 0) {
565239168Simp			assert(res->hr_remotein == NULL);
566157088Simp			pjdlog_debug(1,
567239168Simp			    "Worker process exists (pid=%u), stopping it.",
568239168Simp			    (unsigned int)res->hr_workerpid);
569239168Simp			/* Stop child process. */
570213496Scognet			if (kill(res->hr_workerpid, SIGINT) < 0) {
571239168Simp				pjdlog_errno(LOG_ERR,
572239168Simp				    "Unable to stop worker process (pid=%u)",
573239168Simp				    (unsigned int)res->hr_workerpid);
574210040Scognet				/*
575239168Simp				 * Other than logging the problem we
576157088Simp				 * ignore it - nothing smart to do.
577213496Scognet				 */
578213496Scognet			}
579213496Scognet			/* Wait for it to exit. */
580234281Smarius			else if ((pid = waitpid(res->hr_workerpid,
581213496Scognet			    &status, 0)) != res->hr_workerpid) {
582213496Scognet				/* We can only log the problem. */
583157088Simp				pjdlog_errno(LOG_ERR,
584157088Simp				    "Waiting for worker process (pid=%u) failed",
585157088Simp				    (unsigned int)res->hr_workerpid);
586157088Simp			} else {
587157088Simp				child_exit_log(res->hr_workerpid, status);
588157088Simp			}
589157088Simp			child_cleanup(res);
590234281Smarius		} else if (res->hr_remotein != NULL) {
591234281Smarius			char oaddr[256];
592213496Scognet
593213496Scognet			proto_remote_address(res->hr_remotein, oaddr,
594234292Smarius			    sizeof(oaddr));
595238788Sandrew			pjdlog_debug(1,
596238788Sandrew			    "Canceling half-open connection from %s on connection from %s.",
597238788Sandrew			    oaddr, raddr);
598238788Sandrew			proto_close(res->hr_remotein);
599238788Sandrew			res->hr_remotein = NULL;
600238788Sandrew		}
601213498Scognet	}
602238788Sandrew
603234281Smarius	/*
604213496Scognet	 * Checks and cleanups are done.
605213496Scognet	 */
606213496Scognet
607236658Simp	if (token == NULL) {
608213496Scognet		arc4random_buf(res->hr_token, sizeof(res->hr_token));
609213496Scognet		nvout = nv_alloc();
610213496Scognet		nv_add_uint8_array(nvout, res->hr_token,
611213496Scognet		    sizeof(res->hr_token), "token");
612235715Simp		if (nv_error(nvout) != 0) {
613235715Simp			pjdlog_common(LOG_ERR, 0, nv_error(nvout),
614235715Simp			    "Unable to prepare return header for %s", raddr);
615213496Scognet			nv_add_stringf(nverr, "errmsg",
616213496Scognet			    "Remote node was unable to prepare return header: %s.",
617157088Simp			    strerror(nv_error(nvout)));
618157088Simp			goto fail;
619213496Scognet		}
620157088Simp		if (hast_proto_send(NULL, conn, nvout, NULL, 0) < 0) {
621157088Simp			int error = errno;
622213496Scognet
623157088Simp			pjdlog_errno(LOG_ERR, "Unable to send response to %s",
624157088Simp			    raddr);
625157088Simp			nv_add_stringf(nverr, "errmsg",
626157088Simp			    "Remote node was unable to send response: %s.",
627157088Simp			    strerror(error));
628157088Simp			goto fail;
629157088Simp		}
630157088Simp		res->hr_remotein = conn;
631157088Simp		pjdlog_debug(1, "Incoming connection from %s configured.",
632157088Simp		    raddr);
633157088Simp	} else {
634157088Simp		res->hr_remoteout = conn;
635157088Simp		pjdlog_debug(1, "Outgoing connection to %s configured.", raddr);
636157088Simp		hastd_secondary(res, nvin);
637298055Spfg	}
638157088Simp	nv_free(nvin);
639157088Simp	nv_free(nvout);
640157088Simp	nv_free(nverr);
641157088Simp	pjdlog_prefix_set("%s", "");
642157088Simp	return;
643157088Simpfail:
644157088Simp	if (nv_error(nverr) != 0) {
645157088Simp		pjdlog_common(LOG_ERR, 0, nv_error(nverr),
646157088Simp		    "Unable to prepare error header for %s", raddr);
647157088Simp		goto close;
648157088Simp	}
649157088Simp	if (hast_proto_send(NULL, conn, nverr, NULL, 0) < 0) {
650157088Simp		pjdlog_errno(LOG_ERR, "Unable to send error to %s", raddr);
651157088Simp		goto close;
652157088Simp	}
653157088Simpclose:
654157088Simp	if (nvin != NULL)
655157088Simp		nv_free(nvin);
656157088Simp	if (nvout != NULL)
657157088Simp		nv_free(nvout);
658157088Simp	if (nverr != NULL)
659157088Simp		nv_free(nverr);
660157088Simp	proto_close(conn);
661261684Simp	pjdlog_prefix_set("%s", "");
662290420Scognet}
663290420Scognet
664290420Scognetstatic void
665290420Scognetmain_loop(void)
666261684Simp{
667261684Simp	struct hast_resource *res;
668157088Simp	struct timeval seltimeout;
669157088Simp	struct timespec sigtimeout;
670157088Simp	int fd, maxfd, ret, signo;
671157088Simp	sigset_t mask;
672157088Simp	fd_set rfds;
673157088Simp
674157088Simp	seltimeout.tv_sec = REPORT_INTERVAL;
675157088Simp	seltimeout.tv_usec = 0;
676157088Simp	sigtimeout.tv_sec = 0;
677157088Simp	sigtimeout.tv_nsec = 0;
678157088Simp
679157088Simp	PJDLOG_VERIFY(sigemptyset(&mask) == 0);
680213496Scognet	PJDLOG_VERIFY(sigaddset(&mask, SIGHUP) == 0);
681185491Sstas	PJDLOG_VERIFY(sigaddset(&mask, SIGINT) == 0);
682185491Sstas	PJDLOG_VERIFY(sigaddset(&mask, SIGTERM) == 0);
683185491Sstas	PJDLOG_VERIFY(sigaddset(&mask, SIGCHLD) == 0);
684185491Sstas
685236215Simp	pjdlog_info("Started successfully, running protocol version %d.",
686213496Scognet	    HAST_PROTO_VERSION);
687236215Simp
688236215Simp	for (;;) {
689236215Simp		while ((signo = sigtimedwait(&mask, NULL, &sigtimeout)) != -1) {
690235715Simp			switch (signo) {
691235715Simp			case SIGINT:
692235715Simp			case SIGTERM:
693235715Simp				sigexit_received = true;
694235715Simp				terminate_workers();
695213496Scognet				proto_close(cfg->hc_controlconn);
696157088Simp				exit(EX_OK);
697157088Simp				break;
698157088Simp			case SIGCHLD:
699157088Simp				child_exit();
700157088Simp				break;
701157088Simp			case SIGHUP:
702234281Smarius				hastd_reload();
703157088Simp				break;
704157088Simp			default:
705157088Simp				assert(!"invalid condition");
706157088Simp			}
707157088Simp		}
708157088Simp
709157088Simp		/* Setup descriptors for select(2). */
710157088Simp		FD_ZERO(&rfds);
711157088Simp		maxfd = fd = proto_descriptor(cfg->hc_controlconn);
712261684Simp		assert(fd >= 0);
713269959Simp		FD_SET(fd, &rfds);
714269959Simp		fd = proto_descriptor(cfg->hc_listenconn);
715261684Simp		assert(fd >= 0);
716269959Simp		FD_SET(fd, &rfds);
717269959Simp		maxfd = fd > maxfd ? fd : maxfd;
718261684Simp		TAILQ_FOREACH(res, &cfg->hc_resources, hr_next) {
719			if (res->hr_event == NULL)
720				continue;
721			fd = proto_descriptor(res->hr_event);
722			assert(fd >= 0);
723			FD_SET(fd, &rfds);
724			maxfd = fd > maxfd ? fd : maxfd;
725		}
726
727		assert(maxfd + 1 <= (int)FD_SETSIZE);
728		ret = select(maxfd + 1, &rfds, NULL, NULL, &seltimeout);
729		if (ret == 0)
730			hook_check();
731		else if (ret == -1) {
732			if (errno == EINTR)
733				continue;
734			KEEP_ERRNO((void)pidfile_remove(pfh));
735			pjdlog_exit(EX_OSERR, "select() failed");
736		}
737
738		if (FD_ISSET(proto_descriptor(cfg->hc_controlconn), &rfds))
739			control_handle(cfg);
740		if (FD_ISSET(proto_descriptor(cfg->hc_listenconn), &rfds))
741			listen_accept();
742		TAILQ_FOREACH(res, &cfg->hc_resources, hr_next) {
743			if (res->hr_event == NULL)
744				continue;
745			if (FD_ISSET(proto_descriptor(res->hr_event), &rfds)) {
746				if (event_recv(res) == 0)
747					continue;
748				/* The worker process exited? */
749				proto_close(res->hr_event);
750				res->hr_event = NULL;
751			}
752		}
753	}
754}
755
756static void
757dummy_sighandler(int sig __unused)
758{
759	/* Nothing to do. */
760}
761
762int
763main(int argc, char *argv[])
764{
765	const char *pidfile;
766	pid_t otherpid;
767	bool foreground;
768	int debuglevel;
769	sigset_t mask;
770
771	foreground = false;
772	debuglevel = 0;
773	pidfile = HASTD_PIDFILE;
774
775	for (;;) {
776		int ch;
777
778		ch = getopt(argc, argv, "c:dFhP:");
779		if (ch == -1)
780			break;
781		switch (ch) {
782		case 'c':
783			cfgpath = optarg;
784			break;
785		case 'd':
786			debuglevel++;
787			break;
788		case 'F':
789			foreground = true;
790			break;
791		case 'P':
792			pidfile = optarg;
793			break;
794		case 'h':
795		default:
796			usage();
797		}
798	}
799	argc -= optind;
800	argv += optind;
801
802	pjdlog_init(PJDLOG_MODE_STD);
803	pjdlog_debug_set(debuglevel);
804
805	g_gate_load();
806
807	pfh = pidfile_open(pidfile, 0600, &otherpid);
808	if (pfh == NULL) {
809		if (errno == EEXIST) {
810			pjdlog_exitx(EX_TEMPFAIL,
811			    "Another hastd is already running, pid: %jd.",
812			    (intmax_t)otherpid);
813		}
814		/* If we cannot create pidfile from other reasons, only warn. */
815		pjdlog_errno(LOG_WARNING, "Unable to open or create pidfile");
816	}
817
818	cfg = yy_config_parse(cfgpath, true);
819	assert(cfg != NULL);
820
821	/*
822	 * Restore default actions for interesting signals in case parent
823	 * process (like init(8)) decided to ignore some of them (like SIGHUP).
824	 */
825	PJDLOG_VERIFY(signal(SIGHUP, SIG_DFL) != SIG_ERR);
826	PJDLOG_VERIFY(signal(SIGINT, SIG_DFL) != SIG_ERR);
827	PJDLOG_VERIFY(signal(SIGTERM, SIG_DFL) != SIG_ERR);
828	/*
829	 * Because SIGCHLD is ignored by default, setup dummy handler for it,
830	 * so we can mask it.
831	 */
832	PJDLOG_VERIFY(signal(SIGCHLD, dummy_sighandler) != SIG_ERR);
833
834	PJDLOG_VERIFY(sigemptyset(&mask) == 0);
835	PJDLOG_VERIFY(sigaddset(&mask, SIGHUP) == 0);
836	PJDLOG_VERIFY(sigaddset(&mask, SIGINT) == 0);
837	PJDLOG_VERIFY(sigaddset(&mask, SIGTERM) == 0);
838	PJDLOG_VERIFY(sigaddset(&mask, SIGCHLD) == 0);
839	PJDLOG_VERIFY(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
840
841	/* Listen on control address. */
842	if (proto_server(cfg->hc_controladdr, &cfg->hc_controlconn) < 0) {
843		KEEP_ERRNO((void)pidfile_remove(pfh));
844		pjdlog_exit(EX_OSERR, "Unable to listen on control address %s",
845		    cfg->hc_controladdr);
846	}
847	/* Listen for remote connections. */
848	if (proto_server(cfg->hc_listenaddr, &cfg->hc_listenconn) < 0) {
849		KEEP_ERRNO((void)pidfile_remove(pfh));
850		pjdlog_exit(EX_OSERR, "Unable to listen on address %s",
851		    cfg->hc_listenaddr);
852	}
853
854	if (!foreground) {
855		if (daemon(0, 0) < 0) {
856			KEEP_ERRNO((void)pidfile_remove(pfh));
857			pjdlog_exit(EX_OSERR, "Unable to daemonize");
858		}
859
860		/* Start logging to syslog. */
861		pjdlog_mode_set(PJDLOG_MODE_SYSLOG);
862
863		/* Write PID to a file. */
864		if (pidfile_write(pfh) < 0) {
865			pjdlog_errno(LOG_WARNING,
866			    "Unable to write PID to a file");
867		}
868	}
869
870	hook_init();
871
872	main_loop();
873
874	exit(0);
875}
876