"Fossies" - the Fresh Open Source Software archive

Member "evolution-brutus-1.2.35/log/main.c" of archive evolution-brutus-1.2.35.tar.gz:


/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */

/*
 *    Implementation file for a Brutus log daemon.
 *    Copyright (C) 2007 OMC Denmark ApS.
 *
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 *    MA 02111-1307 USA
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <sys/types.h>
#include <sys/wait.h>
#include <sys/file.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <locale.h>
#include <gcrypt.h>
#include <glib.h>
#include <glib/gstdio.h>
#include "brutus-logd.h"

#ifndef ORBIT2_EX
#define ORBIT2_EX(_ev_) ((NULL != _ev_) && ((_ev_)->_major != CORBA_NO_EXCEPTION))
#endif

static GMutex *log_mutex = NULL;
#include "brutus-log_impl.c"

static const char *prog_name = NULL;
static CORBA_ORB global_orb = CORBA_OBJECT_NIL;
static int global_lock_fd = -1;
static int ref_file_fd = -1;

// shutdown global ORB
static void
orb_shutdown(int sig)
{
	if (CORBA_OBJECT_NIL != global_orb) {
		CORBA_Environment ev[1];

		CORBA_exception_init(ev);
		CORBA_ORB_shutdown(global_orb, TRUE, ev);
		CORBA_exception_free(ev);
	}
}

static CORBA_ORB
create_orb(CORBA_Environment *ev)
{
	CORBA_ORB orb = CORBA_OBJECT_NIL;
	int init_argc = 2;
	char **init_argv = (char**)malloc(sizeof(char*) * (init_argc));
	if (!init_argv)
		return CORBA_OBJECT_NIL;

	init_argv[0] = "DUMMY_ARGV0";

	// Explicitly force ORBit2 to be local only
	init_argv[1] = "--ORBLocalOnly=1";

	// initialize the ORB
	orb = CORBA_ORB_init(&init_argc, init_argv, "brutus-log_orb_orbit-io-thread", ev);
	if (ORBIT2_EX(ev))
		orb = CORBA_OBJECT_NIL;

	free(init_argv);

	return orb;
}

static int
object_ref_to_file(CORBA_ORB orb,
		   CORBA_Object object,
		   gchar *filename,
		   CORBA_Environment *ev)
{
	gboolean retv = FALSE;
	CORBA_char *objref = NULL;
	int file_fd = -1;
	FILE *file = NULL;
	size_t c = 0;

	file_fd = g_open(filename, O_CREAT | O_WRONLY | O_SYNC | O_TRUNC, S_IRUSR | S_IWUSR);
	if (-1 == file_fd)
		return -1;

	file = fdopen(file_fd, "w");
	if (!file)
		goto out;

	objref = CORBA_ORB_object_to_string(orb, object, ev);
	if (ORBIT2_EX(ev) || !objref)
		goto out;

	// must work even if (sizeof(char) != sizeof(CORBA_char))
	c = fwrite((const void*)objref, sizeof(CORBA_char), corba_strlen(objref)+1, file);
	if (c == (1 + corba_strlen(objref))) {
		retv = TRUE;
		fsync(file_fd);
	}

out:
	if (objref)
		CORBA_free(objref);

	if (file) {
		if (fclose(file))
			retv = FALSE;
	} else {
		if (close(file_fd))
			retv = FALSE;
	}
	if (!retv)
		return -1;

	return g_open(filename, O_RDWR | O_SYNC, S_IRUSR | S_IWUSR);
}

typedef enum {
	EXIT_DAEMON = 0, /* we are the daemon                  */
	EXIT_OK = 1,	 /* caller must exit with EXIT_SUCCESS */
	EXIT_ERROR = 2,	 /* caller must exit with EXIT_FAILURE */
} daemon_exit_t;

static daemon_exit_t
become_daemon(void)
{
	struct sigaction sig_act;
	struct rlimit rl;
	unsigned int n;
	pid_t pid = -1;
	int fd0;
	int fd1;
	int fd2;

	/*
	 * A process that has terminated but has not yet been waited for is a zombie.
	 * On the other hand, if the parent dies first, init (process 1) inherits the
	 * child and becomes its parent.
	 *
	 * (*) Citation from <http://www.win.tue.nl/~aeb/linux/lk/lk-10.html>
	 */

	/* fork off the parent process to create the session daemon */
	pid = fork();
	switch (pid) {
	case -1 :
		return EXIT_ERROR;
	case 0 :	
		/* We are the intermediate child.
		 * Now fork once more and try to ensure that this intermediate child
		 * exits before the final child so that the final child is adopted
		 * by init and does not become a zombie. This is racy as the final
		 * child concievably could terminate (and become a zombie) before
		 * this child exits. Knock on wood...
		 */

		if ((setsid()) < 0)
			return EXIT_ERROR;

		sig_act.sa_handler = SIG_IGN;
		sigemptyset(&sig_act.sa_mask);
		sig_act.sa_flags = 0;
		if (sigaction(SIGHUP, &sig_act, NULL))
			return EXIT_ERROR;

		pid = fork();
		switch (pid) {
		case -1 :
			return EXIT_ERROR;
		case 0 :
			break;
		default :
			return EXIT_OK;
		}

		/*
		 * (0 == pid) we are the final child
		 */

		/* sleep for 1 second to give the parent plenty of time to exit */
		g_usleep(1000000);

		break;
	default :
		/* wait for intermediate child */
		waitpid(pid, NULL, 0);

		return EXIT_OK;
	}

	/*
	 * We are now effectively the daemon and must continue
	 * to prep the daemon process for operation
	 */

	// change the working directory
	if ((chdir("/")) < 0)
		return EXIT_ERROR;

	// close any and all open file descriptors
	if (getrlimit(RLIMIT_NOFILE, &rl))
		return EXIT_ERROR;
	if (RLIM_INFINITY == rl.rlim_max)
		rl.rlim_max = 1024;
	for (n = 0; n < rl.rlim_max; n++) {
		if (close(n) && (EBADF != errno))
			return EXIT_ERROR;
	}

	// attach file descriptors 0, 1 and 2 to /dev/null
	fd0 = open("/dev/null", O_RDWR);
	fd1 = dup2(fd0, 1);
	fd2 = dup2(fd0, 2);
	if (0 != fd0)
		return EXIT_ERROR;

	return EXIT_DAEMON;
}

static int 
lock_fd(const int fd)
{
	struct flock lock = {
		.l_type = F_WRLCK,
		.l_start = 0,
		.l_whence = SEEK_SET,
		.l_len = 0,
	};

	if (-1 == fd)
		return -1;

	return fcntl(fd, F_SETLK, &lock);
}

static int
unlock_fd(const int fd)
{
	struct flock lock = {
		.l_type = F_UNLCK,
		.l_start = 0,
		.l_whence = SEEK_SET,
		.l_len = 0,
	};

	if (-1 == fd)
		return -1;

	return fcntl(fd, F_SETLK, &lock);
}

/*
 * Will attempt to lock the PID file and write the
 * pid into it. This function will return FALSE for
 * all errors. Callee is responsible for closing *fd 
 * if (*fd != -1).
 */
static gboolean
get_process_lock(int *fd)
{
	char buf[32] = { '\0' };
	int p = 0;
	ssize_t w = 0;
	char *file_path = NULL;

	*fd = -1;

	file_path = g_strconcat(getenv("HOME"), "/", BRUTUS_LOGD_PID_FILE_NAME, NULL);
	if (!file_path)
		return FALSE;

	*fd = g_open(file_path, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
	g_free(file_path);
	if (-1 == *fd)
		return FALSE;

	// lock it
	if (lock_fd(*fd))
		return FALSE;

	// we have the lock
	if (ftruncate(*fd, 0))
		return FALSE;

	/* write pid */
	p = snprintf(buf, sizeof(buf), "%d", getpid());
	w = write(*fd, buf, strlen(buf) + sizeof(char));
	p += sizeof(char);
	if (p != (int)w)
		return FALSE;

	if (fdatasync(*fd))
		return FALSE;

	return TRUE;
}

/*
 * This handler will only be called after the signal handler
 * is installed. The signal handler is not installed until
 * the lock on th epid file is in place.
 */
static void
restart(int sig)
{
	unlock_fd(ref_file_fd); // unsafe in signal handler
	close(ref_file_fd);
	ref_file_fd = -1;

	unlock_fd(global_lock_fd); // unsafe in signal handler
	close(global_lock_fd);
	global_lock_fd = -1;

	if (-1 == system(prog_name)) // unsafe in signal handler
		;
	orb_shutdown(sig); // unsafe in signal handler
}

static int
open_fifo(void)
{
	int fd = -1;
	char *fifo_file_path = NULL;

	fifo_file_path = g_strconcat(getenv("HOME"), "/", BRUTUS_LOGD_FIFO_FILE_NAME, NULL);
	if (!fifo_file_path)
		goto out;

	if (mknod(fifo_file_path, S_IFIFO | S_IRUSR | S_IWUSR, 0) && (EEXIST != errno))
		goto out;

	fd = g_open(fifo_file_path, O_RDWR, S_IRUSR | S_IWUSR);
out:
	if (fifo_file_path)
		g_free(fifo_file_path);

	return fd;
}

/*
 * Will read from the fifo and write to the log file.
 * The written data is not guaranteed to be null-terminated.
 */
static gpointer
fifo_thread(gpointer data)
{
	int fd = *((int*)data);
	int log = open_log();
	ssize_t num = 0;
	char buf[BRUTUS_LOGD_FIFO_MAX_BUF];

	if (0 >= fd)
		return NULL;

	do {	
		memset((void*)buf, '\0', BRUTUS_LOGD_FIFO_MAX_BUF);

		num = read(fd, (void*)buf, BRUTUS_LOGD_FIFO_MAX_BUF);
		if (-1 == num)
			break;

		while (!g_mutex_trylock(log_mutex))
			g_usleep(10);	

		if (-1 == write(log, (const void*)buf, num))
			; // to avoid compiler warning
		fdatasync(log);
		
		g_mutex_unlock(log_mutex);
	} while (num > 0);

	if (-1 != fd)
		close(fd);

	return NULL;
}

static void
truncate_log(void)
{
	const char *log_file_name = NULL;
	char *log_file_path = NULL;

	while (!g_mutex_trylock(log_mutex))
		g_usleep(10);	

	log_file_name = getenv("BRUTUS_LOG_FILE_NAME");
	if (!log_file_name)
		log_file_name = BRUTUS_LOG_FILE;
	log_file_path = g_strconcat(getenv("HOME"), "/", BRUTUS_LOGD_DIR_NAME, "/", log_file_name, NULL);
	if (!log_file_path)
		goto out;

	if (-1 == truncate(log_file_path, 0))
		;
	g_free(log_file_path);
out:
	g_mutex_unlock(log_mutex);
}

int
main(int argc, char *argv[])
{
	PortableServer_POA root_poa = CORBA_OBJECT_NIL;
	BRUTUS_BrutusLog log_obj = CORBA_OBJECT_NIL;
	CORBA_Environment ev[1];
	gboolean daemon = TRUE;
	int retv = EXIT_FAILURE;
	gchar *ref_file_path = NULL;
	sigset_t mask;
	struct sigaction sig_act;
	daemon_exit_t dstat = EXIT_DAEMON;
	int log_fifo = -1;

	/* clear the file mode mask */
	umask(0);

	if (1 < argc)
		daemon = FALSE;

	/* restore signals */
	sigfillset(&mask);
	pthread_sigmask(SIG_UNBLOCK, &mask, NULL);

	/* for restart */
	prog_name = argv[0];

	/* create application directory */
	{
		gchar *dir_path = NULL;
		int retv = 1;

		dir_path = g_strconcat(getenv("HOME"), "/", BRUTUS_LOGD_DIR_NAME, NULL);
		if (!dir_path)
			exit(EXIT_FAILURE);

		retv = g_mkdir_with_parents(dir_path, 0700);
		g_free(dir_path);
		if (retv)
			exit(EXIT_FAILURE);
	}

	if (daemon)
		dstat = become_daemon();
	switch (dstat) {
	case EXIT_DAEMON :
		break;
	case EXIT_OK :
		exit(EXIT_SUCCESS);
	case EXIT_ERROR :
	default :
		exit(EXIT_FAILURE);
	}

	/*
	 * We are now the would-be log daemon
	 */

	// take the lock and write the pid
	if (!get_process_lock(&global_lock_fd)) {
		if (-1 != global_lock_fd) {
			close(global_lock_fd);
			global_lock_fd = -1;
		}
		exit(EXIT_SUCCESS);
	}

	/*
	 * We are now ensured that we are the only log daemon in the air
	 * unless HOME is on NFS...
	 *
	 * All exits must be taken by "goto out;".
	 */

	// make sure that the thread system is initialized
	if (!g_thread_supported()) 
		g_thread_init(NULL);

	CORBA_exception_init(ev);

	log_mutex = g_mutex_new();
	if (!log_mutex)
		goto out;

	truncate_log();

	/*
	 * Initialize and activate FIFO logging
	 */
	log_fifo = open_fifo();
	if (-1 != log_fifo) { // launch reader thread
		g_thread_create(fifo_thread,
				(gpointer)&log_fifo,
				FALSE,
				NULL);
	} else
		goto out;

	/*
	 * initialize CORBA
	 */

	global_orb = create_orb(ev);
	if (ORBIT2_EX(ev))
		goto out;
	{
		PortableServer_POAManager poa_manager = CORBA_OBJECT_NIL;

		root_poa = (PortableServer_POA) CORBA_ORB_resolve_initial_references(global_orb, "RootPOA", ev);
		if (ORBIT2_EX(ev))
			goto out;

		poa_manager = PortableServer_POA__get_the_POAManager(root_poa, ev);
		if (ORBIT2_EX(ev))
			goto out;

		PortableServer_POAManager_activate(poa_manager, ev);
		if (ORBIT2_EX(ev))
			goto out;

		CORBA_Object_release((CORBA_Object)poa_manager, ev);
		if (ORBIT2_EX(ev))
			goto out;
	}

	/* create log servant */
	log_obj = impl_BRUTUS_BrutusLog__create(root_poa, ev);
	if (ORBIT2_EX(ev))
		goto out;
	if (CORBA_OBJECT_NIL == log_obj)
		goto out;

	/* write ior to file */
	ref_file_path = g_strconcat(getenv("HOME"), "/", BRUTUS_LOGD_REF_FILE_NAME, NULL);
	if (!ref_file_path)
		goto out;
	ref_file_fd = object_ref_to_file(global_orb, log_obj, ref_file_path, ev);
	if (-1 == ref_file_fd)
		goto out;

	/*
	 * handle signals
	 */

	sig_act.sa_handler = orb_shutdown;
	sigfillset(&sig_act.sa_mask); // ignore as many signals as possible in the handler!
	sig_act.sa_flags = SA_RESETHAND;
	sigaction(SIGUSR1, &sig_act, NULL);

	/* restart the daemon if SIGUSR2 is caught */
	if (daemon)
		sig_act.sa_handler = restart;
	else
		sig_act.sa_handler = orb_shutdown;
	sigfillset(&sig_act.sa_mask);
	sig_act.sa_flags = SA_RESETHAND;
	sigaction(SIGUSR2, &sig_act, NULL);

	sig_act.sa_handler = orb_shutdown;
	sigfillset(&sig_act.sa_mask);
	sig_act.sa_flags = SA_RESETHAND;
	sigaction(SIGTERM, &sig_act, NULL);

	sig_act.sa_handler = orb_shutdown;
	sigfillset(&sig_act.sa_mask);
	sig_act.sa_flags = SA_RESETHAND;
	sigaction(SIGINT, &sig_act, NULL);

	/* loop until killed */
	lock_fd(ref_file_fd);
	CORBA_ORB_run(global_orb, ev);
	unlock_fd(ref_file_fd);

	/* cleanup */
	{
		PortableServer_ObjectId *objid = NULL;

		objid = PortableServer_POA_reference_to_id(root_poa, (CORBA_Object)log_obj, ev);
		if (ORBIT2_EX(ev)) {
			if (objid)
				CORBA_free(objid);
			goto out;
		}

		PortableServer_POA_deactivate_object(root_poa, objid, ev);
		CORBA_free(objid);
		if (ORBIT2_EX(ev))
			goto out;

		CORBA_Object_release((CORBA_Object)log_obj, ev);
		if (ORBIT2_EX(ev))
			goto out;

		PortableServer_POA_destroy(root_poa, TRUE, FALSE, ev);
		if (ORBIT2_EX(ev))
			goto out;

		CORBA_Object_release((CORBA_Object)root_poa, ev);
		if (ORBIT2_EX(ev))
			goto out;

		/* ORB: tear down the ORB */
		if (CORBA_OBJECT_NIL != global_orb) {
			CORBA_ORB_destroy(global_orb, ev);
			if (ORBIT2_EX(ev))
				goto out;

			CORBA_Object_release((CORBA_Object)global_orb, ev);
			if (ORBIT2_EX(ev))
				goto out;
			global_orb = CORBA_OBJECT_NIL;
		}
	}
	retv = EXIT_SUCCESS;

out:
	/*
	 * We only ever end up here if we succefully took
	 * the lock and became the daemon
	 */

	CORBA_exception_free(ev);

	/*
	 * There is a small race where the daemon could catch a signal just after
	 * installing signal handlers but before placing the lock. Releasing the
	 * lock unnecessarily should not harm us though.
	 */
	if (-1 != ref_file_fd)
		close(ref_file_fd);

	if (-1 != log_fifo)
		close(log_fifo);

	if (ref_file_path) {
		if (-1 == truncate(ref_file_path, 0))
			;
		g_free(ref_file_path);
	}

	if (-1 != global_lock_fd) {
		unlock_fd(global_lock_fd);
		close(global_lock_fd);
	}

	if (log_mutex)
		g_mutex_free(log_mutex);

	exit(retv);
}