blob: 17650d8a07a9710111918a1e1d08d30526862ace [file] [log] [blame]
/* libnih
*
* main.c - main loop handling and functions often called from main()
*
* Copyright © 2006 Scott James Remnant <scott@netsplit.com>.
*
* 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif /* HAVE_CONFIG_H */
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/select.h>
#include <fcntl.h>
#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#include <nih/macros.h>
#include <nih/alloc.h>
#include <nih/string.h>
#include <nih/list.h>
#include <nih/timer.h>
#include <nih/signal.h>
#include <nih/child.h>
#include <nih/io.h>
#include <nih/error.h>
#include <nih/logging.h>
#include "main.h"
/**
* VAR_RUN:
*
* Directory to write pid files into.
**/
#define VAR_RUN "/var/run"
/**
* DEV_NULL:
*
* Device bound to stdin/out/err when daemonising.
**/
#define DEV_NULL "/dev/null"
/**
* program_name:
*
* The name of the program, taken from the argument array with the directory
* name portion stripped.
**/
const char *program_name = NULL;
/**
* package_name:
*
* The name of the overall package, usually set to the autoconf PACKAGE_NAME
* macro. This should be used in preference.
**/
const char *package_name = NULL;
/**
* package_version:
*
* The version of the overall package, thus also the version of the program.
* Usually set to the autoconf PACKAGE_VERSION macro. This should be used
* in preference.
**/
const char *package_version = NULL;
/**
* package_copyright:
*
* The copyright message for the package, taken from the autoconf
* AC_COPYRIGHT macro.
**/
const char *package_copyright = NULL;
/**
* package_bugreport:
*
* The e-mail address to report bugs on the package to, taken from the
* autoconf PACKAGE_BUGREPORT macro.
**/
const char *package_bugreport = NULL;
/**
* package_string:
*
* The human string for the program, either "program (version)" or if the
* program and package names differ, "program (package version)".
* Generated by and obtained using #nih_main_package_string.
**/
static char *package_string = NULL;
/**
* interrupt_pipe:
*
* Pipe used for interrupting an active select() call in case a signal
* comes in between the last time we handled the signal and the time we
* ran the call.
**/
static int interrupt_pipe[2] = { -1, -1 };
/**
* exit_loop:
*
* Whether to exit the running main loop, set to TRUE by a call to
* #nih_main_loop_exit.
**/
static __thread int exit_loop = 0;
/**
* exit_status:
*
* Status to exit the running main loop with, set by #nih_main_loop_exit.
**/
static __thread int exit_status = 0;
/**
* loop_functions:
*
* List of functions to be called in each main loop iteration.
**/
static NihList *loop_functions = NULL;
/**
* nih_main_init_full:
* @argv0: program name from arguments,
* @package: package name from configure,
* @version: package version from configure,
* @bugreport: bug report address from configure,
* @copyright: package copyright message from configure.
*
* Should be called at the beginning of #main to initialise the various
* global variables exported from this module. For autoconf-using packages
* call the #nih_main_init macro instead.
**/
void
nih_main_init_full (const char *argv0,
const char *package,
const char *version,
const char *bugreport,
const char *copyright)
{
nih_assert (argv0 != NULL);
nih_assert (package != NULL);
nih_assert (version != NULL);
/* Only take the basename of argv0 */
program_name = strrchr (argv0, '/');
if (program_name) {
program_name++;
} else {
program_name = argv0;
}
package_name = package;
package_version = version;
/* bugreport and copyright may be NULL/empty */
if (bugreport && *bugreport)
package_bugreport = bugreport;
if (copyright && *copyright)
package_copyright = copyright;
if (package_string)
nih_free (package_string);
package_string = NULL;
}
/**
* nih_main_package_string:
*
* Compares the invoked program name against the package name, producing
* a string in the form "program (package version)" if they differ or
* "program version" if they match.
*
* Returns: internal copy of the string.
**/
const char *
nih_main_package_string (void)
{
nih_assert (program_name != NULL);
if (package_string)
return package_string;
if (strcmp (program_name, package_name)) {
package_string = nih_sprintf (NULL, "%s (%s %s)", program_name,
package_name, package_version);
} else {
package_string = nih_sprintf (NULL, "%s %s", package_name,
package_version);
}
if (! package_string)
return program_name;
return package_string;
}
/**
* nih_main_suggest_help:
*
* Print a message suggesting --help to stderr.
**/
void
nih_main_suggest_help (void)
{
nih_assert (program_name != NULL);
fprintf (stderr, _("Try `%s --help' for more information.\n"),
program_name);
}
/**
* nih_main_version:
*
* Print the program version to stdout.
**/
void
nih_main_version (void)
{
nih_assert (program_name != NULL);
printf ("%s\n", nih_main_package_string ());
if (package_copyright)
printf ("%s\n", package_copyright);
printf ("\n");
printf (_("This is free software; see the source for copying conditions. There is NO\n"
"warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"));
}
/**
* nih_main_daemonise:
*
* Perform the necessary steps to become a daemon process, this will only
* return in the child process if successful. A file will be written to
* /var/run/<program_name>.pid containing the pid of the child process.
*
* Returns: zero on success, negative value on raised error.
**/
int
nih_main_daemonise (void)
{
pid_t pid;
int i, fd;
nih_assert (program_name != NULL);
/* Fork off child process. This begans the detachment from our
* parent process; this will terminate the intermediate process.
*/
pid = fork ();
if (pid < 0) {
nih_return_system_error (-1);
} else if (pid > 0) {
exit (0);
}
/* Become session leader of a new process group, without any
* controlling tty.
*/
setsid ();
/* When the session leader dies, SIGHUP is sent to all processes
* in that process group, including the child we're about to
* spawn. So make damned sure it's ignored.
*/
nih_signal_set_ignore (SIGHUP);
/* We now spawn off a second child (or at least attempt to),
* we do this so that it is guaranteed not to be a session leader,
* even by accident. Therefore any open() call on a tty won't
* make that it's controlling terminal.
*/
pid = fork ();
if (pid < 0) {
nih_return_system_error (-1);
} else if (pid > 0) {
FILE *pidfile;
char *filename;
umask (022);
NIH_MUST (filename = nih_sprintf (NULL, "%s/%s.pid",
VAR_RUN, program_name));
pidfile = fopen (filename, "w");
if (pidfile) {
fprintf (pidfile, "%d\n", pid);
fclose (pidfile);
}
exit (0);
}
/* We're now in a daemon child process. Change our working directory
* and file creation mask to be more appropriate.
*/
chdir ("/");
umask (0);
/* Close the stdin/stdout/stderr that we inherited */
for (i = 0; i < 3; i++)
close (i);
/* And instead bind /dev/null to them */
fd = open (DEV_NULL, O_RDWR);
dup (fd);
dup (fd);
return 0;
}
/**
* nih_main_loop_init:
*
* Initialise the loop functions list.
**/
static void
nih_main_loop_init (void)
{
if (! loop_functions)
NIH_MUST (loop_functions = nih_list_new ());
/* Set up the interrupt pipe, we need it to be non blocking so that
* we don't accidentally block if there's too many signals been
* triggered or something
*/
if (interrupt_pipe[0] == -1) {
NIH_MUST (pipe (interrupt_pipe) == 0);
nih_io_set_nonblock (interrupt_pipe[0]);
nih_io_set_nonblock (interrupt_pipe[1]);
nih_io_set_cloexec (interrupt_pipe[0]);
nih_io_set_cloexec (interrupt_pipe[1]);
}
}
/**
* nih_main_loop:
*
* Implements a fully functional main loop for a typical process, handling
* I/O events, signals, termination of child processes, timers, etc.
*
* Returns: value given to #nih_main_loop_exit.
**/
int
nih_main_loop (void)
{
nih_main_loop_init ();
/* Set a handler for SIGCHLD so that it can interrupt syscalls */
nih_signal_set_handler (SIGCHLD, nih_signal_handler);
while (! exit_loop) {
NihTimer *next_timer;
struct timeval timeout;
fd_set readfds, writefds, exceptfds;
char buf[1];
int nfds, ret;
/* Use the due time of the next timer to calculate how long
* to spend in select(). That way we don't sleep for any
* less or more time than we need to.
*/
next_timer = nih_timer_next_due ();
if (next_timer) {
timeout.tv_sec = next_timer->due - time (NULL);
timeout.tv_usec = 0;
}
/* Start off with empty watch lists */
FD_ZERO (&readfds);
FD_ZERO (&writefds);
FD_ZERO (&exceptfds);
/* Always look for changes in the interrupt pipe */
FD_SET (interrupt_pipe[0], &readfds);
nfds = interrupt_pipe[0] + 1;
/* And look for changes in anything we're watching */
nih_io_select_fds (&nfds, &readfds, &writefds, &exceptfds);
/* Now we hang around until either a signal comes in (and
* calls nih_main_loop_interrupt), a file descriptor we're
* watching changes in some way or it's time to run a timer.
*/
ret = select (nfds, &readfds, &writefds, &exceptfds,
(next_timer ? &timeout : NULL));
/* Deal with events */
if (ret > 0)
nih_io_handle_fds (&readfds, &writefds, &exceptfds);
/* Deal with signals.
*
* Clear the list first so that if a signal occurs while
* handling signals it'll ensure that the functions get
* a chance to decide whether to do anything next time round
* without having to wait.
*/
while (read (interrupt_pipe[0], buf, sizeof (buf)) > 0)
;
nih_signal_poll ();
/* Deal with terminated children */
nih_child_poll ();
/* Deal with timers */
nih_timer_poll ();
/* Run the loop functions */
NIH_LIST_FOREACH_SAFE (loop_functions, iter) {
NihMainLoopFunc *func = (NihMainLoopFunc *)iter;
func->callback (func->data, func);
}
}
exit_loop = 0;
return exit_status;
}
/**
* nih_main_loop_interrupt:
*
* Interrupts the current (or next) main loop interaction because of an
* event that potentially needs immediate processing, or because some
* condition of the main loop has been changed.
*
* This has no effect unless a main loop is running.
**/
void
nih_main_loop_interrupt (void)
{
nih_main_loop_init ();
if (interrupt_pipe[1] != -1)
write (interrupt_pipe[1], "", 1);
}
/**
* nih_main_loop_exit:
* @status: exit status.
*
* Instructs the current (or next) main loop to exit with the given exit
* status; if the loop is in the middle of processing, it will exit once
* all that processing is complete.
*
* This may be safely called by functions called by the main loop.
**/
void
nih_main_loop_exit (int status)
{
exit_status = status;
exit_loop = TRUE;
nih_main_loop_interrupt ();
}
/**
* nih_main_loop_add_func:
* @parent: parent of callback,
* @callback: function to call,
* @data: pointer to pass to @callback.
*
* Adds @callback to the list of functions that should be called once
* in each main loop iteration.
*
* The callback structure is allocated using #nih_alloc and stored in a linked
* list, a default destructor is set that removes the callback from the list.
* Removal of the callback can be performed by freeing it.
*
* Returns: the signal information, or %NULL if insufficient memory.
**/
NihMainLoopFunc *
nih_main_loop_add_func (void *parent,
NihMainLoopCb callback,
void *data)
{
NihMainLoopFunc *func;
nih_assert (callback != NULL);
nih_main_loop_init ();
func = nih_new (parent, NihMainLoopFunc);
if (! func)
return NULL;
nih_list_init (&func->entry);
nih_alloc_set_destructor (func, (NihDestructor)nih_list_destructor);
func->callback = callback;
func->data = data;
nih_list_add (loop_functions, &func->entry);
return func;
}
/**
* nih_main_term_signal:
* @data: ignored,
* @signal: ignored.
*
* Signal callback that instructs the main loop to exit with a normal
* exit status, usually registered for SIGTERM and SIGINT for non-daemons.
**/
void
nih_main_term_signal (void *data,
NihSignal *signal)
{
nih_main_loop_exit (0);
}