hooks.c revision 213183
1/*-
2 * Copyright (c) 2010 The FreeBSD Foundation
3 * Copyright (c) 2010 Pawel Jakub Dawidek <pjd@FreeBSD.org>
4 * All rights reserved.
5 *
6 * This software was developed by Pawel Jakub Dawidek under sponsorship from
7 * the FreeBSD Foundation.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 *    notice, this list of conditions and the following disclaimer in the
16 *    documentation and/or other materials provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 * SUCH DAMAGE.
29 */
30
31#include <sys/cdefs.h>
32__FBSDID("$FreeBSD: head/sbin/hastd/hooks.c 213183 2010-09-26 10:39:01Z pjd $");
33
34#include <sys/types.h>
35#include <sys/sysctl.h>
36#include <sys/wait.h>
37
38#include <assert.h>
39#include <errno.h>
40#include <fcntl.h>
41#include <libgen.h>
42#include <paths.h>
43#include <signal.h>
44#include <stdbool.h>
45#include <stdint.h>
46#include <stdio.h>
47#include <stdlib.h>
48#include <string.h>
49#include <syslog.h>
50#include <unistd.h>
51
52#include <pjdlog.h>
53
54#include "hooks.h"
55#include "synch.h"
56
57/* Report processes that are running for too long not often than this value. */
58#define	REPORT_INTERVAL	60
59
60/* Are we initialized? */
61static bool hooks_initialized = false;
62
63/*
64 * Keep all processes we forked on a global queue, so we can report nicely
65 * when they finish or report that they are running for a long time.
66 */
67#define	HOOKPROC_MAGIC_ALLOCATED	0x80090ca
68#define	HOOKPROC_MAGIC_ONLIST		0x80090c0
69struct hookproc {
70	/* Magic. */
71	int	hp_magic;
72	/* PID of a forked child. */
73	pid_t	hp_pid;
74	/* When process were forked? */
75	time_t	hp_birthtime;
76	/* When we logged previous reported? */
77	time_t	hp_lastreport;
78	/* Path to executable and all the arguments we passed. */
79	char	hp_comm[PATH_MAX];
80	TAILQ_ENTRY(hookproc) hp_next;
81};
82static TAILQ_HEAD(, hookproc) hookprocs;
83static pthread_mutex_t hookprocs_lock;
84
85static void hook_remove(struct hookproc *hp);
86static void hook_free(struct hookproc *hp);
87
88static void
89descriptors(void)
90{
91	long maxfd;
92	int fd;
93
94	/*
95	 * Close all descriptors.
96	 */
97	maxfd = sysconf(_SC_OPEN_MAX);
98	if (maxfd < 0) {
99		pjdlog_errno(LOG_WARNING, "sysconf(_SC_OPEN_MAX) failed");
100		maxfd = 1024;
101	}
102	for (fd = 0; fd <= maxfd; fd++) {
103		switch (fd) {
104		case STDIN_FILENO:
105		case STDOUT_FILENO:
106		case STDERR_FILENO:
107			if (pjdlog_mode_get() == PJDLOG_MODE_STD)
108				break;
109			/* FALLTHROUGH */
110		default:
111			close(fd);
112			break;
113		}
114	}
115	if (pjdlog_mode_get() == PJDLOG_MODE_STD)
116		return;
117	/*
118	 * Redirect stdin, stdout and stderr to /dev/null.
119	 */
120	fd = open(_PATH_DEVNULL, O_RDONLY);
121	if (fd < 0) {
122		pjdlog_errno(LOG_WARNING, "Unable to open %s for reading",
123		    _PATH_DEVNULL);
124	} else if (fd != STDIN_FILENO) {
125		if (dup2(fd, STDIN_FILENO) < 0) {
126			pjdlog_errno(LOG_WARNING,
127			    "Unable to duplicate descriptor for stdin");
128		}
129		close(fd);
130	}
131	fd = open(_PATH_DEVNULL, O_WRONLY);
132	if (fd < 0) {
133		pjdlog_errno(LOG_WARNING, "Unable to open %s for writing",
134		    _PATH_DEVNULL);
135	} else {
136		if (fd != STDOUT_FILENO && dup2(fd, STDOUT_FILENO) < 0) {
137			pjdlog_errno(LOG_WARNING,
138			    "Unable to duplicate descriptor for stdout");
139		}
140		if (fd != STDERR_FILENO && dup2(fd, STDERR_FILENO) < 0) {
141			pjdlog_errno(LOG_WARNING,
142			    "Unable to duplicate descriptor for stderr");
143		}
144		if (fd != STDOUT_FILENO && fd != STDERR_FILENO)
145			close(fd);
146	}
147}
148
149void
150hook_init(void)
151{
152
153	assert(!hooks_initialized);
154
155	mtx_init(&hookprocs_lock);
156	TAILQ_INIT(&hookprocs);
157	hooks_initialized = true;
158}
159
160void
161hook_fini(void)
162{
163	struct hookproc *hp;
164
165	assert(hooks_initialized);
166
167	mtx_lock(&hookprocs_lock);
168	while ((hp = TAILQ_FIRST(&hookprocs)) != NULL) {
169		assert(hp->hp_magic == HOOKPROC_MAGIC_ONLIST);
170		assert(hp->hp_pid > 0);
171
172		hook_remove(hp);
173		hook_free(hp);
174	}
175	mtx_unlock(&hookprocs_lock);
176
177	mtx_destroy(&hookprocs_lock);
178	TAILQ_INIT(&hookprocs);
179	hooks_initialized = false;
180}
181
182static struct hookproc *
183hook_alloc(const char *path, char **args)
184{
185	struct hookproc *hp;
186	unsigned int ii;
187
188	hp = malloc(sizeof(*hp));
189	if (hp == NULL) {
190		pjdlog_error("Unable to allocate %zu bytes of memory for a hook.",
191		    sizeof(*hp));
192		return (NULL);
193	}
194
195	hp->hp_pid = 0;
196	hp->hp_birthtime = hp->hp_lastreport = time(NULL);
197	(void)strlcpy(hp->hp_comm, path, sizeof(hp->hp_comm));
198	/* We start at 2nd argument as we don't want to have exec name twice. */
199	for (ii = 1; args[ii] != NULL; ii++) {
200		(void)strlcat(hp->hp_comm, " ", sizeof(hp->hp_comm));
201		(void)strlcat(hp->hp_comm, args[ii], sizeof(hp->hp_comm));
202	}
203	if (strlen(hp->hp_comm) >= sizeof(hp->hp_comm) - 1) {
204		pjdlog_error("Exec path too long, correct configuration file.");
205		free(hp);
206		return (NULL);
207	}
208	hp->hp_magic = HOOKPROC_MAGIC_ALLOCATED;
209	return (hp);
210}
211
212static void
213hook_add(struct hookproc *hp, pid_t pid)
214{
215
216	assert(hp->hp_magic == HOOKPROC_MAGIC_ALLOCATED);
217	assert(hp->hp_pid == 0);
218
219	hp->hp_pid = pid;
220	mtx_lock(&hookprocs_lock);
221	hp->hp_magic = HOOKPROC_MAGIC_ONLIST;
222	TAILQ_INSERT_TAIL(&hookprocs, hp, hp_next);
223	mtx_unlock(&hookprocs_lock);
224}
225
226static void
227hook_remove(struct hookproc *hp)
228{
229
230	assert(hp->hp_magic == HOOKPROC_MAGIC_ONLIST);
231	assert(hp->hp_pid > 0);
232	assert(mtx_owned(&hookprocs_lock));
233
234	TAILQ_REMOVE(&hookprocs, hp, hp_next);
235	hp->hp_magic = HOOKPROC_MAGIC_ALLOCATED;
236}
237
238static void
239hook_free(struct hookproc *hp)
240{
241
242	assert(hp->hp_magic == HOOKPROC_MAGIC_ALLOCATED);
243	assert(hp->hp_pid > 0);
244
245	hp->hp_magic = 0;
246	free(hp);
247}
248
249static struct hookproc *
250hook_find(pid_t pid)
251{
252	struct hookproc *hp;
253
254	assert(mtx_owned(&hookprocs_lock));
255
256	TAILQ_FOREACH(hp, &hookprocs, hp_next) {
257		assert(hp->hp_magic == HOOKPROC_MAGIC_ONLIST);
258		assert(hp->hp_pid > 0);
259
260		if (hp->hp_pid == pid)
261			break;
262	}
263
264	return (hp);
265}
266
267void
268hook_check_one(pid_t pid, int status)
269{
270	struct hookproc *hp;
271
272	mtx_lock(&hookprocs_lock);
273	hp = hook_find(pid);
274	if (hp == NULL) {
275		mtx_unlock(&hookprocs_lock);
276		pjdlog_debug(1, "Unknown process pid=%u", pid);
277		return;
278	}
279	hook_remove(hp);
280	mtx_unlock(&hookprocs_lock);
281	if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
282		pjdlog_debug(1, "Hook exited gracefully (pid=%u, cmd=[%s]).",
283		    pid, hp->hp_comm);
284	} else if (WIFSIGNALED(status)) {
285		pjdlog_error("Hook was killed (pid=%u, signal=%d, cmd=[%s]).",
286		    pid, WTERMSIG(status), hp->hp_comm);
287	} else {
288		pjdlog_error("Hook exited ungracefully (pid=%u, exitcode=%d, cmd=[%s]).",
289		    pid, WIFEXITED(status) ? WEXITSTATUS(status) : -1,
290		    hp->hp_comm);
291	}
292	hook_free(hp);
293}
294
295void
296hook_check(bool sigchld)
297{
298	struct hookproc *hp, *hp2;
299	int status;
300	time_t now;
301	pid_t pid;
302
303	assert(hooks_initialized);
304
305	/*
306	 * If SIGCHLD was received, garbage collect finished processes.
307	 */
308	if (sigchld) {
309		while ((pid = wait3(&status, WNOHANG, NULL)) > 0)
310			hook_check_one(pid, status);
311	}
312
313	/*
314	 * Report about processes that are running for a long time.
315	 */
316	now = time(NULL);
317	mtx_lock(&hookprocs_lock);
318	TAILQ_FOREACH_SAFE(hp, &hookprocs, hp_next, hp2) {
319		assert(hp->hp_magic == HOOKPROC_MAGIC_ONLIST);
320		assert(hp->hp_pid > 0);
321
322		/*
323		 * If process doesn't exists we somehow missed it.
324		 * Not much can be done expect for logging this situation.
325		 */
326		if (kill(hp->hp_pid, 0) == -1 && errno == ESRCH) {
327			pjdlog_warning("Hook disappeared (pid=%u, cmd=[%s]).",
328			    hp->hp_pid, hp->hp_comm);
329			hook_remove(hp);
330			hook_free(hp);
331			continue;
332		}
333
334		/*
335		 * Skip proccesses younger than 1 minute.
336		 */
337		if (now - hp->hp_lastreport < REPORT_INTERVAL)
338			continue;
339
340		/*
341		 * Hook is running for too long, report it.
342		 */
343		pjdlog_warning("Hook is running for %ju seconds (pid=%u, cmd=[%s]).",
344		    (uintmax_t)(now - hp->hp_birthtime), hp->hp_pid,
345		    hp->hp_comm);
346		hp->hp_lastreport = now;
347	}
348	mtx_unlock(&hookprocs_lock);
349}
350
351void
352hook_exec(const char *path, ...)
353{
354	va_list ap;
355
356	va_start(ap, path);
357	hook_execv(path, ap);
358	va_end(ap);
359}
360
361void
362hook_execv(const char *path, va_list ap)
363{
364	struct hookproc *hp;
365	char *args[64];
366	unsigned int ii;
367	pid_t pid;
368
369	assert(hooks_initialized);
370
371	if (path == NULL || path[0] == '\0')
372		return;
373
374	memset(args, 0, sizeof(args));
375	args[0] = basename(path);
376	for (ii = 1; ii < sizeof(args) / sizeof(args[0]); ii++) {
377		args[ii] = va_arg(ap, char *);
378		if (args[ii] == NULL)
379			break;
380	}
381	assert(ii < sizeof(args) / sizeof(args[0]));
382
383	hp = hook_alloc(path, args);
384	if (hp == NULL)
385		return;
386
387	pid = fork();
388	switch (pid) {
389	case -1:	/* Error. */
390		pjdlog_errno(LOG_ERR, "Unable to fork to execute %s", path);
391		hook_free(hp);
392		return;
393	case 0:		/* Child. */
394		descriptors();
395		execv(path, args);
396		pjdlog_errno(LOG_ERR, "Unable to execute %s", path);
397		exit(EX_SOFTWARE);
398	default:	/* Parent. */
399		hook_add(hp, pid);
400		break;
401	}
402}
403