"Fossies" - the Fresh Open Source Software archive 
/* -*- 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);
}