}
int
-daemon_open_shared_ports(struct daemon* daemon, struct config_file* cfg)
+daemon_open_shared_ports(struct daemon* daemon)
{
log_assert(daemon);
- daemon->cfg = cfg;
if(daemon->cfg->port == daemon->listening_port)
return 1;
listening_ports_free(daemon->ports);
return;
listening_ports_free(daemon->ports);
lock_basic_destroy(&daemon->lock);
+ free(daemon->cwd);
+ free(daemon->pidfile);
free(daemon);
}
lock_basic_t lock;
/** The config settings */
struct config_file* cfg;
+ /** current working directory */
+ char* cwd;
+ /** pidfile that is used */
+ char* pidfile;
/** port number that has ports opened. */
int listening_port;
/** listening ports, opened, to be shared by threads */
/**
* Open shared listening ports (if needed).
+ * The cfg member pointer must have been set for the daemon.
* @param daemon: the daemon.
- * @param cfg: the cfg settings. Applied to daemon.
* @return: false on error.
*/
-int daemon_open_shared_ports(struct daemon* daemon, struct config_file* cfg);
+int daemon_open_shared_ports(struct daemon* daemon);
/**
* Fork workers and start service.
#include "util/log.h"
#include "daemon/daemon.h"
#include "util/config_file.h"
+#include <signal.h>
#include <fcntl.h>
#include <pwd.h>
printf("Report bugs to %s\n", PACKAGE_BUGREPORT);
}
+/** to changedir, logfile */
+static void
+apply_dir(struct daemon* daemon, struct config_file* cfg, int cmdline_verbose)
+{
+ /* apply changes if they have changed */
+ daemon->cfg = cfg;
+ verbosity = cmdline_verbose + cfg->verbosity;
+ if(cfg->directory && cfg->directory[0]) {
+ if(!daemon->cwd || strcmp(daemon->cwd, cfg->directory) != 0) {
+ if(chdir(cfg->directory)) {
+ log_err("Could not chdir to %s: %s",
+ cfg->directory, strerror(errno));
+ }
+ free(daemon->cwd);
+ if(!(daemon->cwd = strdup(cfg->directory)))
+ fatal_exit("malloc failed");
+ }
+ }
+}
+
+/** Read existing pid from pidfile. */
+static pid_t
+readpid (const char* file)
+{
+ int fd;
+ pid_t pid;
+ char pidbuf[32];
+ char* t;
+ int l;
+
+ if ((fd = open(file, O_RDONLY)) == -1) {
+ return -1;
+ }
+
+ if (((l = read(fd, pidbuf, sizeof(pidbuf)))) == -1) {
+ close(fd);
+ return -1;
+ }
+
+ close(fd);
+
+ /* Empty pidfile means no pidfile... */
+ if (l == 0) {
+ errno = ENOENT;
+ return -1;
+ }
+
+ pid = strtol(pidbuf, &t, 10);
+
+ if (*t && *t != '\n') {
+ return -1;
+ }
+ return pid;
+}
+
+/** write pid to file. */
+static void
+writepid (const char* pidfile, pid_t pid)
+{
+ FILE* f;
+
+ if ((f = fopen(pidfile, "w")) == NULL ) {
+ log_err("cannot open pidfile %s: %s",
+ pidfile, strerror(errno));
+ return;
+ }
+ if(fprintf(f, "%lu\n", (unsigned long)pid) < 0) {
+ log_err("cannot write to pidfile %s: %s",
+ pidfile, strerror(errno));
+ }
+ fclose(f);
+}
+
+/** check old pid file */
+static void
+checkoldpid(struct config_file* cfg)
+{
+ pid_t old;
+ if((old = readpid(cfg->pidfile)) == -1) {
+ if(errno != ENOENT) {
+ log_err("Could not read pidfile %s: %s",
+ cfg->pidfile, strerror(errno));
+ }
+ } else {
+ /** see if it is still alive */
+ if(kill(old, 0) == 0 || errno == EPERM)
+ log_warn("unbound is already running as pid %u.", old);
+ else log_warn("did not exit gracefully last time (%u)", old);
+ }
+}
+
/** daemonize, drop user priviliges and chroot if needed */
static void
-do_chroot(struct config_file* cfg)
+do_chroot(struct daemon* daemon, struct config_file* cfg)
{
log_assert(cfg);
fatal_exit("unable to set user id: %s", strerror(errno));
endpwent();
}
+ /* check old pid file before forking */
+ if(cfg->pidfile && cfg->pidfile[0]) {
+ checkoldpid(cfg);
+ }
+
+ /* init logfile just before fork */
+ log_init(cfg->logfile);
if(cfg->do_daemonize) {
int fd;
/* Take off... */
case 0:
break;
case -1:
+ unlink(cfg->pidfile);
fatal_exit("fork failed: %s", strerror(errno));
default:
/* exit interactive session */
(void)close(fd);
}
}
+ if(cfg->pidfile && cfg->pidfile[0]) {
+ writepid(cfg->pidfile, getpid());
+ daemon->pidfile = strdup(cfg->pidfile);
+ }
}
/**
fatal_exit("Could not alloc config defaults");
if(!config_read(cfg, cfgfile))
fatal_exit("Could not read config file: %s", cfgfile);
- verbosity = cmdline_verbose + cfg->verbosity;
+ apply_dir(daemon, cfg, cmdline_verbose);
/* prepare */
- if(!daemon_open_shared_ports(daemon, cfg))
+ if(!daemon_open_shared_ports(daemon))
fatal_exit("could not open ports");
if(!done_chroot) {
- do_chroot(cfg);
+ do_chroot(daemon, cfg);
done_chroot = 1;
}
/* work */
config_delete(cfg);
}
verbose(VERB_ALGO, "Exit cleanup.");
+ if(daemon->pidfile)
+ unlink(daemon->pidfile);
daemon_delete(daemon);
}
const char* cfgfile = NULL;
int cmdline_verbose = 0;
- log_init();
+ log_init(NULL);
/* parse the options */
while( (c=getopt(argc, argv, "c:hv")) != -1) {
switch(c) {
And everything is started again (and listening ports if needed).
- Ports for queries are shared.
- config file added interface:, chroot: and username:.
+ - config file: directory, logfile, pidfile. And they work too.
22 February 2007: Wouter
- Have a config file. Removed commandline options, moved to config.
# forward-to-port: 53
# if given, a chroot(2) is done to the given directory.
+ # i.e. you can chroot to the working directory, for example,
+ # for extra security, but make sure all files are in that directory.
# chroot: "/some/directory"
# if given, user privileges are dropped (after binding port),
# and the given username is assumed. Default is nothing "".
# username: "unbound"
+
+ # the working directory.
+ # directory: "/etc/unbound"
+
+ # the log file, "" means log to stderr.
+ # logfile: ""
+
+ # the pid file.
+ # pidfile: "unbound.pid"
.El
.Sh SEE ALSO
-.Xr resolv.conf 5 ,
.Xr unbound.conf 5 .
.Sh AUTHORS
.It \fBusername:\fR <name>
If given, after binding the port the user privileges are dropped. Default is
not to change user, username: "". If this user is not capable of binding the
-port, reloads (by signal HUP) will work, however, if you change the port
-number in the config file, and that port number requires privileges, then
-a reload will fail to bind to the new port number; a restart is needed.
+port, reloads (by signal HUP) will still retain the opened ports.
+If you change the port number in the config file, and that new port number
+requires privileges, then a restart is needed.
+.It \fBdirectory:\fR <directory>
+Sets the working directory for the program.
+.It \fBlogfile:\fR <filename>
+If "" is given, logging goes to stderr, or nowhere once daemonized.
+The logfile is appended to, in the following format:
+[seconds since 1970] unbound[pid]: type: message.
+.It \fBpidfile:\fR <filename>
+The process id is written to the file. Default is "unbound.pid". So,
+kill -HUP `cat /etc/unbound/unbound.pid` will trigger a reload,
+kill -QUIT `cat /etc/unbound/unbound.pid` will gracefully terminate.
.Sh FILES
.Bl -tag -width indent
char* init_optarg = optarg;
struct replay_scenario* scen = NULL;
+ log_init(NULL);
log_info("Start of %s testbound program.", PACKAGE_STRING);
/* determine commandline options for the daemon */
pass_argc = 1;
int
main(int argc, char* argv[])
{
+ log_init(NULL);
if(argc != 1) {
printf("usage: %s\n", argv[0]);
printf("\tperforms unit tests.\n");
cfg->do_tcp = 1;
cfg->outgoing_base_port = cfg->port + 1000;
cfg->outgoing_num_ports = 16;
- if(!(cfg->fwd_address = strdup(""))) {
- config_delete(cfg);
- return NULL;
- }
- if(!(cfg->username = strdup(""))) {
- config_delete(cfg);
- return NULL;
- }
- if(!(cfg->chrootdir = strdup(""))) {
- config_delete(cfg);
- return NULL;
- }
+ if(!(cfg->fwd_address = strdup(""))) {config_delete(cfg); return NULL;}
+ if(!(cfg->username = strdup(""))) {config_delete(cfg); return NULL;}
+ if(!(cfg->chrootdir = strdup(""))) {config_delete(cfg); return NULL;}
+ if(!(cfg->directory = strdup("/etc/unbound"))) {config_delete(cfg); return NULL;}
+ if(!(cfg->logfile = strdup(""))) {config_delete(cfg); return NULL;}
+ if(!(cfg->pidfile = strdup("unbound.pid"))) {config_delete(cfg); return NULL;}
cfg->fwd_port = UNBOUND_DNS_PORT;
cfg->do_daemonize = 0;
cfg->num_ifs = 0;
free(cfg->fwd_address);
free(cfg->username);
free(cfg->chrootdir);
+ free(cfg->directory);
+ free(cfg->logfile);
+ free(cfg->pidfile);
if(cfg->ifs) {
int i;
for(i=0; i<cfg->num_ifs; i++)
char* chrootdir;
/** username to change to, if not "". */
char* username;
+ /** working directory */
+ char* directory;
+ /** filename to log to. */
+ char* logfile;
+ /** pidfile to write pid to. */
+ char* pidfile;
/** daemonize, i.e. fork into the background. */
int do_daemonize;
interface{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_INTERFACE;}
chroot{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_CHROOT;}
username{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_USERNAME;}
+directory{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_DIRECTORY;}
+logfile{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_LOGFILE;}
+pidfile{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_PIDFILE;}
{NEWLINE} { LEXOUT(("NL\n")); cfg_parser->line++;}
/* Quoted strings. Strip leading and ending quotes */
%token VAR_OUTGOING_PORT VAR_OUTGOING_RANGE VAR_INTERFACE
%token VAR_DO_IP4 VAR_DO_IP6 VAR_DO_UDP VAR_DO_TCP
%token VAR_FORWARD_TO VAR_FORWARD_TO_PORT VAR_CHROOT
-%token VAR_USERNAME
+%token VAR_USERNAME VAR_DIRECTORY VAR_LOGFILE VAR_PIDFILE
%%
toplevelvars: /* empty */ | toplevelvars toplevelvar ;
server_outgoing_port | server_outgoing_range | server_do_ip4 |
server_do_ip6 | server_do_udp | server_do_tcp | server_forward_to |
server_forward_to_port | server_interface | server_chroot |
- server_username;
+ server_username | server_directory | server_logfile | server_pidfile;
server_num_threads: VAR_NUM_THREADS STRING
{
OUTYY(("P(server_num_threads:%s)\n", $2));
cfg_parser->cfg->username = $2;
}
;
+server_directory: VAR_DIRECTORY STRING
+ {
+ OUTYY(("P(server_directory:%s)\n", $2));
+ free(cfg_parser->cfg->directory);
+ cfg_parser->cfg->directory = $2;
+ }
+ ;
+server_logfile: VAR_LOGFILE STRING
+ {
+ OUTYY(("P(server_logfile:%s)\n", $2));
+ free(cfg_parser->cfg->logfile);
+ cfg_parser->cfg->logfile = $2;
+ }
+ ;
+server_pidfile: VAR_PIDFILE STRING
+ {
+ OUTYY(("P(server_pidfile:%s)\n", $2));
+ free(cfg_parser->cfg->pidfile);
+ cfg_parser->cfg->pidfile = $2;
+ }
+ ;
%%
/* parse helper routines could be here */
#endif
enum verbosity_value verbosity = 0;
+static FILE* logfile = 0;
void
-log_init()
+log_init(const char* filename)
{
+ FILE *f;
+ if(!filename || !filename[0]) {
+ if(logfile && logfile != stderr)
+ fclose(logfile);
+ logfile = stderr;
+ return;
+ }
+ /* open the file for logging */
+ f = fopen(filename, "a");
+ if(!f) {
+ log_err("Could not open logfile %s: %s", filename,
+ strerror(errno));
+ return;
+ }
+ if(logfile && logfile != stderr)
+ fclose(logfile);
+ logfile = f;
}
void
char message[MAXSYSLOGMSGLEN];
const char* ident="unbound";
vsnprintf(message, sizeof(message), format, args);
- fprintf(stderr, "[%d] %s[%d] %s: %s\n",
+ fprintf(logfile, "[%d] %s[%d] %s: %s\n",
(int)time(NULL), ident, (int)getpid(), type, message);
- fflush(stderr);
+ fflush(logfile);
}
/**
va_end(args);
}
+/**
+ * implementation of log_warn
+ * @param format: format string printf-style.
+ */
+void
+log_warn(const char *format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ log_vmsg("warning", format, args);
+ va_end(args);
+}
+
/**
* implementation of fatal_exit
* @param format: format string printf-style.
/**
* call this to initialize logging services.
+ * @param filename: if NULL stderr is used.
*/
-void log_init();
+void log_init(const char* filename);
/**
* Log informational message.
*/
void log_err(const char* format, ...) ATTR_FORMAT(printf, 1, 2);
+/**
+ * Log warning message.
+ * Pass printf formatted arguments. No trailing newline is needed.
+ * @param format: printf-style format string. Arguments follow.
+ */
+void log_warn(const char* format, ...) ATTR_FORMAT(printf, 1, 2);
+
/**
* Log fatal error message, and exit the current process.
* Pass printf formatted arguments. No trailing newline is needed.