From abbdd96699bd8ba0b1b388a598ddbd249b9d3dd6 Mon Sep 17 00:00:00 2001 From: Andreas Baumann Date: Mon, 25 Aug 2008 21:36:24 +0200 Subject: checked in initial version from SVN (as SVN is dead!) --- .gitignore | 17 + GNUmakefile | 5 + doc/GNUmakefile | 68 ++ doc/README.Daemon | 9 + doc/README.GenGetopt | 8 + doc/README.Makefile | 4 + doc/README.Porting | 58 + doc/README.SunPro | 5 + doc/daemon.php.html | 367 ++++++ doc/makedepend.html | 1771 ++++++++++++++++++++++++++++ makefiles/clean.mk | 29 + makefiles/compiler.mk | 174 +++ makefiles/depend.mk | 29 + makefiles/guess_env | 106 ++ makefiles/platform.mk | 34 + makefiles/sub.mk | 20 + makefiles/top.mk | 30 + src/GNUmakefile | 41 + src/daemon.c | 551 +++++++++ src/daemon.ggo | 70 ++ src/daemon.h | 36 + src/errors.h | 25 + src/log.c | 160 +++ src/log.h | 60 + src/pidfile.c | 245 ++++ src/pidfile.h | 32 + src/port/limits.h | 8 + src/port/lockf.c | 62 + src/port/lockf.h | 20 + src/port/noreturn.h | 10 + src/port/snprintf.c | 2109 ++++++++++++++++++++++++++++++++++ src/port/snprintf.h | 59 + src/port/stdbool.h | 35 + src/port/stdint.h | 15 + src/port/stdio.h | 10 + src/port/string.h | 16 + src/port/sys.h | 119 ++ src/port/unistd.h | 12 + src/port/unused.h | 8 + src/signals.c | 560 +++++++++ src/signals.h | 46 + src/testd.c | 149 +++ tests/GNUmakefile | 31 + tests/stdargs_for_signal_functions.c | 33 + 44 files changed, 7256 insertions(+) create mode 100644 .gitignore create mode 100644 GNUmakefile create mode 100644 doc/GNUmakefile create mode 100644 doc/README.Daemon create mode 100644 doc/README.GenGetopt create mode 100644 doc/README.Makefile create mode 100644 doc/README.Porting create mode 100644 doc/README.SunPro create mode 100644 doc/daemon.php.html create mode 100644 doc/makedepend.html create mode 100644 makefiles/clean.mk create mode 100644 makefiles/compiler.mk create mode 100644 makefiles/depend.mk create mode 100755 makefiles/guess_env create mode 100644 makefiles/platform.mk create mode 100644 makefiles/sub.mk create mode 100644 makefiles/top.mk create mode 100644 src/GNUmakefile create mode 100644 src/daemon.c create mode 100644 src/daemon.ggo create mode 100644 src/daemon.h create mode 100644 src/errors.h create mode 100644 src/log.c create mode 100644 src/log.h create mode 100644 src/pidfile.c create mode 100644 src/pidfile.h create mode 100644 src/port/limits.h create mode 100644 src/port/lockf.c create mode 100644 src/port/lockf.h create mode 100644 src/port/noreturn.h create mode 100644 src/port/snprintf.c create mode 100644 src/port/snprintf.h create mode 100644 src/port/stdbool.h create mode 100644 src/port/stdint.h create mode 100644 src/port/stdio.h create mode 100644 src/port/string.h create mode 100644 src/port/sys.h create mode 100644 src/port/unistd.h create mode 100644 src/port/unused.h create mode 100644 src/signals.c create mode 100644 src/signals.h create mode 100644 src/testd.c create mode 100644 tests/GNUmakefile create mode 100644 tests/stdargs_for_signal_functions.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..712b246 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +# generated stuff +cmdline.h +cmdline.c + +# myself +.gitignore + +# backup files +*~ + +# dependeny files +*.d + +# object files +*.o + +# binaries diff --git a/GNUmakefile b/GNUmakefile new file mode 100644 index 0000000..a6ddcb8 --- /dev/null +++ b/GNUmakefile @@ -0,0 +1,5 @@ +TOPDIR = . + +SUBDIRS = src tests + +-include $(TOPDIR)/makefiles/top.mk diff --git a/doc/GNUmakefile b/doc/GNUmakefile new file mode 100644 index 0000000..f1b80aa --- /dev/null +++ b/doc/GNUmakefile @@ -0,0 +1,68 @@ +.PHONY: all clean + +BINS = \ + testd + +all: $(BINS) + +PLATFORM = $(shell makefiles/guess_env --platform) +OS_MAJOR_VERSION = $(shell makefiles/guess_env --os-major-version) +OS_MINOR_VERSION = $(shell makefiles/guess_env --os-minor-version) + +COMPILE_FLAGS = -g -O2 -D_REENTRANT -Wall -W -Werror -std=c99 -pedantic -I. +LDFLAGS = +LIBS = + +CFLAGS = $(COMPILE_FLAGS) -D$(PLATFORM) -DOS_MAJOR_VERSION=$(OS_MAJOR_VERSION) -DOS_MINOR_VERSION=$(OS_MINOR_VERSION) +CC = gcc +MAKEDEPEND = $(CC) -MM + +OBJS = \ + port/snprintf.o \ + port/lockf.o \ + cmdline.o \ + log.o \ + signals.o \ + pidfile.o \ + daemon.o \ + testd.o + +# ABa: currently a special rule for cmdline.c as gengetopt is not +# completly fixed yet +cmdline.c : daemon.ggo + gengetopt --include-getopt -i $< + +cmdline.o : cmdline.c + $(CC) -c -o $@ $< + +%.o : %.c + $(CC) -c -o $@ $(CFLAGS) $< + +testd : $(OBJS) + $(CC) -o $@ $(LDFLAGS) $(LIBS) $^ + +clean: + -@rm *.bak port/*.bak 2>/dev/null + -@rm *~ port/*~ 2>/dev/null + -@rm *.d port/*.d 2>/dev/null + -@rm $(BINS) *.exe 2>/dev/null + -@rm $(OBJS) 2>/dev/null + +distclean: clean + -@rm cmdline.c cmdline.h + +%.d : %.c + @$(MAKEDEPEND) $(CFLAGS) $< 2>/dev/null | \ + sed "s,\($*\.o\)[ :]*\(.*\),$@ : $$\(wildcard \2\)___\1 : \2,g" | tr -s "_" "\n" > $@ + +-include $(OBJS:.o=.d) + +test: all + @fakeroot ./testd -d --pidfile /tmp/testd.pid && \ + sleep 3 && \ + ls -altr /tmp/testd* && \ + cat /tmp/testd.pid + -@ps -alef | grep test | grep -v grep + @sleep 1 + @pkill testd + -@ls -altr /tmp/testd* diff --git a/doc/README.Daemon b/doc/README.Daemon new file mode 100644 index 0000000..faf774e --- /dev/null +++ b/doc/README.Daemon @@ -0,0 +1,9 @@ +http://www.enderunix.org/documents/eng/daemon.php + +libraries: + +- libdaemon: C + http://0pointer.de/lennart/projects/libdaemon/ + +- see: + http://www.gmonline.demon.co.uk/cscene/CS4/CS4-07.html diff --git a/doc/README.GenGetopt b/doc/README.GenGetopt new file mode 100644 index 0000000..1763029 --- /dev/null +++ b/doc/README.GenGetopt @@ -0,0 +1,8 @@ +gengetopt: generates plain C code using getopt_long/getopt and +has a local getopt_long replacement for Unixes which don't +support getopt_long properly + +popt is happily broken + +self-made ones are usually quite funny to use and we don't use +some getopt features (like the ones on Solaris) \ No newline at end of file diff --git a/doc/README.Makefile b/doc/README.Makefile new file mode 100644 index 0000000..0a4ade0 --- /dev/null +++ b/doc/README.Makefile @@ -0,0 +1,4 @@ +http://make.paulandlesley.org/multi-arch.html +http://mattmccutchen.net/buildsys/index.html +http://cdrecord.berlios.de/private/makefiles.html +http://miller.emu.id.au/pmiller/books/rmch/ diff --git a/doc/README.Porting b/doc/README.Porting new file mode 100644 index 0000000..9c13a99 --- /dev/null +++ b/doc/README.Porting @@ -0,0 +1,58 @@ +How to support a new operating system, platform or version thereof? +------------------------------------------------------------------- + +Make sure 'port/guess_env' reliably detects the version of your +new operating system. If you have a new operating system choose +a new label for the platform like 'SUNOS'. + +Never port for the future with 'OS_MINOR_VERSION >= 5', make sure +you check the new version first. + +We try to follow the X-Open group if possible (and POSIX). Try +to avoid BSD emulations of functions on SysV systems and vice +versa. + +Defines in 'port/sys.h' +----------------------- + +Don't port using '#ifdef LINUX'! Instead use the platform flags +only in 'port/sys.h' and define descriptive macros for features +of the system like 'HAVE_VSNPRINTF'. + +Currently there are the following definitions which must be set: +- HAVE_STDBOOL_H and HAVE_ENUM_BOOL: + HAVE_STDBOOL_H whether the platform has a C99 bool type in stdbool.h + HAVE_ENUM_BOOL for platforms which define an internal _Bool somewhere + but not the official bool data type +- HAVE_VSNPRINTF, HAVE_SNPRINTF: vsnprintf and snprintf, there + is a stub implementation if this function doesn't exist or + is buggy (see http://www.jhweiss.de/software/snprintf.html) +- HAVE_STRDUP: a string duplication function (there is a stub for + really old platforms) +- HAVE_STRERROR_R: whether we have a reentrant strerror function +- HAVE_LOCKF: whether we have a POSIX lockf interface. A stub implemented + with fcntl is available for platform which don't have a lockf function. + +Currently tested on: +-------------------- + +- x86 Linux 2.6.x +- x86 FreeBSD 6.2 +- x86 OpenBSD 4.2 +- x86 Solaris 10 +- SPARC Solaris 8 + +How to use the porting layer in your code +----------------------------------------- + +Don't include system header files if there is a similar file in the +'ports' subdir: + +#include "port/limits.h" +#include "port/stdbool.h" +#include "port/stdio.h" +#include "port/string.h" +#include "port/unistd.h" + +You also may to have new such stubs when porting to new platforms +or when you start to use new features. diff --git a/doc/README.SunPro b/doc/README.SunPro new file mode 100644 index 0000000..6808732 --- /dev/null +++ b/doc/README.SunPro @@ -0,0 +1,5 @@ +Sun C 5.9: +gmake \ + CC=/opt/SUNWspro/bin/c99 COMPILE_FLAGS="-I. -O2 -mt -v" \ + MAKEDEPEND="/opt/SUNWspro/bin/c99 -xM1" clean all + diff --git a/doc/daemon.php.html b/doc/daemon.php.html new file mode 100644 index 0000000..c86ccff --- /dev/null +++ b/doc/daemon.php.html @@ -0,0 +1,367 @@ + + + + + + +Unix Daemon Server Programming + + + +
+ + + + + +
+ +

Unix Daemon Server Programming

+ +

Introduction

+ +

Unix processes works either in foreground or background. A process +running in foreground interacts with the user in front of the terminal +(makes I/O), whereas a background process runs by itself. The user can +check its status but he doesn't (need to) know what it is doing. The +term 'daemon' is used for processes that performs service in +background. A server is a process that begins execution at startup (not +neccessarily), runs forever, usually do not die or get restarted, +operates in background, waits for requests to arrive and respond to +them and frequently spawn other processes to handle these requests.

+ +

Readers are suppossed to know Unix fundamentals and C language. For +further description on any topic use "man" command (I write useful +keywords in brackets), it has always been very useful, trust me :)) +Keep in mind that this document does not contain everything, it is just +a guide.

+ + +

1) Daemonizing (programming to operate in background) [fork]

+ +

First the fork() system call will be used to create a copy of our +process(child), then let parent exit. Orphaned child will become a +child of init process (this is the initial system process, in other +words the parent of all processes). As a result our process will be +completely detached from its parent and start operating in background.

+ +
	i=fork();
+	if (i<0) exit(1); /* fork error */
+	if (i>0) exit(0); /* parent exits */
+	/* child (daemon) continues */
+
+ +

2) Process Independency [setsid]

+ +

A process receives signals from the terminal that it is connected +to, and each process inherits its parent's controlling tty. A server +should not receive signals from the process that started it, so it must +detach itself from its controlling tty.

+ +

In Unix systems, processes operates within a process group, so that +all processes within a group is treated as a single entity. Process +group or session is also inherited. A server should operate +independently from other processes.

+ +
	setsid() /* obtain a new process group */
+
+ +

This call will place the server in a new process group and session +and detach its controlling terminal. (setpgrp() is an alternative for +this)

+ +

3) Inherited Descriptors and Standart I/0 Descriptors [gettablesize,fork,open,close,dup,stdio.h]

+ +

Open descriptors are inherited to child process, this may cause the +use of resources unneccessarily. Unneccesarry descriptors should be +closed before fork() system call (so that they are not inherited) or +close all open descriptors as soon as the child process starts running.

+ +
	for (i=getdtablesize();i>=0;--i) close(i); /* close all descriptors */
+
+ +

There are three standart I/O descriptors: standart input 'stdin' +(0), standart output 'stdout' (1), standart error 'stderr' (2). A +standard library routine may read or write to standart I/O and it may +occur to a terminal or file. For safety, these descriptors should be +opened and connectedthem to a harmless I/O device (such as /dev/null).

+ +
	i=open("/dev/null",O_RDWR); /* open stdin */
+	dup(i); /* stdout */
+	dup(i); /* stderr */
+
+ +

As Unix assigns descriptors sequentially, fopen call will open stdin and dup calls will provide a copy for stdout and stderr.

+ +

4) File Creation Mask [umask]

+ +

Most servers runs as super-user, for security reasons they should +protect files that they create. Setting user mask will pre vent +unsecure file priviliges that may occur on file creation.

+ +
	umask(027);
+
+ +

This will restrict file creation mode to 750 (complement of 027).

+ +

5) Running Directory [chdir]

+ +

A server should run in a known directory. There are many advantages, +in fact the opposite has many disadvantages: suppose that our server is +started in a user's home directory, it will not be able to find some +input and output files.

+ +
	chdir("/servers/");
+
+ +

The root "/" directory may not be appropriate for every server, it +should be choosen carefully depending on the type of the server.

+ +

6) Mutual Exclusion and Running a Single Copy [open,lockf,getpid]

+ +

Most services require running only one copy of a server at a time. +File locking method is a good solution for mutual exclusion. The first +instance of the server locks the file so that other instances +understand that an instance is already running. If server terminates +lock will be automatically released so that a new instance can run. +Recording the pid of the running instance is a good idea. It will +surely be efficient to make 'cat mydaamon.lock' instead of 'ps -ef|grep +mydaemon'

+ +
	lfp=open("exampled.lock",O_RDWR|O_CREAT,0640);
+	if (lfp<0) exit(1); /* can not open */
+	if (lockf(lfp,F_TLOCK,0)<0) exit(0); /* can not lock */
+	/* only first instance continues */
+
+	sprintf(str,"%d\n",getpid());
+	write(lfp,str,strlen(str)); /* record pid to lockfile */
+
+ +

7) Catching Signals [signal,sys/signal.h]

+ +

A process may receive signal from a user or a process, its best to +catch those signals and behave accordingly. Child processes send +SIGCHLD signal when they terminate, server process must either ignore +or handle these signals. Some servers also use hang-up signal to +restart the server and it is a good idea to rehash with a signal. Note +that 'kill' command sends SIGTERM (15) by default and SIGKILL (9) +signal can not be caught.

+ +
	signal(SIG_IGN,SIGCHLD); /* child terminate signal */
+
+ +

The above code ignores the child terminate signal (on BSD systems +parents should wait for their child, so this signal should be caught to +avoid zombie processes), and the one below demonstrates how to catch +the signals.

+ +
	void Signal_Handler(sig) /* signal handler function */
+	int sig;
+	{
+		switch(sig){
+			case SIGHUP:
+				/* rehash the server */
+				break;		
+			case SIGTERM:
+				/* finalize the server */
+				exit(0)
+				break;		
+		}	
+	}
+
+	signal(SIGHUP,Signal_Handler); /* hangup signal */
+	signal(SIGTERM,Signal_Handler); /* software termination signal from kill */
+
+ +

First we construct a signal handling function and then tie up signals to that function.

+ +

8) Logging [syslogd,syslog.conf,openlog,syslog,closelog]

+ +

A running server creates messages, naturally some are important and +should be logged. A programmer wants to see debug messages or a system +operator wants to see error messages. There are several ways to handle +those messages.

+ +

Redirecting all output to standard I/O : This is what ancient +servers do, they use stdout and stderr so that messages are written to +console, terminal, file or printed on paper. I/O is redirected when +starting the server. (to change destination, server must be restarted) +In fact this kind of a server is a program running in foreground (not a +daemon).

+ +
	# mydaemon 2> error.log
+
+ +

This example is a program that prints output (stdout) messages to +console and error (stderr) messages to a file named "error.log". Note +that this is not a daemon but a normal program.

+

Log file method : All messages are logged to files (to different files as needed). There is a sample logging function below.

+ +
	void log_message(filename,message)
+	char *filename;
+	char *message;
+	{
+	FILE *logfile;
+		logfile=fopen(filename,"a");
+		if(!logfile) return;
+		fprintf(logfile,"%s\n",message);
+		fclose(logfile);
+	}
+
+	log_message("conn.log","connection accepted");
+	log_message("error.log","can not open file");
+
+ +

Log server method : A more flexible logging technique is +using log servers. Unix distributions have system log daemon named +"syslogd". This daemon groups messages into classes (known as facility) +and these classes can be redirected to different places. Syslog uses a +configuration file (/etc/syslog.conf) that those redirection rules +reside in.

+ +
	openlog("mydaemon",LOG_PID,LOG_DAEMON)
+	syslog(LOG_INFO, "Connection from host %d", callinghostname);
+	syslog(LOG_ALERT, "Database Error !");
+	closelog();
+
+ +

In openlog call "mydaemon" is a string that identifies our daemon, +LOG_PID makes syslogd log the process id with each message and +LOG_DAEMON is the message class. When calling syslog call first +parameter is the priority and the rest works like printf/sprintf. There +are several message classes (or facility names), log options and +priority levels. Here are some examples :

+ +
+
Message classes : LOG_USER, LOG_DAEMON, LOG_LOCAL0 to LOG_LOCAL7
+
Log options : LOG_PID, LOG_CONS, LOG_PERROR
+
Priority levels : LOG_EMERG, LOG_ALERT, LOG_ERR, LOG_WARNING, LOG_INFO
+
+ +

About

+ +

This text is written by Levent Karakas <levent at mektup dot at >. +Several books, sources and manual pages are used. This text includes a +sample daemon program (compiles on Linux 2.4.2, OpenBSD 2.7, SunOS 5.8, +SCO-Unix 3.2 and probably on your flavor of Unix). You can also +download plain source file : exampled.c. Hope you find this document useful. We do love Unix.

+ +
+/*
+UNIX Daemon Server Programming Sample Program
+Levent Karakas <levent at mektup dot at> May 2001
+
+To compile:	cc -o exampled examped.c
+To run:		./exampled
+To test daemon:	ps -ef|grep exampled (or ps -aux on BSD systems)
+To test log:	tail -f /tmp/exampled.log
+To test signal:	kill -HUP `cat /tmp/exampled.lock`
+To terminate:	kill `cat /tmp/exampled.lock`
+*/
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <unistd.h>
+
+#define RUNNING_DIR	"/tmp"
+#define LOCK_FILE	"exampled.lock"
+#define LOG_FILE	"exampled.log"
+
+void log_message(filename,message)
+char *filename;
+char *message;
+{
+FILE *logfile;
+	logfile=fopen(filename,"a");
+	if(!logfile) return;
+	fprintf(logfile,"%s\n",message);
+	fclose(logfile);
+}
+
+void signal_handler(sig)
+int sig;
+{
+	switch(sig) {
+	case SIGHUP:
+		log_message(LOG_FILE,"hangup signal catched");
+		break;
+	case SIGTERM:
+		log_message(LOG_FILE,"terminate signal catched");
+		exit(0);
+		break;
+	}
+}
+
+void daemonize()
+{
+int i,lfp;
+char str[10];
+	if(getppid()==1) return; /* already a daemon */
+	i=fork();
+	if (i<0) exit(1); /* fork error */
+	if (i>0) exit(0); /* parent exits */
+	/* child (daemon) continues */
+	setsid(); /* obtain a new process group */
+	for (i=getdtablesize();i>=0;--i) close(i); /* close all descriptors */
+	i=open("/dev/null",O_RDWR); dup(i); dup(i); /* handle standart I/O */
+	umask(027); /* set newly created file permissions */
+	chdir(RUNNING_DIR); /* change running directory */
+	lfp=open(LOCK_FILE,O_RDWR|O_CREAT,0640);
+	if (lfp<0) exit(1); /* can not open */
+	if (lockf(lfp,F_TLOCK,0)<0) exit(0); /* can not lock */
+	/* first instance continues */
+	sprintf(str,"%d\n",getpid());
+	write(lfp,str,strlen(str)); /* record pid to lockfile */
+	signal(SIGCHLD,SIG_IGN); /* ignore child */
+	signal(SIGTSTP,SIG_IGN); /* ignore tty signals */
+	signal(SIGTTOU,SIG_IGN);
+	signal(SIGTTIN,SIG_IGN);
+	signal(SIGHUP,signal_handler); /* catch hangup signal */
+	signal(SIGTERM,signal_handler); /* catch kill signal */
+}
+
+main()
+{
+	daemonize();
+	while(1) sleep(1); /* run */
+}
+
+/* EOF */
+
+
+ +

 

+

Last Update : 16.05.2001

+

 

+ + +

+ +
+ + + \ No newline at end of file diff --git a/doc/makedepend.html b/doc/makedepend.html new file mode 100644 index 0000000..c5b2e77 --- /dev/null +++ b/doc/makedepend.html @@ -0,0 +1,1771 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Dr. Dobb's | Dependency Management | April 1, 2006 + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +
+ + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ FOCAL POINTS: Sponsored Links
+ + + + + + +Focus on Office Business Applications (OBA) + +
+ + +
+
+ + +
+ + + Site Archive (Complete)
+
+ + ABOUT US | + CONTACT | + ADVERTISE | + SUBSCRIBE | + + SOURCE CODE | + CURRENT PRINT ISSUE | + + NEWSLETTERS + | + RESOURCES + | + BLOGS + | + PODCASTS + | + CAREERS + +
+
+
+ + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Open Source +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Email
+
+ Print
+ Reprint +

add to:
Del.icio.us
+ Digg
+ Google
+ Spurl +
Slashdot
+ Y! MyWeb
+ Blink
+ Furl +
+
+ April 01, 2006
+ +
Dependency Management
+

The irony of Make

+
+ + + + + + +
John Graham-Cumming
+ + + + + + + + + +
John explores the irony of Make.
+ + + + + +

+John is vice president of engineering at Electric Cloud, which +focuses on reducing software build times. He can be contacted at +jgc@electric-cloud.com. +

+


+

+ +

+ +

+ +

+Make giveth and Make taketh away. It's ironic that the tool Make, +designed to solve the "I've changed some source now what do I need to +recompile?" problem, creates dependency nightmares.

+

+ +

+Any project larger than a "Hello World" example faces dependency +management problems. Dependencies must be generated and kept up to date +as you add to, modify, and delete from the project. And Make itself +provides no tools for dealing with this problem—all Make provides is a +mechanism for expressing the relationships between files with its familiar target : prerequisite1 prerequisite2... syntax.

+

+ +

+The target of the rule (the file that will be built) appears before the +colon (:) and the files that the target depends upon (called either the +"dependencies" or "prerequisites") appears after the colon (:). For +example:

+

+

+foo.o : foo.c header.h
+

+

+

+ +

+has target foo.o and prerequisites foo.c and header.h.

+

+ +

+Even Make's dependency syntax is flawed because it incorporates both +"foo.o must be updated if header.h or foo.c are changed" and "foo.o is +the result of compiling foo.c". Thus, anything to the right of the ":" +is a prerequisite, but the first prerequisite where there's a rule body +(that is, commands) is special—it's the prerequisite that's passed to +the compiler (or other command) to actually generate the target.

+

+ +

+Look at this GNU Make example (I use GNU Make throughout this article +because of its wide platform coverage and large set of features):

+

+

+foo.o: foo.c header.h system.h
+ @echo Compiling $@ from $<...
+

+

+

+ +

+which outputs:

+

+

+Compiling foo.o from foo.c...
+

+

+

+ +

+Here, foo.o is built if foo.c, header.h, or system.h change, but the rule also states that foo.o is made from foo.c (in GNU Make terms, the target of the rule to the left side of the ":" is written $@ and the first prerequisite is $<). If the example were written like this:

+

+

+foo.o: foo.c
+foo.o: header.h system.h
+ @echo Compiling $@ from $<...
+

+

+

+ +

+the output would be:

+

+

+Compiling foo.o from header.h...
+

+

+

+ +

+which is clearly wrong. If you want to continue exploring Make's idiosyncrasies with $<, play around with this Makefile:

+

+

+foo.o: header.h
+foo.o: system.h
+foo.o: foo.c
+foo.o:
+ @echo Compiling $@ from $<...
+

+

+

+ +

+and try permuting the first three lines to see how Make's interpretation of the Makefile changes.

+

+ +

+The biggest problem of all is generating these rules for a large +project. In the rest of this article, I use this contrived example +Makefile as a starting point:

+

+

+.PHONY: all
+ all: foo.o bar.o baz.o
+
+ foo.o: foo.c foo.h common.h header.h
+ bar.o: bar.c bar.h common.h header.h ba.h
+ baz.o: baz.c baz.h common.h header.h ba.h
+

+

+

+ +

+Three object files (foo.o, bar.o, and baz.o) are built from +corresponding C files (foo.c, bar.c, and baz.c). Each .o file has +dependencies on various header files as expressed in the Makefile. The +Makefile uses GNU Make's built-in rules to perform compilation using +the system's compiler.

+

+ +

+There's no mention here of the final executable being built because I +concentrate on dealing with dependencies between sources and objects. +Relationships between objects are usually easier to maintain by hand as +there are fewer of them and the relationships are part of the product +design.

+

+ +

+Because maintaining any real Makefile by hand is impossible, many +projects use the widely available makedepend program (makedepend is +usually installed on UNIX and CYGWIN systems). makedepend reads C and +C++ files looking at the #include statements, follows the #includes, and builds the dependency lines for you. A basic way of incorporating makedepend in a project is a special depend target:

+

+

+.PHONY: all
+ all: foo.o bar.o baz.o
+
+ SRCS = foo.c bar.c baz.c
+
+ DEPENDS = dependencies.mk
+ .PHONY: depend
+ depend:
+ @makedepend -f - $(SRCS) > $(DEPENDS)
+ -include $(DEPENDS)
+

+

+

+ +

+Doing makedepend with this Makefile causes the depend rule to execute, which runs makedepend on the sources (defined in the SRCS variable) and outputs the dependency lines to dependencies.mk (defined by the DEPENDS variable).

+

+ +

+The Makefile includes the dependencies lines in its final line. dependencies.mk looks like this:

+

+

+# DO NOT DELETE
+
+foo.o: foo.h header.h common.h
+bar.o: bar.h header.h common.h ba.h
+baz.o: baz.h header.h common.h ba.h
+

+

+

+ +

+Notice that makedepend doesn't try to define the relationship between +an object file (for example, foo.o) and the source file it is made from +(foo.c). In this case, GNU Make's standard rules automatically find the +related .c file. There are two problems with the makedepend style:

+

+ +

+

    +
  • Running makedepend can be slow, as every source file has to be searched even if there are no changes.
  • +
  • It's a manual step. Before every make, users have to do makedepend to ensure that the dependencies are correct.
  • +
+

+ +

+ +

+The answer to these problems is automation. Here's a version of the +Makefile that still uses makedepend to generate dependencies, but +automates the process, and only runs makedepend for sources that have +changed:

+

+

+.PHONY: all
+ all: foo.o bar.o baz.o
+ SRCS = foo.c bar.c baz.c
+ %.d : %.c
+ @makedepend -f - $< | sed 's,\($*\.o\)[ :]*,\1
+ $@ : ,g' > $@
+ -include $(SRCS:.c=.d)
+

+

+

+ +

+It works by associating a .d file with each .c; for example, foo.o has +a foo.d file that only contains the dependency line for foo.o. Here are +foo.d's contents:

+

+

+# DO NOT DELETE
+foo.o foo.d : foo.h header.h common.h
+

+

+

+ +

+This line specifies when to rebuild foo.o, but also that foo.d should +be rebuilt under the same conditions—if any of the sources associated +with foo.o change, then foo.d gets rebuilt. foo.c isn't mentioned in +this list because it's mentioned as part of the pattern rule for +rebuilding a .d file (%.d : %.c +means that foo.d gets rebuilt if foo.c itself changes). foo.d got added +to the dependency line created by makedepend using the aforementioned sed magic.

+

+ +

+The final line of the Makefile includes all the .d files: The $(SRCS:.c=.d) macro transforms the list of sources in the SRCS variable by changing the extension from .c to .d. The include +also tells GNU Make to check to see if the .d files need rebuilding. +GNU Make looks to see if there are rules to rebuild included Makefiles +(in this case, the .d files), rebuilds them if necessary (following the +dependencies specified in the Makefile), then restarts. This "Makefile +remaking" feature (see section 3.7 "How Makefiles Are Remade" in the +FSF's GNU Make manual; +http://www.gnu.org/software/make/manual/html_mono/make.html#SEC20) +means that simply typing "make" will rebuild any dependency files that +need rebuilding—but only if the sources have changed. Then, GNU Make +will perform the build, taking into account the new dependencies.

+

+ +

+Unfortunately, this Makefile breaks with a fatal error if a header file +is removed. If header.h is no longer needed and all references to it +are removed from the .c files and the file is removed from disk, this +error occurs when Make is run:

+

+

+No rule to make target 'header.h',
+ needed by 'foo.d'.
+

+

+

+ +

+This happens because header.h is still mentioned in foo.d as being a +prerequisite of foo.d; hence, foo.d cannot be rebuilt. This Catch-22 +can be fixed by making the generation of foo.d smarter. The new foo.d +includes the dependencies for foo.o and foo.d separately. foo.d's +dependencies are wrapped in a call to GNU Make's $(wildcard) +function (see section 4.4.3 "The Function Wildcard" in the FSF's GNU +Make manual; http://www.gnu.org/software/make/manual/html_mono/make +.html#SEC33). Here's the new foo.d:

+

+

+# DO NOT DELETE
+foo.d :
+ $(wildcard foo.h header.h common.h)
+foo.o : foo.h header.h common.h
+

+

+

+ +

+And here's the updated Makefile with a new invocation of makedepend, followed by a sed line that creates the modified .d file:

+

+

+.PHONY: all
+ all: foo.o bar.o baz.o
+ SRCS = foo.c bar.c baz.c
+ %.d : %.c
+ @makedepend -f - $< | sed 's,
+ \($*\.o\)[ :]*\(.*\),
+ $@ : $$\(wildcard \2\)\n\1 : \2,g' > $@
+ -include $(SRCS:.c=.d)
+

+

+

+ +

+Now removing a header file doesn't break the Make: When foo.d is parsed, the dependency line for foo.d is passed through $(wildcard). When there are no globbing symbols such as "*" or "?" in the filename, $(wildcard) acts +as an existence filter, removing those files that don't exist from the +list. So, if header.h had been removed, the first line of foo.d would +be equivalent to:

+

+

+foo.d : foo.h common.h
+

+

+

+ +

+and the Make would work correctly. This example Makefile now works when .c files are added (users just update SRCS and the new .d file is created automatically), when .c files are removed (users update SRCS +and the old .d file is ignored), and when headers are added (because +that requires touching an existing .c or .h, the .d file is +regenerated). And when they are removed, the $(wildcard) hides the deletion and the .d file is regenerated.

+

+ +

+An optimization is to remove the need for GNU Make to restart by +merging the rule that makes the .d file into the rule that makes the .o +file. Because the .d file is updated if (and only if) the .o file needs +to be updated (both are updated when any of the sources for the .o +change), it's possible to have the makedepend occur at the same time as the compilation:

+

+

+.PHONY: all
+ all: foo.o bar.o baz.o
+ SRCS = foo.c bar.c baz.c
+ %.o : %.c
+ @makedepend -f - $< |
+ sed 's,\($*\.o\)[ :]*\(.*\),
+ $@ : $$\(wildcard \2\)\n\1 : \2,g' > $*.d
+ @$(COMPILE.c) -o $@ $<
+ -include $(SRCS:.c=.d)
+

+

+

+ +

+This rule makes use of $*, another GNU Make variable. $* is the part of the pattern %.c that matches the %. If this rule is building foo.o from foo.c, then $* is just foo. $* +is used to create the name of the .d file that makedepend writes to. +This final version does not use GNU Make's "Makefile remaking" system. +There are no rules for making .d files (they are made as a side effect +of making the .o); hence, GNU Make does not have to restart, providing +the best combination of accuracy and speed possible.

+

+ +

+In general, it's a bad idea to have a rule that makes multiple files +because it's impossible for GNU Make to find the rule that makes a file +if it's created as a side effect of something else. In this case, that +behavior is desired: You want to hide the creation of .d files from GNU +Make so that it doesn't try to make them and then have to restart. A +similar idea was proposed by Tom Tromey, without the $(wildcard) trick, +and more information about building dependency files can be found on +the GNU Make maintainer Paul Smith's web site at +http://make.paulandlesley.org/autodep.html. Another good resource for +any GNU Make developer—once you've purchased the FSF's GNU Make +manual—is Robert Mecklenburg's Managing Projects with GNU Make (O'Reilly & Associates, 2004).

+

+ +

+Finally, it's possible to omit makedepend altogether if you are using the GNU GCC compiler. It has a -MD option that does the work of makedepend at the same time as the compilation:

+

+

+.PHONY: all
+ all: foo.o bar.o baz.o
+ SRCS = foo.c bar.c baz.c
+ %.o : %.c
+ @$(COMPILE.c) -MD -o $@ $<
+ @sed -i 's,\($*\.o\)[ :]*\(.*\),
+ $@ : $$\(wildcard
+ \2\)\n\1 : \2,g' $*.d
+ -include $(SRCS:.c=.d)
+

+

+

+ +

+For example, the compilation step for foo.o will create foo.d from foo.c and then sed is run on the foo.d to add the extra line for foo.d containing the $(wildcard).

+

+ +

+The use of GCC -MD is an example of creating +dependencies without using makedepend. There are a number of other +possibilities. For example, GCC has -M and -MM options that just output dependency information (-M outputs all dependencies and -MM omits the system headers because they are unlikely to change).

+

+ +

+Another option is the program fastdep +(http://www.irule.be/bvh/c++/fastdep/), which aims to be a fast +replacement for makedepend. Finally, Windows programmers can use +Microsoft CL's /showincludes option to get include information and build dependency information.

+

+ +

+All of these solutions share some common problems. They only work well +for C and C++ code and need to be modified to handle other languages +(although luckily, not all languages have the same dependency forests +as C and C++). In addition, the files included might change as the +result of preprocessor defines; hence, makedepend (or an equivalent +program) needs to be told about any command line -D defines so that it builds the right dependency information.

+

+ +

+Despite the problems outlined here, these Makefile snippets do provide +a reliable way of keeping C and C++ dependencies up to date +automatically and quickly.

+

+ +

+ +DDJ

+

+ +

+ +

+ +
+ + +
+ + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + +
+ + +
+
RELATED ARTICLES
+ + + + + + + + + +
+
+ +
TOP 5 ARTICLES
+
+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + + + + + + + + + + +
+ + + +
+ + + + +
+
+ +LIFE 2.0 SUMMIT SPRING 2008 +
+
+Join us on the CMP Region for Life 2.0 Summit Spring, a virtual conference, happening in Second Life. Register for your COMPLIMENTARY PASS today!
+
+ +
+ + + + + + + + +
+ +
+ + +
+
+DR. DOBB'S CAREER CENTER + + + + + + + +
+
+Ready to take that job and shove it? open | close
+
+
+ + + + + + + + + + + + + + + + +
Search jobs on Dr. Dobb's TechCareers
+ + + + + +
+ +
Function:
+
+
Keyword(s):
+
+
+
State:  
+
+ +
+ + +
  • Post Your Resume +
  • Employers Area +
  • News & Features +
  • Blogs & Forums +
  • Career Resources +

    +Browse By:
    + Location | Employer | City
    + + + + + + + + +
  • Most Recent Posts:
    + + +Software Sales Specialist
    Hewlett Packard seeking Software Sales Specialist in San Francisco, CA

    Programmer / System Analyst
    American First CU seeking Programmer / System Analyst in La Habra, CA

    Senior Engineer
    Mine Safety Appliances seeking Senior Engineer in Cranberry Twp, PA

    Sr Staff Circuit Designer
    Xilinx seeking Sr Staff Circuit Designer in San Jose, CA

    Senior Embedded Applications Software Developer
    SigmaTel seeking Senior Embedded Applications Software Developer in Austin, TX

    +
    +
    +
    + +
    + + + + + +
    + +
    + + + + + + +
    +
    + + + + + + + + + + + + + +
    + + + + + + + + + + + +
    + + + +
    +
    +
    + + + + + + +
    +
    + + +
    + + + +
    + + + +
    + +
    MARKETPLACE (Sponsored Links)
    Automate service desk activities and integrate processes across IT. Learn more here.
    Search file shares, SharePoint sites, Exchange Public Folders, Lotus Notes repositories, and more!
    Develop 10 times faster ! ALM, IDE, .Net, RAD, 5GL, Database, 5GL, 64-bit, etc. Free Express version
    Understand C/C++ code in less time. A new team member ? Inherited legacy code ? Get up to speed fast...
    Easily create an automated, repeatable process for building and deploying software.
    Advertise With Us
    + +
    + + + +
    +  + + + +
    DEPARTMENTS
    + + + +
    + + +
    + + +
    + + + +
    + + +
    + + +
    + + +
    + + +
    + + + +
    + + +
    + + + +
    + + +
    + + +
    +
    + +
    + +
    +
    +
    ♦ sponsored
    + + + +
    +
    + + + + + + + + + +
    +
    + + + + + + + + +
    + + + +
    + + +
    + +
    + + + + + + + +
    + +
    + + + + + +
    + + + + + + + +
    + + + +
    + + + + + +
    +
    + +
    + + +
    + +
    + + + + + + + + + + + + | + +
    +
    + + Related Sites: + DotNetJunkies, + SD Expo, + SqlJunkies +
    + + + + + + + + + + + + + \ No newline at end of file diff --git a/makefiles/clean.mk b/makefiles/clean.mk new file mode 100644 index 0000000..711d6ae --- /dev/null +++ b/makefiles/clean.mk @@ -0,0 +1,29 @@ +# cleans up directories +# +# requires: +# - BINS, OBJS, CPPOBJS, BIN_OBJS +# - CMODULES, CPPMODULES +# - SUBDIRS +# +# provides: +# - target: clean +# - target: distclean + +.PHONY: clean local_clean +clean: local_clean + @test -z "$(SUBDIRS)" || ( set -e; for d in $(SUBDIRS)""; do \ + (set -e; $(MAKE) -C $$d clean || exit 1); done) + -@rm *.bak 2>/dev/null + -@rm *~ 2>/dev/null + -@rm *.d port/*.d 2>/dev/null + -@rm $(BINS) 2>/dev/null + -@rm $(OBJS) $(CPPOBJS) $(BIN_OBJS) 2>/dev/null + -@rm exec/* + -@rm *.core + -@rm $(CMODULES) $(CPPMODULES) + -@rm $(CMODULES .o=.d) $(CPPMODULES .o=.d) + +.PHONY: distclean local_distclean +distclean: clean local_distclean + @test -z "$(SUBDIRS)" || ( set -e; for d in $(SUBDIRS)""; do \ + (set -e; $(MAKE) -C $$d distclean || exit 1); done) diff --git a/makefiles/compiler.mk b/makefiles/compiler.mk new file mode 100644 index 0000000..a0176a5 --- /dev/null +++ b/makefiles/compiler.mk @@ -0,0 +1,174 @@ +# sets compiler settings +# +# requires: +# - INCLUDE_DIRS +# +# provides: +# - BIN_OBJS: the object files we need for the binaries (containing the main) +# + +# TODO: sort out if we need so many flags :-) +# TODO: why don't specify the CFLAGS directly :-) +# - TODO: handle also -g -O2 +# -DTRACEDEBUG "$(DEBUGLEVEL)" "TRACE +# -DDEBUG $(DEBUGLEVEL)" "DEBUG" +# when nothing is defined, define -DNDEBUG (release) +# TODO: do we use precompiled header files feature of gcc (also -Winvalid-pch) + +# debug/tracing flags +ifeq "$(DEBUGLEVEL)" "TRACE" +DEBUGLEVELFLAGS=-DTRACEDEBUG -DDEBUG +else +ifeq "$(DEBUGLEVEL)" "DEBUG" +DEBUGLEVELFLAGS=-DDEBUG +else +DEBUGLEVELFLAGS=-DNDEBUG +endif +endif + +# -Wswitch-default: not good for switches with enums +# -Wsystem-headers: bad idea, as header files are usually happily broken :-) +# -Wtraditional: we don't want to program tradition K&R C anymore! +# -Wunsafe-loop-optimizations: ?? +# -Wno-attributes, -Wmissing-format-attribute: ?? later +# -Wpacked -Wpadded: ?? very questionable +# -Wunreachable-code: doesn't work +# -Wno-div-by-zero: we get NaN and friend over macros, so need for funny tricks :-) +# -Wstrict-overflow=5 is relatively new, later maybe +# -fstack-protector or -fstack-protector-all: should be used, but U +# have currently big problems to get it around compiler gcc and -lssl +# probing! FIXME later +# -fstack-protector-all: does something funny to the shared objects.. +# -Wstack-protector makes no sense without SSP +# everything implied by -Wall is not explicitly specified (gcc 4.2.3) + +# compilation flags and compilers +COMMON_COMPILE_FLAGS = \ + -g -O2 -D_REENTRANT \ + -fstrict-aliasing \ + -pedantic -Wall -Werror \ + -Wunused -Wno-import \ + -Wformat -Wformat-y2k -Wformat-nonliteral -Wformat-security -Wformat-y2k \ + -Wswitch-enum -Wunknown-pragmas -Wfloat-equal \ + -Wundef -Wshadow -Wpointer-arith \ + -Wcast-qual -Wcast-align \ + -Wwrite-strings -Wconversion -Waggregate-return \ + -Wmissing-noreturn \ + -Wno-multichar -Wparentheses -Wredundant-decls \ + -Winline \ + -Wdisabled-optimization \ + $(INCLUDE_DIRS) + +ifeq "$(GCC_MAJOR_VERSION)" "4" +COMMON_COMPILE_FLAGS += \ + -Wfatal-errors -Wmissing-include-dirs -Wvariadic-macros \ + -Wvolatile-register-var \ + -Wstrict-aliasing=2 -Wextra -Winit-self +endif + +ifeq "$(GCC_MAJOR_VERSION)" "3" + +# gcc 3.3, testend on OpenBSD 4.2 +ifeq "$(GCC_MINOR_VERSION)" "3" +COMMON_COMPILE_FLAGS += \ + -W +endif + +# gcc 3.4, not tested yet +ifeq "$(GCC_MINOR_VERSION)" "4" +COMMON_COMPILE_FLAGS += \ + -Wstrict-aliasing=2 -Wextra -Winit-self +endif + +endif + +COMPILE_FLAGS = \ + $(COMMON_COMPILE_FLAGS) \ + -std=c99 \ + -Wnonnull \ + -Wbad-function-cast -Wstrict-prototypes \ + -Wmissing-prototypes -Wmissing-declarations \ + -Wnested-externs + +# gcc 4.x +ifeq "$(GCC_MAJOR_VERSION)" "4" +COMPILE_FLAGS += \ + -Wc++-compat -Wdeclaration-after-statement -Wold-style-definition +endif + +ifeq "$(GCC_MAJOR_VERSION)" "3" + +# gcc 3.4, not tested yet +ifeq "$(GCC_MINOR_VERSION)" "4" +COMPILE_FLAGS += \ + -Wdeclaration-after-statement -Wold-style-definition +endif + +# gcc 3.3, testend on OpenBSD 4.2 +ifeq "$(GCC_MINOR_VERSION)" "3" +#COMPILE_FLAGS += \ +# -Wdeclaration-after-statement +endif + + +endif + +CCPP_COMPILE_FLAGS = \ + $(COMMON_COMPILE_FLAGS) \ + -std=c++98 + +# gcc 4.x +ifeq "$(GCC_MAJOR_VERSION)" "4" +CCPP_COMPILE_FLAGS += \ + -Wno-invalid-offsetof +endif + +ifeq "$(GCC_MAJOR_VERSION)" "3" + +# gcc 3.4, not tested yet +ifeq "$(GCC_MINOR_VERSION)" "4" +CCPP_COMPILE_FLAGS += \ + -Wno-invalid-offsetof +endif + +# gcc 3.3, testend on OpenBSD 4.2 +ifeq "$(GCC_MINOR_VERSION)" "3" +#CCPP_COMPILE_FLAGS += \ +# -Wdeclaration-after-statement +endif + +endif + +CFLAGS = $(COMPILE_FLAGS) $(PLATFORM_COMPILE_FLAGS) $(DEBUGLEVELFLAGS) +CCPPFLAGS = $(CCPP_COMPILE_FLAGS) $(PLATFORM_COMPILE_FLAGS) $(DEBUGLEVELFLAGS) +CC = gcc +CCPP = g++ + +LDFLAGS = +LIBS = $(LIBS_DL) $(LIBS_SSP) +LINK = $(CC) +CCPP_LINK = $(CCPP) + +%.o : %.c + $(CC) -c -o $@ $(CFLAGS) $< + +%.o : %.cc + $(CCPP) -c -o $@ $(CCPPFLAGS) $< + +%$(EXE): %.o $(OBJS) + $(LINK) -o $@ $(LIBS) $(OBJS) $< + +%.sho : %.c + $(CC) -c -o $@ -fPIC -DSHARED $(CFLAGS) $< + +%$(SO) : %.sho $(OBJS) + $(LINK) -shared -o $@ $(LDFLAGS) $(LIBS) $(OBJS) $< + +%.sho++ : %.cc + $(CCPP) -c -o $@ -fPIC -DSHARED $(CCPPFLAGS) $< + +%$(SO) : %.sho++ $(OBJS) $(CPPOBJS) + $(CCPP_LINK) -shared -o $@ $(LDFLAGS) $(LIBS) $(OBJS) $(CPPOBJS) $< + +BIN_OBJS = $(BINS:$(EXE)=.o) + diff --git a/makefiles/depend.mk b/makefiles/depend.mk new file mode 100644 index 0000000..2ff7bd5 --- /dev/null +++ b/makefiles/depend.mk @@ -0,0 +1,29 @@ +# provides generic rules for C/C++ dependeny generation using +# 'makedepend', 'gcc -MM' or similar mechanisms +# +# requires: +# - compilers CC and CCPP +# - INCLUDEDIRS +# - OBJS, CPP_OBJS and BIN_OBJS +# +# provides: +# - included dependency files +# +# author: Andreas Baumann, abaumann at yahoo dot com + +MAKEDEPEND = $(CC) -MM $(INCLUDE_DIRS) +CCPP_MAKEDEPEND = $(CCPP) -MM $(INCLUDE_DIRS) + +%.d : %.c + @echo Generating dependencies for $< + @$(MAKEDEPEND) $(CFLAGS) $< | \ + sed "s,\($*\.o\)[ :]*\(.*\),$@ : $$\(wildcard \2\)\&\&\&\1 : \2,g" | tr -s '&' "\n" > $@ + +%.d : %.cc + @echo Generating dependencies for $< + @$(CCPP_MAKEDEPEND) $(CCPPFLAGS) $< | \ + sed "s,\($*\.o\)[ :]*\(.*\),$@ : $$\(wildcard \2\)\&\&\&\1 : \2,g" | tr -s '&' "\n" > $@ + +-include $(OBJS:.o=.d) +-include $(CPP_OBJS:.o=.d) +-include $(BIN_OBJS:.o=.d) diff --git a/makefiles/guess_env b/makefiles/guess_env new file mode 100755 index 0000000..f11ef5a --- /dev/null +++ b/makefiles/guess_env @@ -0,0 +1,106 @@ +#!/bin/sh + +UNAME_SYSTEM=`(uname -s) 2>/dev/null` +UNAME_RELEASE=`(uname -r) 2>/dev/null` +UNAME_VERSION=`(uname -v) 2>/dev/null` +UNAME_MACHINE=`(uname -m) 2>/dev/null` + +# operating system and major, minor version, more should not be necessary +case "$UNAME_SYSTEM.$UNAME_RELEASE" in + Linux*) PLATFORM=LINUX + OS_MAJOR_VERSION=`echo $UNAME_RELEASE | cut -d . -f 1` + OS_MINOR_VERSION=`echo $UNAME_RELEASE | cut -d . -f 2` + LIBS_DL='-ldl' + ;; + + FreeBSD*) PLATFORM=FREEBSD + OS_MAJOR_VERSION=`echo $UNAME_RELEASE | cut -d . -f 1` + OS_MINOR_VERSION=`echo $UNAME_RELEASE | cut -d . -f 2 | cut -d - -f 1` + LIBS_DL= + LIBS_SSP= + ;; + + OpenBSD*) PLATFORM=OPENBSD + OS_MAJOR_VERSION=`echo $UNAME_RELEASE | cut -d . -f 1` + OS_MINOR_VERSION=`echo $UNAME_RELEASE | cut -d . -f 2 | cut -d - -f 1` + LIBS_DL= + LIBS_SSP= + ;; + + SunOS*) PLATFORM=SUNOS + OS_MAJOR_VERSION=`echo $UNAME_RELEASE | cut -d . -f 1` + OS_MINOR_VERSION=`echo $UNAME_RELEASE | cut -d . -f 2` + LIBS_DL='-ldl' + LIBS_SSP= + ;; + + CYGWIN_NT*) PLATFORM=CYGWIN + _tmp=`echo $UNAME_SYSTEM | cut -d - -f 2` + OS_MAJOR_VERSION=`echo $_tmp | cut -d . -f 1` + OS_MINOR_VERSION=`echo $_tmp | cut -d . -f 2` + LIBS_SSP= + ;; + + *) + PLATFORM=UNKNOWN + echo "Unknown platform '$UNAME_SYSTEM#$UNAME_RELEASE'" + exit 1 +esac + +# the architecture +case "$UNAME_MACHINE" in + i*86*) ARCH=x86 + ;; + + sun4u) ARCH=sun4u + ;; + + *) ARCH=UNKNOWN + echo "Unknown architecture '$UNAME_MACHINE'" + exit 1 + +esac + +# the compiler and version +GCC_VERSION=`gcc -dumpversion` +GCC_MAJOR_VERSION=`echo $GCC_VERSION | cut -d . -f 1` +GCC_MINOR_VERSION=`echo $GCC_VERSION | cut -d . -f 2` + +case "$1" in + --platform) echo $PLATFORM + ;; + + --os-major-version) echo $OS_MAJOR_VERSION + ;; + + --os-minor-version) echo $OS_MINOR_VERSION + ;; + + --arch) echo $ARCH + ;; + + --libs-dl) echo $LIBS_DL + ;; + + --libs-ssl) echo $LIBS_SSL + ;; + + --gcc-major-version) echo $GCC_MAJOR_VERSION + ;; + + --gcc-minor-version) echo $GCC_MINOR_VERSION + ;; + + *) + cat </dev/null + -@rm port/*~ 2>/dev/null + -@rm port/*.d 2>/dev/null + +local_distclean: + -@rm cmdline.c cmdline.h diff --git a/src/daemon.c b/src/daemon.c new file mode 100644 index 0000000..a635053 --- /dev/null +++ b/src/daemon.c @@ -0,0 +1,551 @@ +#include "port/stdio.h" /* for snprintf */ +#include "port/string.h" /* for strdup */ +#include "port/limits.h" /* for PATH_MAX */ +#include "port/noreturn.h" /* for NORETURN */ + +#include "daemon.h" +#include "log.h" +#include "signals.h" +#include "pidfile.h" + +#include /* for pid_t, ssize_t, off_t */ +#include /* for getppid, geteuid, fork, + chdir, pipe, sysconf */ +#include /* for malloc, free */ +#include /* for errno */ +#include /* for umask */ +#include /* for O_RDWR */ +#include /* for getgrnam, setgid */ +#include /* for getpwnam, setuid */ + +static int exit_code_pipe[2] = { -1, -1 }; + +int daemon_parent_pipe[2] = { -1, -1 }; + +#define TERMINATE_EXIT_CODE 99 +#define TERMINATE_PARENT 98 + +struct daemon_t { + daemon_params_t params; /**< the parameters for creation */ + error_t error; /**< error of last start */ + struct pidfile_t pidfile; /**< pid/lock file of the daemon */ + struct group *groupent; /**< group entry */ + struct passwd *userent; /**< password entry */ +}; + +daemon_p daemon_new( daemon_params_t params, + error_t *error ) { + daemon_p d; + + d = (struct daemon_t *)malloc( sizeof ( struct daemon_t ) ); + if( d == NULL ) { + *error = ERR_OUT_OF_MEMORY; + return NULL; + } + + d->error = -1; + d->params = params; + + if( params.daemon_name == NULL ) { + *error = ERR_INVALID_VALUE; + return NULL; + } + + if( params.pid_filename == NULL ) { + pidfile_set_from_daemon_name( &d->pidfile, params.daemon_name ); + } else { + pidfile_set_from_filename( &d->pidfile, params.pid_filename ); + } + + *error = OK; + return d; +} + +void daemon_free( daemon_p d ) { + free( d ); + d = NULL; +} + +static error_t close_fd( int fd ) { + if( close( fd ) < 0 ) { + switch( errno ) { + case EBADF: + /* skip */ + break; + + default: + LOG( LOG_EMERG, "Error while closing file descriptor %d: %s (%d)", + fd, strerror( errno ), errno ); + return ERR_INTERNAL; + } + } + + return OK; +} + +static error_t daemon_close_standard_fds( void ) { + error_t error; + + if( ( error = close_fd( STDIN_FILENO ) ) != OK ) return error; + if( ( error = close_fd( STDOUT_FILENO ) ) != OK ) return error; + if( ( error = close_fd( STDERR_FILENO ) ) != OK ) return error; + + return OK; + +} + +static error_t daemon_close_all_fds( void ) { + long nof_files; + int i; + error_t error; + + nof_files = sysconf( _SC_OPEN_MAX ); + if( nof_files < 0 ) { + LOG( LOG_EMERG, "Unable to retrieve maximal number of files: %s (%d)", + strerror( errno ), errno ); + return ERR_INTERNAL; + } + LOG( LOG_DEBUG, "Closing all filedescriptors up to %ld", nof_files ); + + for( i = 0; i < nof_files; i++ ) { + if( ( i == STDIN_FILENO ) || + ( i == STDOUT_FILENO ) || + ( i == STDERR_FILENO ) || + ( i == daemon_signal_pipe[0] ) || + ( i == daemon_signal_pipe[1] ) || + ( i == daemon_parent_pipe[0] ) || + ( i == daemon_parent_pipe[1] ) || + ( i == exit_code_pipe[0] ) || + ( i == exit_code_pipe[1] ) ) { + continue; + } + if( ( error = close_fd( i ) ) != OK ) return error; + } + + return OK; +} + +static error_t open_null_fd( int must_fd, int flags ) { + int fd; + + fd = open( "/dev/null", flags ); + if( fd < 0 ) { + LOG( LOG_EMERG, "Unable to open fd %d as /dev/null: %s (%d)", + must_fd, strerror( errno ), errno ); + return ERR_INTERNAL; + } + if( fd != must_fd ) { + LOG( LOG_EMERG, "Something is wrong with the file descriptors (expecting %d,got %d)!", + must_fd, fd ); + return ERR_PROGRAMMING; + } + + return OK; +} + +static void atomar_write( int fd, void *data, size_t data_len ) { + ssize_t res; + +atomar_write_again: + res = write( fd, data, data_len ); + if( res < 0 ) { + if( errno == EINTR ) goto atomar_write_again; + LOG( LOG_EMERG, "Error in atomar write to fd %d: %s (%d)", + fd, strerror( errno ), errno ); + } else if( (size_t)res != data_len ) { + LOG( LOG_EMERG, "Unexpected number of octets %zd in atomar write to fd %d (expected %zd)", + res, fd, data_len ); + } +} + +static void atomar_read( int fd, void *data, size_t data_len ) { + ssize_t res; + +atomar_read_again: + res = read( fd, data, data_len ); + if( res < 0 ) { + if( errno == EINTR ) goto atomar_read_again; + LOG( LOG_EMERG, "Error in atmoar read from fd %d: %s (%d)", + fd, strerror( errno ), errno ); + } else if( (size_t)res != data_len ) { + LOG( LOG_EMERG, "Unexpected number of octets %zd in atomar read from fd %d (expected %zd)", + res, fd, data_len ); + } +} + +error_t daemon_start( daemon_p d ) { + pid_t pid; + mode_t mode; + error_t error; + int exit_code; + + /* Our parent is already the init process (which has pid 1 by + * convention), we are already a daemon. This is a programming + * error.. + */ + if( getppid( ) == 1 ) { + LOG( LOG_EMERG, "Already running as daemon!" ); + return( d->error = ERR_PROGRAMMING ); + } + + /* Are we starting as root? If not, bail out, we need root + * permissions because we have to listen to ports below 1024 + * or we must open logfiles/pidfiles with proper permissions. + * Also we must change to the proper running user/group and + * we must be root (uid 0 by convention) for that. + */ + if( geteuid( ) != 0 ) { + LOG( LOG_EMERG, "Unable to start daemon as not root user!" ); + return( d->error = ERR_INVALID_STATE ); + } + + /* We fork so SIGCHLD signals get to the parent and grandfather. + * We don't want that and we are installing empty signal handlers. + */ +#if defined( SIGCHLD ) && defined( SIGCLD ) +#if SIGCLD != SIGCHLD + signal_install_empty( SIGCLD, 0 ); + signal_install_empty( SIGCHLD, 0 ); +#else +#if defined( SIGCHLD ) + signal_install_empty( SIGCHLD, 0 ); +#endif +#if defined( SIGCLD ) + signal_install_empty( SIGCLD, 0 ); +#endif +#endif +#endif + + /* first pipe communicates from parent of daemon and daemon + * itself back to the grand-parent (exit codes). + */ + if( pipe( exit_code_pipe ) < 0 ) { + LOG( LOG_EMERG, "Unable to create exit code pipe: %s (%d)", + strerror( errno ), errno ); + return ( d->error = ERR_INTERNAL ); + } + LOG( LOG_DEBUG, "Created exit code pipe (%d,%d)", exit_code_pipe[0], exit_code_pipe[1] ); + + /* first fork: make sure we are no longer process group leader. + * So we can get our own process group leader by calling setsid + */ + switch( ( pid = fork( ) ) ) { + case -1: + /* error */ + LOG( LOG_EMERG, "Unable to fork the first time: %s", strerror( errno ) ); + return ( d->error = ERR_INTERNAL ); + + case 0: + /* the child becomes the daemon */ + LOG( LOG_DEBUG, "First fork reached" ); + break; + + default: + /* the parent waits till it gets feedback from + * the child (error pipe) + */ + /* TODO: wait for some time for correct exit + * code from the error pipe + */ + LOG( LOG_DEBUG, "Parent after first fork: child is %d", pid ); + return ( d->error = TERMINATE_EXIT_CODE ); + } + + /* second pipe communicates from the daemon back to its parent + * (for termination and cleanup which can only be done as root) + */ + if( pipe( daemon_parent_pipe ) < 0 ) { + LOG( LOG_EMERG, "Unable to create parent pipe: %s (%d)", + strerror( errno ), errno ); + return ( d->error = ERR_INTERNAL ); + } + LOG( LOG_DEBUG, "Created parent pipe (%d,%d)", daemon_parent_pipe[0], daemon_parent_pipe[1] ); + + /* Put the first child in it's own process group and finally detach it + * from its controlling terminal. This ensure we don't get funny + * signals which could kill the daemon. + */ + if( setsid( ) < 0 ) { + /* should actually never fail */ + LOG( LOG_EMERG, "Starting new process group session for the parent of the daemon failed: %s", strerror( errno ) ); + return ERR_INTERNAL; + } + + /* We fork so SIGCHLD signals get to the parent and grandfather. + * We don't want that and we are installing empty signal handlers. + */ +#if defined( SIGCHLD ) && defined( SIGCLD ) +#if SIGCLD != SIGCHLD + signal_install_empty( SIGCLD, 0 ); + signal_install_empty( SIGCHLD, 0 ); +#else +#if defined( SIGCHLD ) + signal_install_empty( SIGCHLD, 0 ); +#endif +#if defined( SIGCLD ) + signal_install_empty( SIGCLD, 0 ); +#endif +#endif +#endif + + /* Now that the parent is process group leader, fork again. This + * way the final child can not inherit a controlling terminal + */ + switch( ( pid = fork( ) ) ) { + case -1: + /* error */ + LOG( LOG_EMERG, "Unable to fork the second time: %s", strerror( errno ) ); + return ( d->error = ERR_INTERNAL ); + + case 0: + /* the child becomes the daemon */ + LOG( LOG_DEBUG, "Second fork reached" ); + break; + + default: + (void)signal_install_handlers_parent( ); + LOG( LOG_DEBUG, "Parent after second fork: child (and daemon) is %d", pid ); + return ( d->error = TERMINATE_PARENT ); + } + + /* Now install signal handlers */ + if( ( error = signal_initialize( ) ) != OK ) + return ( d->error = error ); + if( ( error = signal_install_handlers_daemon( ) ) != OK ) + return ( d->error = error ); + + /* Put the daemon in it's own process group and finally detach it + * from its controlling terminal. This ensure we don't get funny + * signals which could kill the daemon. + */ + if( setsid( ) < 0 ) { + /* should actually never fail */ + LOG( LOG_EMERG, "Starting new process group for daemon session failed: %s", strerror( errno ) ); + return ERR_INTERNAL; + } + + /* Change to the root directory for several reasons: + * - all but the root directory we may want to unmount without + * stopping the daemon + * - we don't have write permissions there so a core dump + * would not result in DoSA + * - non-priviledged users can't do anything in the root + * directory (especially they can't write files there) + */ + if( chdir( "/" ) < 0 ) { + LOG( LOG_EMERG, "Changing to root diretory failed: %s", strerror( errno ) ); + return ERR_INTERNAL; + } + LOG( LOG_DEBUG, "Changed to root directory /" ); + + /* Change the umask to 0133 temporarily so we don't have to + * chmod the logfiles, pidfiles later. + * + * Create pid files with permissions 0644 (rw-r--r--) + */ + mode = umask( 0133 ); + LOG( LOG_DEBUG, "Switched umask from %04o to %04o", mode, 0133 ); + + /* check if another daemon is already running, if yes, bail out */ + if( is_daemon_running( &d->pidfile, &pid, &error ) || ( error != OK ) ) { + if( error == OK ) { + LOG( LOG_EMERG, "Another daemon is already running with pid '%d', can't start!", pid ); + } + return ERR_INTERNAL; + } + + /* we loose logfile and syslog file descriptors anyway, so close + * them here */ + closelogtosyslog( ); + closelogtofile( ); + closelogtostderr( ); + + /* close all filedescriptors */ + if( daemon_close_all_fds( ) != OK ) + return ERR_INTERNAL; + + /* reopen the logs to the logfile and syslog (not stderr) */ + reopenlogtosyslog( ); + reopenlogtofile( ); + + /* create and lock the pidfile now, write pid of daemon into it */ + if( pidfile_create( &d->pidfile ) != OK ) { + return ERR_INTERNAL; + } + + /* Install the final permissions for new files. The reason is + * simple: We don't want to create files in a defective part + * of the daemon, especially third party code. We don't know + * what kind of plugins we gonna integrate.. + * + * Make sure that no files can be written except the ones belonging + * to us (because now we are no longer root) and that no files can + * be created with executable permission (code injection!) + * + * New files always get the permission 640 (rw-r-----) + */ + mode = umask( 0137 ); + LOG( LOG_DEBUG, "Switched umask from %04o to %04o", mode, 0137 ); + + /* drop permissions to a non-priviledged user now */ + if( ( d->params.group_name != NULL ) && ( d->params.user_name != NULL ) ) { + errno = 0; + d->groupent = getgrnam( d->params.group_name ); + if( d->groupent == NULL ) { + if( errno == 0 ) { + LOG( LOG_EMERG, "No group '%s' found", d->params.group_name ); + } else { + LOG( LOG_EMERG, "Unable to retrieve group information for group '%s': %s (%d)", + d->params.group_name, strerror( errno ), errno ); + } + return ERR_INTERNAL; + } + + errno = 0; + d->userent = getpwnam( d->params.user_name ); + if( d->userent == NULL ) { + if( errno == 0 ) { + LOG( LOG_EMERG, "No user '%s' found", d->params.user_name ); + } else { + LOG( LOG_EMERG, "Unable to retrieve user information for user '%s': %s (%d)", + d->params.user_name, strerror( errno ), errno ); + } + return ERR_INTERNAL; + } + + if( setgid( d->userent->pw_gid ) < 0 ) { + LOG( LOG_EMERG, "Setting unprivileged group failed: %s (%d)", + strerror( errno ), errno ); + return ERR_INTERNAL; + } + + if( setuid( d->userent->pw_uid ) < 0 ) { + LOG( LOG_EMERG, "Setting unprivileged user failed: %s (%d)", + strerror( errno ), errno ); + return ERR_INTERNAL; + } + + /* TODO: setsid and setting all groups of the user */ + /* TODO: also check the permissions of the home dir + * of the unpriviledged user */ + + LOG( LOG_DEBUG, "Switched to user '%s' (%d) and group '%s' (%d)", + d->params.user_name, d->userent->pw_uid, + d->params.group_name, d->userent->pw_gid ); + } + + /* assign stdin/stdout/stderr again to something harmless + * (/dev/null). Do this as late as possible, so somebody + * starting the daemon by hand still gets error messages + * on stderr. + */ + if( ( error = daemon_close_standard_fds( ) ) != OK ) return error; + if( ( error = open_null_fd( STDIN_FILENO, O_RDONLY ) ) != OK ) return error; + if( ( error = open_null_fd( STDOUT_FILENO, O_WRONLY ) ) != OK ) return error; + if( ( error = open_null_fd( STDERR_FILENO, O_WRONLY ) ) != OK ) return error; + + /* signal the grand-parent process, that he can return 0 to + * the calling shell/script + */ + exit_code = EXIT_SUCCESS; + atomar_write( exit_code_pipe[1], &exit_code, sizeof( int ) ); + + return ( d->error = OK ); +} + +NORETURN void daemon_exit( daemon_p d ) { + int exit_code; + int pid_file_fd; + + LOG( LOG_DEBUG, "daemon_exit called with error %d", d->error ); + + switch( d->error ) { + case TERMINATE_EXIT_CODE: + /* wait here for exit code in exit_code_pipe + * so we can return the correct exit code to + * the calling script or shell + */ + LOG( LOG_DEBUG, "Waiting on exit_code pipe for exit code" ); + + atomar_read( exit_code_pipe[0], &exit_code, sizeof( int ) ); + + LOG( LOG_DEBUG, "Terminating grand-parent of daemon with code %d (PID: %lu)", + exit_code, getpid( ) ); + + signal_terminate( ); + + /* Do not close file descriptors of the child! + * We are the father or grand-father process + */ + _exit( exit_code ); + + case TERMINATE_PARENT: + /* TODO: handle houskeeping here which needs + * root rights (log rotation?) + */ + + /* wait for termination signal */ + LOG( LOG_DEBUG, "Waiting on parent pipe for termination signal" ); + + atomar_read( daemon_parent_pipe[0], &pid_file_fd, sizeof( int ) ); + LOG( LOG_DEBUG, "Parent got termination (pidfile fd: %d).. cleaning up now (PID: %lu)", + pid_file_fd, getpid( ) ); + + /* we need root permissions for that! */ + d->pidfile.fd = pid_file_fd; + (void)pidfile_remove( &d->pidfile ); + + signal_terminate( ); + + LOG( LOG_DEBUG, "Terminating parent of daemon pid file fd %d (PID %lu)", + pid_file_fd, getpid( ) ); + + /* exit code is irrelevant here.. */ + exit( EXIT_SUCCESS ); + + case OK: + /* close and unlock the pidfile here */ + (void)pidfile_release( &d->pidfile ); + + /* This is the daemon terminating, signal the + * parent that we are terminating + */ + atomar_write( daemon_parent_pipe[1], &d->pidfile.fd, sizeof( int ) ); + + signal_terminate( ); + + LOG( LOG_DEBUG, "Terminating daemon (PID: %lu)", getpid( ) ); + + /* exit code is irrelevant here */ + _exit( EXIT_SUCCESS ); + + default: + /* no exit_code_pipe exists yet, we are before the + * first fork, so terminate with proper exit code + */ + if( exit_code_pipe[1] == -1 ) { + exit( EXIT_FAILURE ); + } + + /* This is an error case, communicate exit code back + * to grand-parent + */ + exit_code = EXIT_FAILURE; + atomar_write( exit_code_pipe[1], &exit_code, sizeof( int ) ); + + /* also terminate the parent of the daemon */ + atomar_write( daemon_parent_pipe[1], &d->pidfile.fd, sizeof( int ) ); + + signal_terminate( ); + + LOG( LOG_DEBUG, "Terminating daemon (PID: %lu)", getpid( ) ); + + /* exit code is irrelevant here */ + _exit( EXIT_SUCCESS ); + } + + /* silence up some versions of the GCC compiler ("noreturn function returns"), + * because they errornously tread _exit as a return, which is clearly wrong + * (experienced with gcc 3.3.5 on OpenBSD 4.3) */ + exit( EXIT_SUCCESS ); +} diff --git a/src/daemon.ggo b/src/daemon.ggo new file mode 100644 index 0000000..08c83cb --- /dev/null +++ b/src/daemon.ggo @@ -0,0 +1,70 @@ +package "testd" +version "0.0.1" +usage "testd [options]" +description "tests daemonizing on Unix without any functionality\n" + +section "Main Options" + option "foreground" f + "Do not daemonize, run in foreground, write to stdout/stderr" + optional + + option "config-file" c + "The location of the configuration file of the daemon" + string typestr="file" + optional + + option "debug" d + "Increase debug level (option can be given many times)" + optional multiple hidden + + option "test" t + "Test the configuration without running the daemon" + optional + +section "Query Options" + option "list-modules" - + "List loaded modules" + optional hidden + +section "Daemon Options" + option "user" u + "User the daemon should run as" + string typestr="user" + optional hidden + + option "group" g + "Group the daemon should run as" + string typestr="group" + optional hidden + + option "pidfile" - + "Location of the pidfile (explicitly, normaly the system knows where to store them)" + string typestr="file" + optional hidden + + option "syslog-facility" - + "System log facility to use for logging to system log" + optional hidden + typestr="facility" + values="KERN","USER","MAIL","DAEMON","AUTH","SYSLOG","LPR","NEWS","UUCP","CRON","AUTHPRIV","FTP" + default="DAEMON" + + option "syslog-level" - + "Level for logging to system log" + optional hidden + typestr="level" + values="EMERG","ALERT","CRIT","ERR","WARNING","NOTICE","INFO","DEBUG","DEBUG1","DEBUG2","DEBUG3","DEBUG4","DEBUG5" + default="NOTICE" + + option "logfile" - + "Name of a log file where to log to" + string typestr="file" + optional hidden + + option "logfile-level" - + "Level for logging to the logfile" + optional hidden + typestr="level" + values="EMERG","ALERT","CRIT","ERR","WARNING","NOTICE","INFO","DEBUG","DEBUG1","DEBUG2","DEBUG3","DEBUG4","DEBUG5" + default="NOTICE" + diff --git a/src/daemon.h b/src/daemon.h new file mode 100644 index 0000000..d060cb8 --- /dev/null +++ b/src/daemon.h @@ -0,0 +1,36 @@ +#ifndef __DAEMON_H +#define __DAEMON_H + +#include "errors.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern int daemon_parent_pipe[2]; +extern int daemon_signal_pipe[2]; + +typedef struct daemon_t *daemon_p; + +typedef struct daemon_params_t { + const char *daemon_name; /**< name of the daemon (used for setproctitle, + default name of the pidfile, logfile) */ + const char *pid_filename; /**< name of the pid/lock file */ + const char *group_name; /**< group of the effective user */ + const char *user_name; /**< name of the effective user */ +} daemon_params_t; + +daemon_p daemon_new( daemon_params_t params, + error_t *error ); + +void daemon_free( daemon_p demon ); + +error_t daemon_start( daemon_p demon ); + +void daemon_exit( daemon_p demon ); + +#ifdef __cplusplus +} +#endif + +#endif /* ifndef __DAEMON_H */ diff --git a/src/errors.h b/src/errors.h new file mode 100644 index 0000000..add5b3c --- /dev/null +++ b/src/errors.h @@ -0,0 +1,25 @@ +#ifndef __ERRORS_H +#define __ERRORS_H + +#include "port/sys.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef signed int error_t; + +#define OK 0 /* no error, everything is fine */ +#define ERR_OUT_OF_MEMORY -1 /* out of memory in malloc */ +#define ERR_INVALID_STATE -2 /* the object is called in an illegal moment */ +#define ERR_INVALID_VALUE -3 /* invalid parameter to a function */ +#define ERR_INTERNAL -4 /* internal error of the object, usualy rare */ +#define ERR_PROGRAMMING -5 /* programming mistake, should not happen */ +#define ERR_NOT_IMPLEMENTED -6 /* not implemented yet */ +#define ERR_TIMEOUT -7 /* timeout */ + +#ifdef __cplusplus +} +#endif + +#endif /* ifndef __ERRORS_H */ diff --git a/src/log.c b/src/log.c new file mode 100644 index 0000000..48b6210 --- /dev/null +++ b/src/log.c @@ -0,0 +1,160 @@ +#include "log.h" + +#include "port/stdio.h" /* for vsnprintf */ +#include "port/string.h" /* for strcmp */ + +#include /* for variable arguments */ + +const char *log_syslog_facility_to_str( int facility ) { + switch( facility ) { + case LOG_KERN: return "KERN"; + case LOG_USER: return "USER"; + case LOG_MAIL: return "MAIL"; + case LOG_DAEMON: return "DAEMON"; + case LOG_AUTH: return "AUTH"; + case LOG_SYSLOG: return "SYSLOG"; + case LOG_LPR: return "LPR"; + case LOG_NEWS: return "NEWS"; + case LOG_UUCP: return "UUCP"; + case LOG_CRON: return "CRON"; +#if defined LOG_AUTHPRIV + case LOG_AUTHPRIV: return "AUTHPRIV"; +#endif +#if defined LOG_FTP + case LOG_FTP: return "FTP"; +#endif + default: return ""; + } +} + +int log_str_to_level( const char *level ) { + if( strcmp( level, "EMERG" ) == 0 ) return LOG_EMERG; + if( strcmp( level, "ALERT" ) == 0 ) return LOG_ALERT; + if( strcmp( level, "CRIT" ) == 0 ) return LOG_CRIT; + if( strcmp( level, "ERR" ) == 0 ) return LOG_ERR; + if( strcmp( level, "WARNING" ) == 0 ) return LOG_WARNING; + if( strcmp( level, "NOTICE" ) == 0 ) return LOG_NOTICE; + if( strcmp( level, "INFO" ) == 0 ) return LOG_INFO; + if( strcmp( level, "DEBUG" ) == 0 ) return LOG_DEBUG; + if( strcmp( level, "DEBUG1" ) == 0 ) return LOG_DEBUG1; + if( strcmp( level, "DEBUG2" ) == 0 ) return LOG_DEBUG2; + if( strcmp( level, "DEBUG3" ) == 0 ) return LOG_DEBUG3; + if( strcmp( level, "DEBUG4" ) == 0 ) return LOG_DEBUG4; + if( strcmp( level, "DEBUG5" ) == 0 ) return LOG_DEBUG5; + + return LOG_NOTICE; +} + +static const char *log_logfile_filename; +static FILE *log_file = NULL; +static int log_logfile_level = -1; +static int log_stderr_level = -1; +static const char *syslog_ident = NULL; +static int syslog_facility; +static int syslog_level; + +void openlogtofile( const char *filename, int level ) { + if( log_file != NULL ) { + fclose( log_file ); + } + log_file = fopen( filename, "a" ); + log_logfile_filename = filename; + log_logfile_level = level; +} + +void openlogtosyslog( const char *ident, int facility, int level ) { + openlog( ident, LOG_CONS | LOG_NDELAY, facility ); + setlogmask( LOG_UPTO( level ) ); + syslog_ident = ident; + syslog_facility = facility; + syslog_level = level; +} + +void openlogtostderr( int level ) { + log_stderr_level = level; +} + +void closelogtofile( void ) { + if( log_file != NULL ) { + fclose( log_file ); + log_file = NULL; + } +} + +void closelogtosyslog( void ) { + closelog( ); +} + +void closelogtostderr( void ) { + /* do nothing here */ +} + +void reopenlogtofile( void ) { + if( log_file != NULL ) { + fclose( log_file ); + } + log_file = fopen( log_logfile_filename, "a" ); +} + +void reopenlogtosyslog( void ) { + openlog( syslog_ident, LOG_CONS | LOG_NDELAY, syslog_facility ); + setlogmask( LOG_UPTO( syslog_level ) ); +} + +void LOG( int level, const char *format, ... ) { + va_list ap; + char s[1024]; + va_start( ap, format ); + vsnprintf( s, 1024, format, ap ); + va_end( ap ); + + if( level <= log_stderr_level ) + fprintf( stderr, "%s: %s\n", log_level_to_str( level ), s ); + + if( level <= log_logfile_level ) { + fprintf( log_file, "%s: %s\n", log_level_to_str( level ), s ); + fflush( log_file ); + } + + syslog( level, "%s", s ); +} diff --git a/src/log.h b/src/log.h new file mode 100644 index 0000000..f6c0534 --- /dev/null +++ b/src/log.h @@ -0,0 +1,60 @@ +#ifndef __LOG_H +#define __LOG_H + +#include "port/sys.h" + +#include /* for syslog, closelog and levels */ + +#ifdef __cplusplus +extern "C" { +#endif + +#if 0 +/* defined in syslog.h */ +#define LOG_EMERG 0 /* system is unusable */ +#define LOG_ALERT 1 /* action must be taken immediately */ +#define LOG_CRIT 2 /* critical conditions */ +#define LOG_ERR 3 /* error conditions */ +#define LOG_WARNING 4 /* warning conditions */ +#define LOG_NOTICE 5 /* normal but significant condition */ +#define LOG_INFO 6 /* informational */ +#define LOG_DEBUG 7 /* debug-level messages */ +#endif + +#define LOG_DEBUG1 ( LOG_DEBUG + 1 ) +#define LOG_DEBUG2 ( LOG_DEBUG + 2 ) +#define LOG_DEBUG3 ( LOG_DEBUG + 3 ) +#define LOG_DEBUG4 ( LOG_DEBUG + 4 ) +#define LOG_DEBUG5 ( LOG_DEBUG + 5 ) + +const char *log_syslog_facility_to_str( int facility ); + +int log_str_to_syslog_facility( const char *facility ); + +const char *log_level_to_str( int level ); + +int log_str_to_level( const char *level ); + +void openlogtofile( const char *filename, int level ); + +void openlogtosyslog( const char *ident, int facility, int level ); + +void openlogtostderr( int level ); + +void closelogtofile( void ); + +void closelogtosyslog( void ); + +void closelogtostderr( void ); + +void reopenlogtofile( void ); + +void reopenlogtosyslog( void ); + +void LOG( int level, const char *format, ... ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/pidfile.c b/src/pidfile.c new file mode 100644 index 0000000..07f7521 --- /dev/null +++ b/src/pidfile.c @@ -0,0 +1,245 @@ +#include "port/stdio.h" /* for snprintf */ +#include "port/string.h" /* for strdup, strcspn */ +#include "port/stdbool.h" /* for bool */ +#include "port/unistd.h" /* for getpid, open, write, read, + * unlink, lockf */ + +#include "pidfile.h" +#include "log.h" +#include "errors.h" + +#include /* for strtol */ +#include /* for errno */ +#include /* for pid_t, ssize_t, off_t */ +#include /* for umask */ +#include /* for O_RDWR */ +#include /* for kill */ + +/* abstraction of the pid file handling in order to unclutter the + * daemon start and stop function + */ + +#define VAR_RUN_DIR "/var/run" + +void pidfile_init( struct pidfile_t *pidfile ) { + pidfile->filename[0] = '\0'; + pidfile->fd = -1; + pidfile->locked = false; + pidfile->running = false; +} + +void pidfile_set_from_daemon_name( struct pidfile_t *pidfile, const char *daemon ) { + pidfile_init( pidfile ); + snprintf( pidfile->filename, PATH_MAX, "%s/%s.pid", VAR_RUN_DIR, daemon ); +} + +void pidfile_set_from_filename( struct pidfile_t *pidfile, const char *filename ) { + pidfile_init( pidfile ); + /* make sure the filename is shorter than the POSIX.1 length */ + snprintf( pidfile->filename, PATH_MAX, "%s", filename ); +} + +bool is_daemon_running( struct pidfile_t *pidfile, pid_t *pid, error_t *error ) { + int res; + ssize_t bytes_read; + char buf[256]; + char *end_ptr; + + /* assume daemon is not running */ + pidfile->running = false; + + /* open pidfile with correct permissions */ + pidfile->fd = open( pidfile->filename, O_RDWR, 0644 ); + if( pidfile->fd < 0 ) { + if( errno == ENOENT ) { + /* this is good, pid file doesn't exist at all */ + LOG( LOG_DEBUG, "No pidfile '%s' found, daemon is not running", pidfile->filename ); + (void)close( pidfile->fd ); + *error = OK; + return pidfile->running; + } else { + LOG( LOG_EMERG, "Unable to open pidfile '%s' for reading: %s", pidfile->filename, strerror( errno ) ); + *error = ERR_INTERNAL; + return pidfile->running; + } + } + + /* try to lock the pid file (non-blocking) */ + res = lockf( pidfile->fd, F_TLOCK, (off_t)0 ); + if( res < 0 ) { + if( errno == EAGAIN ) { + /* another process locks the file already */ + LOG( LOG_DEBUG, "Another process locks the pidfile, daemon already running" ); + *error = OK; + pidfile->locked = true; + pidfile->running = true; + } else { + LOG( LOG_EMERG, "Unable to lock pidfile '%s': %s", pidfile->filename, strerror( errno ) ); + (void)close( pidfile->fd ); + *error = ERR_INTERNAL; + pidfile->running = false; + return pidfile->running; + } + } else { + pidfile->locked = false; + } + + /* try to read the pid from the file */ + bytes_read = read( pidfile->fd, buf, sizeof( buf ) - 1 ); + if( bytes_read < 0 ) { + LOG( LOG_EMERG, "Unable to read pid from pidfile '%s': %s", pidfile->filename, strerror( errno ) ); + (void)close( pidfile->fd ); + *error = ERR_INTERNAL; + return pidfile->running; + } + + /* parse the file and see if we have a valid pid on the first line */ + buf[bytes_read] = 0; + buf[strcspn( buf, "\n" )] = 0; + + errno = 0; + *pid = (pid_t)strtol( buf, &end_ptr, 10 ); + if( ( errno != 0 ) /* ERANGE or valid base */ || + ( end_ptr == NULL ) /* pre-condition for check for '\0'! */ || + ( end_ptr - buf < 1 ) /* too short */ || + ( *pid < 2 ) /* too small value */ ) { + LOG( LOG_EMERG, "pidfile '%s' contains invalid data, can't read PID from it!", pidfile->filename ); + (void)close( pidfile->fd ); + *error = ERR_INTERNAL; + return pidfile->running; + } + LOG( LOG_DEBUG, "Found PID '%lu' in pidfile", *pid ); + + /* the pid is valid, but is there a process with the pid actually running? + * (this handles the case of kill -9!) + */ + res = kill( *pid, 0 ); + if( res < 0 ) { + if( errno == ESRCH ) { + /* this is fine, process doesn't exist with this PID */ + LOG( LOG_EMERG, "Found PID '%lu' in pidfile '%s', but no such process is running. Check and manually delete the pidfile!", *pid, pidfile->filename ); + (void)close( pidfile->fd ); + *error = ERR_INTERNAL; + return pidfile->running; + } else { + LOG( LOG_EMERG, "Can't check if processor with PID '%lu' is alive: %s", *pid, strerror( errno ) ); + (void)close( pidfile->fd ); + *error = ERR_INTERNAL; + return pidfile->running; + } + } + LOG( LOG_DEBUG, "A process with PID '%lu' is already running", *pid ); + + /* process successfuly signaled, so a process with this PID exists + * (worst case, we assume, it's the daemon) + */ + (void)close( pidfile->fd ); + *error = OK; + return pidfile->running; +} + +error_t pidfile_create( struct pidfile_t *pidfile ) { + int res; + char pid_string[20]; + ssize_t bytes_writen; + + /* create or open pid file with correct permissions */ + pidfile->fd = open( pidfile->filename, O_CREAT | O_WRONLY | O_EXCL, 0644 ); + if( pidfile->fd < 0 ) { + LOG( LOG_EMERG, "Unable to open pidfile '%s' for writing: %s", pidfile->filename, strerror( errno ) ); + return ERR_INTERNAL; + } + + /* Try to lock the pid file (non-blocking) */ + res = lockf( pidfile->fd, F_TLOCK, (off_t)0 ); + if( res < 0 ) { + if( errno == EAGAIN ) { + /* another process locks the file already, this should not happen, maybe a + * race between to daemons started in parallel? + */ + LOG( LOG_EMERG, "Unable to lock pidfile '%s' after creation, daemon started in parallel?", pidfile->filename ); + (void)close( pidfile->fd ); + (void)unlink( pidfile->filename ); + return ERR_INVALID_STATE; + } else { + LOG( LOG_EMERG, "Unable to lock pidfile '%s' after creation: %s", pidfile->filename, strerror( errno ) ); + (void)close( pidfile->fd ); + (void)unlink( pidfile->filename ); + return ERR_INTERNAL; + } + } + + /* Truncate the contents of the file, so we don't get funny values + * in the pid file + */ + if( ftruncate( pidfile->fd, (off_t)0 ) < 0 ) { + LOG( LOG_EMERG, "Unable to truncate the pidfile '%s' before writing to it", pidfile->filename, strerror( errno ) ); + (void)close( pidfile->fd ); + (void)unlink( pidfile->filename ); + return ERR_INTERNAL; + } + + /* We remember the pid in the file for init scripts which rely on the pid + * to be stored here. + */ + snprintf( pid_string, 20, "%lu\n", (unsigned long)getpid( ) ); + bytes_writen = write( pidfile->fd, pid_string, strlen( pid_string ) ); + if( bytes_writen < 0 ) { + LOG( LOG_EMERG, "Unable to write PID into the pidfile '%s': %s", pidfile->filename, strerror( errno ) ); + (void)close( pidfile->fd ); + (void)unlink( pidfile->filename ); + return ERR_INTERNAL; + } else if( bytes_writen != (ssize_t)strlen( pid_string ) ) { + /* non-atomic write on files with so little data, strange, should never happen! */ + LOG( LOG_EMERG, "Non-atomic write failed when storing the PID into the pidfile '%s'", pidfile->filename ); + } + LOG( LOG_DEBUG, "Stored '%lu' into the pidfile '%s' and locked it", (unsigned long)getpid( ), pidfile->filename ); + + pidfile->locked = true; + + return OK; +} + +error_t pidfile_release( struct pidfile_t *pidfile ) { + error_t error = OK; + + LOG( LOG_DEBUG, "Releasing (unlocking/closing) pidfile '%s' (fd: %d, locked: %d)", + pidfile->filename, pidfile->fd, pidfile->locked ); + + if( pidfile->locked ) { + if( lockf( pidfile->fd, F_ULOCK, (off_t)0 ) < 0 ) { + LOG( LOG_ALERT, "Unable to unlock the pidfile '%s': %s (%d)", + pidfile->filename, strerror( errno ), errno ); + error = ERR_INTERNAL; + } + } + + if( pidfile->fd >= 0 ) { + if( close( pidfile->fd ) < 0 ) { + LOG( LOG_ALERT, "Unable to close the pidfile '%s': %s (%d)", + pidfile->filename, strerror( errno ), errno ); + error = ERR_INTERNAL; + } + pidfile->fd = -1; + } + + return error; +} + +error_t pidfile_remove( struct pidfile_t *pidfile ) { + error_t error = OK; + + LOG( LOG_DEBUG, "Removing pidfile '%s' (fd: %d, locked: %d, running: %d)", + pidfile->filename, pidfile->fd, pidfile->locked, pidfile->running ); + + + if( !pidfile->running && pidfile->fd != -1 ) { + if( unlink( pidfile->filename ) < 0 ) { + LOG( LOG_ALERT, "Unable to remove the pidfile '%s': %s (%d)", + pidfile->filename, strerror( errno ), errno ); + error = ERR_INTERNAL; + } + } + + return error; +} diff --git a/src/pidfile.h b/src/pidfile.h new file mode 100644 index 0000000..25ddd48 --- /dev/null +++ b/src/pidfile.h @@ -0,0 +1,32 @@ +#ifndef __PIDFILE_H +#define __PIDFILE_H + +#include "port/limits.h" /* for PATH_MAX */ +#include "port/stdbool.h" /* for bool */ + +#include "errors.h" + +#include /* for pid_t */ + +struct pidfile_t { + char filename[PATH_MAX]; /**< the filename */ + int fd; /**< file descriptor */ + bool locked; /**< is the pidfile locked? */ + bool running; /**< is another process locking too? */ +}; + +void pidfile_init( struct pidfile_t *pidfile ); + +void pidfile_set_from_daemon_name( struct pidfile_t *pidfile, const char *daemon ); + +void pidfile_set_from_filename( struct pidfile_t *pidfile, const char *filename ); + +bool is_daemon_running( struct pidfile_t *pidfile, pid_t *pid, error_t *error ); + +error_t pidfile_create( struct pidfile_t *pidfile ); + +error_t pidfile_release( struct pidfile_t *pidfile ); + +error_t pidfile_remove( struct pidfile_t *pidfile ); + +#endif /* ifndef __PIDFILE_H */ diff --git a/src/port/limits.h b/src/port/limits.h new file mode 100644 index 0000000..a9e13a7 --- /dev/null +++ b/src/port/limits.h @@ -0,0 +1,8 @@ +#ifndef __LIMITS_H +#define __LIMITS_H + +#include "sys.h" + +#include + +#endif /* ifndef __LIMITS_H */ diff --git a/src/port/lockf.c b/src/port/lockf.c new file mode 100644 index 0000000..09c613e --- /dev/null +++ b/src/port/lockf.c @@ -0,0 +1,62 @@ +#include "lockf.h" + +#include "string.h" /* for memset */ +#include "unistd.h" /* for getpid */ + +#if !defined HAVE_LOCKF + +#include /* for off_t */ +#include /* for errno */ + +int lockf( int fd, int cmd, off_t len ) { + struct flock fl; + + memset( (char *)&fl, 0, sizeof( fl ) ); + /* lockf is always relative to the current file position. */ + fl.l_whence = SEEK_CUR; + fl.l_start = 0; + fl.l_len = len; + + errno = 0; + + switch( cmd ) { + case F_TEST: + fl.l_type = F_RDLCK; + if( fcntl( fd, F_GETLK, &fl ) < 0 ) { + return -1; + } + if( fl.l_type == F_UNLCK || + fl.l_pid == getpid( ) ) { + return 0; + } + errno = EACCES; + return -1; + + case F_ULOCK: + fl.l_type = F_UNLCK; + cmd = F_SETLK; + break; + + case F_LOCK: + fl.l_type = F_WRLCK; + cmd = F_SETLKW; + break; + + case F_TLOCK: + fl.l_type = F_WRLCK; + cmd = F_SETLK; + break; + + default: + errno = EINVAL; + return -1; + } + + return fcntl( fd, cmd, &fl ); +} + +#else + +extern int dummy; /* prevent ISO C forbids an empty source file' warning */ + +#endif /* !defined HAVE_LOCKF */ diff --git a/src/port/lockf.h b/src/port/lockf.h new file mode 100644 index 0000000..5ec99b3 --- /dev/null +++ b/src/port/lockf.h @@ -0,0 +1,20 @@ +#ifndef __LOCKF_H +#define __LOCKF_H + +#include "sys.h" + +#if !defined HAVE_LOCKF + +/* Drop-in replacement for the simplified lockf interface (POSIX) */ +#include /* for fcntl and locking flags */ + +#define F_ULOCK 0 /* Unlock a previously locked region */ +#define F_LOCK 1 /* Lock exclusively */ +#define F_TLOCK 2 /* Test and lock exclusively */ +#define F_TEST 3 /* Test for a locked region */ + +extern int lockf( int fd, int cmd, off_t len ); + +#endif /* !defined HAVE_LOCKF */ + +#endif /* ifndef __LOCKF_H */ diff --git a/src/port/noreturn.h b/src/port/noreturn.h new file mode 100644 index 0000000..afb4d1a --- /dev/null +++ b/src/port/noreturn.h @@ -0,0 +1,10 @@ +#ifndef __NORETURN_H +#define __NORETURN_H + +#if defined(__GNUC__) +#define NORETURN __attribute__((noreturn)) +#else +#define NORETURN +#endif /* defined(__GNUC__) */ + +#endif /* ifndef __NORETURN_H */ diff --git a/src/port/snprintf.c b/src/port/snprintf.c new file mode 100644 index 0000000..65d9602 --- /dev/null +++ b/src/port/snprintf.c @@ -0,0 +1,2109 @@ +/* $Id: snprintf.c,v 1.9 2008/01/20 14:02:00 holger Exp $ */ + +/* + * Copyright (c) 1995 Patrick Powell. + * + * This code is based on code written by Patrick Powell . + * It may be used for any purpose as long as this notice remains intact on all + * source code distributions. + */ + +/* + * Copyright (c) 2008 Holger Weiss. + * + * This version of the code is maintained by Holger Weiss . + * My changes to the code may freely be used, modified and/or redistributed for + * any purpose. It would be nice if additions and fixes to this file (including + * trivial code cleanups) would be sent back in order to let me include them in + * the version available at . + * However, this is not a requirement for using or redistributing (possibly + * modified) versions of this file, nor is leaving this notice intact mandatory. + */ + +/* + * History + * + * 2008-01-20 Holger Weiss for C99-snprintf 1.1: + * + * Fixed the detection of infinite floating point values on IRIX (and + * possibly other systems) and applied another few minor cleanups. + * + * 2008-01-06 Holger Weiss for C99-snprintf 1.0: + * + * Added a lot of new features, fixed many bugs, and incorporated various + * improvements done by Andrew Tridgell , Russ Allbery + * , Hrvoje Niksic , Damien Miller + * , and others for the Samba, INN, Wget, and OpenSSH + * projects. The additions include: support the "e", "E", "g", "G", and + * "F" conversion specifiers (and use conversion style "f" or "F" for the + * still unsupported "a" and "A" specifiers); support the "hh", "ll", "j", + * "t", and "z" length modifiers; support the "#" flag and the (non-C99) + * "'" flag; use localeconv(3) (if available) to get both the current + * locale's decimal point character and the separator between groups of + * digits; fix the handling of various corner cases of field width and + * precision specifications; fix various floating point conversion bugs; + * handle infinite and NaN floating point values; don't attempt to write to + * the output buffer (which may be NULL) if a size of zero was specified; + * check for integer overflow of the field width, precision, and return + * values and during the floating point conversion; use the OUTCHAR() macro + * instead of a function for better performance; provide asprintf(3) and + * vasprintf(3) functions; add new test cases. The replacement functions + * have been renamed to use an "rpl_" prefix, the function calls in the + * main project (and in this file) must be redefined accordingly for each + * replacement function which is needed (by using Autoconf or other means). + * Various other minor improvements have been applied and the coding style + * was cleaned up for consistency. + * + * 2007-07-23 Holger Weiss for Mutt 1.5.13: + * + * C99 compliant snprintf(3) and vsnprintf(3) functions return the number + * of characters that would have been written to a sufficiently sized + * buffer (excluding the '\0'). The original code simply returned the + * length of the resulting output string, so that's been fixed. + * + * 1998-03-05 Michael Elkins for Mutt 0.90.8: + * + * The original code assumed that both snprintf(3) and vsnprintf(3) were + * missing. Some systems only have snprintf(3) but not vsnprintf(3), so + * the code is now broken down under HAVE_SNPRINTF and HAVE_VSNPRINTF. + * + * 1998-01-27 Thomas Roessler for Mutt 0.89i: + * + * The PGP code was using unsigned hexadecimal formats. Unfortunately, + * unsigned formats simply didn't work. + * + * 1997-10-22 Brandon Long for Mutt 0.87.1: + * + * Ok, added some minimal floating point support, which means this probably + * requires libm on most operating systems. Don't yet support the exponent + * (e,E) and sigfig (g,G). Also, fmtint() was pretty badly broken, it just + * wasn't being exercised in ways which showed it, so that's been fixed. + * Also, formatted the code to Mutt conventions, and removed dead code left + * over from the original. Also, there is now a builtin-test, run with: + * gcc -DTEST_SNPRINTF -o snprintf snprintf.c -lm && ./snprintf + * + * 2996-09-15 Brandon Long for Mutt 0.43: + * + * This was ugly. It is still ugly. I opted out of floating point + * numbers, but the formatter understands just about everything from the + * normal C string format, at least as far as I can tell from the Solaris + * 2.5 printf(3S) man page. + */ + +/* + * ToDo + * + * - Add wide character support. + * - Add support for "%a" and "%A" conversions. + * - Create test routines which predefine the expected results. Our test cases + * usually expose bugs in system implementations rather than in ours :-) + */ + +/* + * Usage + * + * 1) The following preprocessor macros should be defined to 1 if the feature or + * file in question is available on the target system (by using Autoconf or + * other means), though basic functionality should be available as long as + * HAVE_STDARG_H and HAVE_STDLIB_H are defined correctly: + * + * HAVE_VSNPRINTF + * HAVE_SNPRINTF + * HAVE_VASPRINTF + * HAVE_ASPRINTF + * HAVE_STDARG_H + * HAVE_STDDEF_H + * HAVE_STDINT_H + * HAVE_STDLIB_H + * HAVE_INTTYPES_H + * HAVE_LOCALE_H + * HAVE_LOCALECONV + * HAVE_LCONV_DECIMAL_POINT + * HAVE_LCONV_THOUSANDS_SEP + * HAVE_LONG_DOUBLE + * HAVE_LONG_LONG_INT + * HAVE_UNSIGNED_LONG_LONG_INT + * HAVE_INTMAX_T + * HAVE_UINTMAX_T + * HAVE_UINTPTR_T + * HAVE_PTRDIFF_T + * HAVE_VA_COPY + * HAVE___VA_COPY + * + * 2) The calls to the functions which should be replaced must be redefined + * throughout the project files (by using Autoconf or other means): + * + * #define vsnprintf rpl_vsnprintf + * #define snprintf rpl_snprintf + * #define vasprintf rpl_vasprintf + * #define asprintf rpl_asprintf + * + * 3) The required replacement functions should be declared in some header file + * included throughout the project files: + * + * #if HAVE_CONFIG_H + * #include + * #endif + * #if HAVE_STDARG_H + * #include + * #if !HAVE_VSNPRINTF + * int rpl_vsnprintf(char *, size_t, const char *, va_list); + * #endif + * #if !HAVE_SNPRINTF + * int rpl_snprintf(char *, size_t, const char *, ...); + * #endif + * #if !HAVE_VASPRINTF + * int rpl_vasprintf(char **, const char *, va_list); + * #endif + * #if !HAVE_ASPRINTF + * int rpl_asprintf(char **, const char *, ...); + * #endif + * #endif + * + * Autoconf macros for handling step 1 and step 2 are available at + * . + */ + +#include "port/snprintf.h" + +#if HAVE_CONFIG_H +#include +#endif /* HAVE_CONFIG_H */ + +#if TEST_SNPRINTF +#include /* For pow(3), NAN, and INFINITY. */ +#include /* For strcmp(3). */ +#if defined(__NetBSD__) || \ + defined(__FreeBSD__) || \ + defined(__OpenBSD__) || \ + defined(__NeXT__) || \ + defined(__bsd__) +#define OS_BSD 1 +#else +#if defined(sgi) || defined(__sgi) +#ifndef __c99 +#define __c99 /* Force C99 mode to get included on IRIX 6.5.30. */ +#endif /* !defined(__c99) */ +#define OS_IRIX 1 +#define OS_SYSV 1 +#else +#if defined(__svr4__) +#define OS_SYSV 1 +#else +#if defined(__linux__) +#define OS_LINUX 1 +#endif +#endif +#endif +#endif /* defined(__NetBSD__) || defined(__FreeBSD__) || [...] */ +#if HAVE_CONFIG_H /* Undefine definitions possibly done in config.h. */ +#ifdef HAVE_SNPRINTF +#undef HAVE_SNPRINTF +#endif /* defined(HAVE_SNPRINTF) */ +#ifdef HAVE_VSNPRINTF +#undef HAVE_VSNPRINTF +#endif /* defined(HAVE_VSNPRINTF) */ +#ifdef HAVE_ASPRINTF +#undef HAVE_ASPRINTF +#endif /* defined(HAVE_ASPRINTF) */ +#ifdef HAVE_VASPRINTF +#undef HAVE_VASPRINTF +#endif /* defined(HAVE_VASPRINTF) */ +#ifdef snprintf +#undef snprintf +#endif /* defined(snprintf) */ +#ifdef vsnprintf +#undef vsnprintf +#endif /* defined(vsnprintf) */ +#ifdef asprintf +#undef asprintf +#endif /* defined(asprintf) */ +#ifdef vasprintf +#undef vasprintf +#endif /* defined(vasprintf) */ +#else /* By default, we assume a modern system for testing. */ +#ifndef HAVE_STDARG_H +#define HAVE_STDARG_H 1 +#endif /* HAVE_STDARG_H */ +#ifndef HAVE_STDDEF_H +#define HAVE_STDDEF_H 1 +#endif /* HAVE_STDDEF_H */ +#ifndef HAVE_STDINT_H +#define HAVE_STDINT_H 1 +#endif /* HAVE_STDINT_H */ +#ifndef HAVE_STDLIB_H +#define HAVE_STDLIB_H 1 +#endif /* HAVE_STDLIB_H */ +#ifndef HAVE_INTTYPES_H +#define HAVE_INTTYPES_H 1 +#endif /* HAVE_INTTYPES_H */ +#ifndef HAVE_LOCALE_H +#define HAVE_LOCALE_H 1 +#endif /* HAVE_LOCALE_H */ +#ifndef HAVE_LOCALECONV +#define HAVE_LOCALECONV 1 +#endif /* !defined(HAVE_LOCALECONV) */ +#ifndef HAVE_LCONV_DECIMAL_POINT +#define HAVE_LCONV_DECIMAL_POINT 1 +#endif /* HAVE_LCONV_DECIMAL_POINT */ +#ifndef HAVE_LCONV_THOUSANDS_SEP +#define HAVE_LCONV_THOUSANDS_SEP 1 +#endif /* HAVE_LCONV_THOUSANDS_SEP */ +#ifndef HAVE_LONG_DOUBLE +#define HAVE_LONG_DOUBLE 1 +#endif /* !defined(HAVE_LONG_DOUBLE) */ +#ifndef HAVE_LONG_LONG_INT +#define HAVE_LONG_LONG_INT 1 +#endif /* !defined(HAVE_LONG_LONG_INT) */ +#ifndef HAVE_UNSIGNED_LONG_LONG_INT +#define HAVE_UNSIGNED_LONG_LONG_INT 1 +#endif /* !defined(HAVE_UNSIGNED_LONG_LONG_INT) */ +#ifndef HAVE_INTMAX_T +#define HAVE_INTMAX_T 1 +#endif /* !defined(HAVE_INTMAX_T) */ +#ifndef HAVE_UINTMAX_T +#define HAVE_UINTMAX_T 1 +#endif /* !defined(HAVE_UINTMAX_T) */ +#ifndef HAVE_UINTPTR_T +#define HAVE_UINTPTR_T 1 +#endif /* !defined(HAVE_UINTPTR_T) */ +#ifndef HAVE_PTRDIFF_T +#define HAVE_PTRDIFF_T 1 +#endif /* !defined(HAVE_PTRDIFF_T) */ +#ifndef HAVE_VA_COPY +#define HAVE_VA_COPY 1 +#endif /* !defined(HAVE_VA_COPY) */ +#ifndef HAVE___VA_COPY +#define HAVE___VA_COPY 1 +#endif /* !defined(HAVE___VA_COPY) */ +#endif /* HAVE_CONFIG_H */ +#define snprintf rpl_snprintf +#define vsnprintf rpl_vsnprintf +#define asprintf rpl_asprintf +#define vasprintf rpl_vasprintf +#endif /* TEST_SNPRINTF */ + +#if !defined( HAVE_SNPRINTF ) || !defined( HAVE_VSNPRINTF ) || !defined( HAVE_ASPRINTF ) || !defined( HAVE_VASPRINTF ) +#include /* For NULL, size_t, vsnprintf(3), and vasprintf(3). */ +#ifdef VA_START +#undef VA_START +#endif /* defined(VA_START) */ +#ifdef VA_SHIFT +#undef VA_SHIFT +#endif /* defined(VA_SHIFT) */ +#if HAVE_STDARG_H +#include +#define VA_START(ap, last) va_start(ap, last) +#define VA_SHIFT(ap, value, type) /* No-op for ANSI C. */ +#else /* Assume is available. */ +#include +#define VA_START(ap, last) va_start(ap) /* "last" is ignored. */ +#define VA_SHIFT(ap, value, type) value = va_arg(ap, type) +#endif /* HAVE_STDARG_H */ + +#if !defined HAVE_VASPRINTF || !HAVE_VASPRINTF +#if HAVE_STDLIB_H +#include /* For malloc(3). */ +#endif /* HAVE_STDLIB_H */ +#ifdef VA_COPY +#undef VA_COPY +#endif /* defined(VA_COPY) */ +#ifdef VA_END_COPY +#undef VA_END_COPY +#endif /* defined(VA_END_COPY) */ +#if defined HAVE_VA_COPY && HAVE_VA_COPY +#define VA_COPY(dest, src) va_copy(dest, src) +#define VA_END_COPY(ap) va_end(ap) +#else +#if defined HAVE___VA_COPY && HAVE___VA_COPY +#define VA_COPY(dest, src) __va_copy(dest, src) +#define VA_END_COPY(ap) va_end(ap) +#else +#define VA_COPY(dest, src) (void)mymemcpy(&dest, &src, sizeof(va_list)) +#define VA_END_COPY(ap) /* No-op. */ +#define NEED_MYMEMCPY 1 +static void *mymemcpy(void *, void *, size_t); +#endif /* HAVE___VA_COPY */ +#endif /* HAVE_VA_COPY */ +#endif /* !HAVE_VASPRINTF */ + +#if !defined HAVE_VSNPRINTF +#include /* For ERANGE and errno. */ +#include /* For *_MAX. */ +#if HAVE_INTTYPES_H +#include /* For intmax_t (if not defined in ). */ +#endif /* HAVE_INTTYPES_H */ +#if HAVE_LOCALE_H +#include /* For localeconv(3). */ +#endif /* HAVE_LOCALE_H */ +#if HAVE_STDDEF_H +#include /* For ptrdiff_t. */ +#endif /* HAVE_STDDEF_H */ +#if HAVE_STDINT_H +#include /* For intmax_t. */ +#endif /* HAVE_STDINT_H */ + +/* Support for unsigned long long int. We may also need ULLONG_MAX. */ +#ifndef ULONG_MAX /* We may need ULONG_MAX as a fallback. */ +#ifdef UINT_MAX +#define ULONG_MAX UINT_MAX +#else +#define ULONG_MAX INT_MAX +#endif /* defined(UINT_MAX) */ +#endif /* !defined(ULONG_MAX) */ +#ifdef ULLONG +#undef ULLONG +#endif /* defined(ULLONG) */ +#if HAVE_UNSIGNED_LONG_LONG_INT +#define ULLONG unsigned long long int +#ifndef ULLONG_MAX +#define ULLONG_MAX ULONG_MAX +#endif /* !defined(ULLONG_MAX) */ +#else +#define ULLONG unsigned long int +#ifdef ULLONG_MAX +#undef ULLONG_MAX +#endif /* defined(ULLONG_MAX) */ +#define ULLONG_MAX ULONG_MAX +#endif /* HAVE_LONG_LONG_INT */ + +/* Support for uintmax_t. We also need UINTMAX_MAX. */ +#ifdef UINTMAX_T +#undef UINTMAX_T +#endif /* defined(UINTMAX_T) */ +#if HAVE_UINTMAX_T || defined(uintmax_t) +#define UINTMAX_T uintmax_t +#ifndef UINTMAX_MAX +#define UINTMAX_MAX ULLONG_MAX +#endif /* !defined(UINTMAX_MAX) */ +#else +#define UINTMAX_T ULLONG +#ifdef UINTMAX_MAX +#undef UINTMAX_MAX +#endif /* defined(UINTMAX_MAX) */ +#define UINTMAX_MAX ULLONG_MAX +#endif /* HAVE_UINTMAX_T || defined(uintmax_t) */ + +/* Support for long double. */ +#ifndef LDOUBLE +#if HAVE_LONG_DOUBLE +#define LDOUBLE long double +#else +#define LDOUBLE double +#endif /* HAVE_LONG_DOUBLE */ +#endif /* !defined(LDOUBLE) */ + +/* Support for long long int. */ +#ifndef LLONG +#if HAVE_LONG_LONG_INT +#define LLONG long long int +#else +#define LLONG long int +#endif /* HAVE_LONG_LONG_INT */ +#endif /* !defined(LLONG) */ + +/* Support for intmax_t. */ +#ifndef INTMAX_T +#if HAVE_INTMAX_T || defined(intmax_t) +#define INTMAX_T intmax_t +#else +#define INTMAX_T LLONG +#endif /* HAVE_INTMAX_T || defined(intmax_t) */ +#endif /* !defined(INTMAX_T) */ + +/* Support for uintptr_t. */ +#ifndef UINTPTR_T +#if HAVE_UINTPTR_T || defined(uintptr_t) +#define UINTPTR_T uintptr_t +#else +#define UINTPTR_T unsigned long int +#endif /* HAVE_UINTPTR_T || defined(uintptr_t) */ +#endif /* !defined(UINTPTR_T) */ + +/* Support for ptrdiff_t. */ +#ifndef PTRDIFF_T +#if HAVE_PTRDIFF_T || defined(ptrdiff_t) +#define PTRDIFF_T ptrdiff_t +#else +#define PTRDIFF_T long int +#endif /* HAVE_PTRDIFF_T || defined(ptrdiff_t) */ +#endif /* !defined(PTRDIFF_T) */ + +/* + * We need an unsigned integer type corresponding to ptrdiff_t (cf. C99: + * 7.19.6.1, 7). However, we'll simply use PTRDIFF_T and convert it to an + * unsigned type if necessary. This should work just fine in practice. + */ +#ifndef UPTRDIFF_T +#define UPTRDIFF_T PTRDIFF_T +#endif /* !defined(UPTRDIFF_T) */ + +/* + * We need a signed integer type corresponding to size_t (cf. C99: 7.19.6.1, 7). + * However, we'll simply use size_t and convert it to a signed type if + * necessary. This should work just fine in practice. + */ +#ifndef SSIZE_T +#define SSIZE_T size_t +#endif /* !defined(SSIZE_T) */ + +/* Either ERANGE or E2BIG should be available everywhere. */ +#ifndef ERANGE +#define ERANGE E2BIG +#endif /* !defined(ERANGE) */ +#ifndef EOVERFLOW +#define EOVERFLOW ERANGE +#endif /* !defined(EOVERFLOW) */ + +/* + * Buffer size to hold the octal string representation of UINT128_MAX without + * nul-termination ("3777777777777777777777777777777777777777777"). + */ +#ifdef MAX_CONVERT_LENGTH +#undef MAX_CONVERT_LENGTH +#endif /* defined(MAX_CONVERT_LENGTH) */ +#define MAX_CONVERT_LENGTH 43 + +/* Format read states. */ +#define PRINT_S_DEFAULT 0 +#define PRINT_S_FLAGS 1 +#define PRINT_S_WIDTH 2 +#define PRINT_S_DOT 3 +#define PRINT_S_PRECISION 4 +#define PRINT_S_MOD 5 +#define PRINT_S_CONV 6 + +/* Format flags. */ +#define PRINT_F_MINUS (1 << 0) +#define PRINT_F_PLUS (1 << 1) +#define PRINT_F_SPACE (1 << 2) +#define PRINT_F_NUM (1 << 3) +#define PRINT_F_ZERO (1 << 4) +#define PRINT_F_QUOTE (1 << 5) +#define PRINT_F_UP (1 << 6) +#define PRINT_F_UNSIGNED (1 << 7) +#define PRINT_F_TYPE_G (1 << 8) +#define PRINT_F_TYPE_E (1 << 9) + +/* Conversion flags. */ +#define PRINT_C_CHAR 1 +#define PRINT_C_SHORT 2 +#define PRINT_C_LONG 3 +#define PRINT_C_LLONG 4 +#define PRINT_C_LDOUBLE 5 +#define PRINT_C_SIZE 6 +#define PRINT_C_PTRDIFF 7 +#define PRINT_C_INTMAX 8 + +#ifndef MAX +#define MAX(x, y) ((x >= y) ? x : y) +#endif /* !defined(MAX) */ +#ifndef CHARTOINT +#define CHARTOINT(ch) (ch - '0') +#endif /* !defined(CHARTOINT) */ +#ifndef ISDIGIT +#define ISDIGIT(ch) ('0' <= (unsigned char)ch && (unsigned char)ch <= '9') +#endif /* !defined(ISDIGIT) */ +#ifndef ISNAN +#define ISNAN(x) (x != x) +#endif /* !defined(ISNAN) */ +#ifndef ISINF +#define ISINF(x) (x != 0.0 && x + x == x) +#endif /* !defined(ISINF) */ + +#ifdef OUTCHAR +#undef OUTCHAR +#endif /* defined(OUTCHAR) */ +#define OUTCHAR(str, len, size, ch) \ +do { \ + if (len + 1 < size) \ + str[len] = ch; \ + (len)++; \ +} while (/* CONSTCOND */ 0) + +static void fmtstr(char *, size_t *, size_t, const char *, int, int, int); +static void fmtint(char *, size_t *, size_t, INTMAX_T, int, int, int, int); +static void fmtflt(char *, size_t *, size_t, LDOUBLE, int, int, int, int *); +static void printsep(char *, size_t *, size_t); +static int getnumsep(int); +static int getexponent(LDOUBLE); +static int convert(UINTMAX_T, char *, size_t, int, int); +static UINTMAX_T cast(LDOUBLE); +static UINTMAX_T myround(LDOUBLE); +static LDOUBLE mypow10(int); + +extern int errno; + +int +rpl_vsnprintf(char *str, size_t size, const char *format, va_list args) +{ + LDOUBLE fvalue; + INTMAX_T value; + unsigned char cvalue; + const char *strvalue; + INTMAX_T *intmaxptr; + PTRDIFF_T *ptrdiffptr; + SSIZE_T *sizeptr; + LLONG *llongptr; + long int *longptr; + int *intptr; + short int *shortptr; + signed char *charptr; + size_t len = 0; + int overflow = 0; + int base = 0; + int cflags = 0; + int flags = 0; + int width = 0; + int precision = -1; + int state = PRINT_S_DEFAULT; + char ch = *format++; + + /* + * C99 says: "If `n' is zero, nothing is written, and `s' may be a null + * pointer." (7.19.6.5, 2) We're forgiving and allow a NULL pointer + * even if a size larger than zero was specified. At least NetBSD's + * snprintf(3) does the same, as well as other versions of this file. + * (Though some of these versions will write to a non-NULL buffer even + * if a size of zero was specified, which violates the standard.) + */ + if (str == NULL && size != 0) + size = 0; + + while (ch != '\0') + switch (state) { + case PRINT_S_DEFAULT: + if (ch == '%') + state = PRINT_S_FLAGS; + else + OUTCHAR(str, len, size, ch); + ch = *format++; + break; + case PRINT_S_FLAGS: + switch (ch) { + case '-': + flags |= PRINT_F_MINUS; + ch = *format++; + break; + case '+': + flags |= PRINT_F_PLUS; + ch = *format++; + break; + case ' ': + flags |= PRINT_F_SPACE; + ch = *format++; + break; + case '#': + flags |= PRINT_F_NUM; + ch = *format++; + break; + case '0': + flags |= PRINT_F_ZERO; + ch = *format++; + break; + case '\'': /* SUSv2 flag (not in C99). */ + flags |= PRINT_F_QUOTE; + ch = *format++; + break; + default: + state = PRINT_S_WIDTH; + break; + } + break; + case PRINT_S_WIDTH: + if (ISDIGIT(ch)) { + ch = CHARTOINT(ch); + if (width > (INT_MAX - ch) / 10) { + overflow = 1; + goto out; + } + width = 10 * width + ch; + ch = *format++; + } else if (ch == '*') { + /* + * C99 says: "A negative field width argument is + * taken as a `-' flag followed by a positive + * field width." (7.19.6.1, 5) + */ + if ((width = va_arg(args, int)) < 0) { + flags |= PRINT_F_MINUS; + width = -width; + } + ch = *format++; + state = PRINT_S_DOT; + } else + state = PRINT_S_DOT; + break; + case PRINT_S_DOT: + if (ch == '.') { + state = PRINT_S_PRECISION; + ch = *format++; + } else + state = PRINT_S_MOD; + break; + case PRINT_S_PRECISION: + if (precision == -1) + precision = 0; + if (ISDIGIT(ch)) { + ch = CHARTOINT(ch); + if (precision > (INT_MAX - ch) / 10) { + overflow = 1; + goto out; + } + precision = 10 * precision + ch; + ch = *format++; + } else if (ch == '*') { + /* + * C99 says: "A negative precision argument is + * taken as if the precision were omitted." + * (7.19.6.1, 5) + */ + if ((precision = va_arg(args, int)) < 0) + precision = -1; + ch = *format++; + state = PRINT_S_MOD; + } else + state = PRINT_S_MOD; + break; + case PRINT_S_MOD: + switch (ch) { + case 'h': + ch = *format++; + if (ch == 'h') { /* It's a char. */ + ch = *format++; + cflags = PRINT_C_CHAR; + } else + cflags = PRINT_C_SHORT; + break; + case 'l': + ch = *format++; + if (ch == 'l') { /* It's a long long. */ + ch = *format++; + cflags = PRINT_C_LLONG; + } else + cflags = PRINT_C_LONG; + break; + case 'L': + cflags = PRINT_C_LDOUBLE; + ch = *format++; + break; + case 'j': + cflags = PRINT_C_INTMAX; + ch = *format++; + break; + case 't': + cflags = PRINT_C_PTRDIFF; + ch = *format++; + break; + case 'z': + cflags = PRINT_C_SIZE; + ch = *format++; + break; + } + state = PRINT_S_CONV; + break; + case PRINT_S_CONV: + switch (ch) { + case 'd': + /* FALLTHROUGH */ + case 'i': + switch (cflags) { + case PRINT_C_CHAR: + value = (signed char)va_arg(args, int); + break; + case PRINT_C_SHORT: + value = (short int)va_arg(args, int); + break; + case PRINT_C_LONG: + value = va_arg(args, long int); + break; + case PRINT_C_LLONG: + value = va_arg(args, LLONG); + break; + case PRINT_C_SIZE: + value = va_arg(args, SSIZE_T); + break; + case PRINT_C_INTMAX: + value = va_arg(args, INTMAX_T); + break; + case PRINT_C_PTRDIFF: + value = va_arg(args, PTRDIFF_T); + break; + default: + value = va_arg(args, int); + break; + } + fmtint(str, &len, size, value, 10, width, + precision, flags); + break; + case 'X': + flags |= PRINT_F_UP; + /* FALLTHROUGH */ + case 'x': + base = 16; + /* FALLTHROUGH */ + case 'o': + if (base == 0) + base = 8; + /* FALLTHROUGH */ + case 'u': + if (base == 0) + base = 10; + flags |= PRINT_F_UNSIGNED; + switch (cflags) { + case PRINT_C_CHAR: + value = (unsigned char)va_arg(args, + unsigned int); + break; + case PRINT_C_SHORT: + value = (unsigned short int)va_arg(args, + unsigned int); + break; + case PRINT_C_LONG: + value = va_arg(args, unsigned long int); + break; + case PRINT_C_LLONG: + value = va_arg(args, ULLONG); + break; + case PRINT_C_SIZE: + value = va_arg(args, size_t); + break; + case PRINT_C_INTMAX: + value = va_arg(args, UINTMAX_T); + break; + case PRINT_C_PTRDIFF: + value = va_arg(args, UPTRDIFF_T); + break; + default: + value = va_arg(args, unsigned int); + break; + } + fmtint(str, &len, size, value, base, width, + precision, flags); + break; + case 'A': + /* Not yet supported, we'll use "%F". */ + /* FALLTHROUGH */ + case 'F': + flags |= PRINT_F_UP; + case 'a': + /* Not yet supported, we'll use "%f". */ + /* FALLTHROUGH */ + case 'f': + if (cflags == PRINT_C_LDOUBLE) + fvalue = va_arg(args, LDOUBLE); + else + fvalue = va_arg(args, double); + fmtflt(str, &len, size, fvalue, width, + precision, flags, &overflow); + if (overflow) + goto out; + break; + case 'E': + flags |= PRINT_F_UP; + /* FALLTHROUGH */ + case 'e': + flags |= PRINT_F_TYPE_E; + if (cflags == PRINT_C_LDOUBLE) + fvalue = va_arg(args, LDOUBLE); + else + fvalue = va_arg(args, double); + fmtflt(str, &len, size, fvalue, width, + precision, flags, &overflow); + if (overflow) + goto out; + break; + case 'G': + flags |= PRINT_F_UP; + /* FALLTHROUGH */ + case 'g': + flags |= PRINT_F_TYPE_G; + if (cflags == PRINT_C_LDOUBLE) + fvalue = va_arg(args, LDOUBLE); + else + fvalue = va_arg(args, double); + /* + * If the precision is zero, it is treated as + * one (cf. C99: 7.19.6.1, 8). + */ + if (precision == 0) + precision = 1; + fmtflt(str, &len, size, fvalue, width, + precision, flags, &overflow); + if (overflow) + goto out; + break; + case 'c': + cvalue = va_arg(args, int); + OUTCHAR(str, len, size, cvalue); + break; + case 's': + strvalue = va_arg(args, char *); + fmtstr(str, &len, size, strvalue, width, + precision, flags); + break; + case 'p': + /* + * C99 says: "The value of the pointer is + * converted to a sequence of printing + * characters, in an implementation-defined + * manner." (C99: 7.19.6.1, 8) + */ + if ((strvalue = va_arg(args, void *)) == NULL) + /* + * We use the glibc format. BSD prints + * "0x0", SysV "0". + */ + fmtstr(str, &len, size, "(nil)", width, + -1, flags); + else { + /* + * We use the BSD/glibc format. SysV + * omits the "0x" prefix (which we emit + * using the PRINT_F_NUM flag). + */ + flags |= PRINT_F_NUM; + flags |= PRINT_F_UNSIGNED; + fmtint(str, &len, size, + (UINTPTR_T)strvalue, 16, width, + precision, flags); + } + break; + case 'n': + switch (cflags) { + case PRINT_C_CHAR: + charptr = va_arg(args, signed char *); + *charptr = len; + break; + case PRINT_C_SHORT: + shortptr = va_arg(args, short int *); + *shortptr = len; + break; + case PRINT_C_LONG: + longptr = va_arg(args, long int *); + *longptr = len; + break; + case PRINT_C_LLONG: + llongptr = va_arg(args, LLONG *); + *llongptr = len; + break; + case PRINT_C_SIZE: + /* + * C99 says that with the "z" length + * modifier, "a following `n' conversion + * specifier applies to a pointer to a + * signed integer type corresponding to + * size_t argument." (7.19.6.1, 7) + */ + sizeptr = va_arg(args, SSIZE_T *); + *sizeptr = len; + break; + case PRINT_C_INTMAX: + intmaxptr = va_arg(args, INTMAX_T *); + *intmaxptr = len; + break; + case PRINT_C_PTRDIFF: + ptrdiffptr = va_arg(args, PTRDIFF_T *); + *ptrdiffptr = len; + break; + default: + intptr = va_arg(args, int *); + *intptr = len; + break; + } + break; + case '%': /* Print a "%" character verbatim. */ + OUTCHAR(str, len, size, ch); + break; + default: /* Skip other characters. */ + break; + } + ch = *format++; + state = PRINT_S_DEFAULT; + base = cflags = flags = width = 0; + precision = -1; + break; + } +out: + if (len < size) + str[len] = '\0'; + else if (size > 0) + str[size - 1] = '\0'; + + if (overflow || len >= INT_MAX) { + errno = overflow ? EOVERFLOW : ERANGE; + return -1; + } + return (int)len; +} + +static void +fmtstr(char *str, size_t *len, size_t size, const char *value, int width, + int precision, int flags) +{ + int padlen, strln; /* Amount to pad. */ + int noprecision = (precision == -1); + + if (value == NULL) /* We're forgiving. */ + value = "(null)"; + + /* If a precision was specified, don't read the string past it. */ + for (strln = 0; value[strln] != '\0' && + (noprecision || strln < precision); strln++) + continue; + + if ((padlen = width - strln) < 0) + padlen = 0; + if (flags & PRINT_F_MINUS) /* Left justify. */ + padlen = -padlen; + + while (padlen > 0) { /* Leading spaces. */ + OUTCHAR(str, *len, size, ' '); + padlen--; + } + while (*value != '\0' && (noprecision || precision-- > 0)) { + OUTCHAR(str, *len, size, *value); + value++; + } + while (padlen < 0) { /* Trailing spaces. */ + OUTCHAR(str, *len, size, ' '); + padlen++; + } +} + +static void +fmtint(char *str, size_t *len, size_t size, INTMAX_T value, int base, int width, + int precision, int flags) +{ + UINTMAX_T uvalue; + char iconvert[MAX_CONVERT_LENGTH]; + char sign = 0; + char hexprefix = 0; + int spadlen = 0; /* Amount to space pad. */ + int zpadlen = 0; /* Amount to zero pad. */ + int pos; + int separators = (flags & PRINT_F_QUOTE); + int noprecision = (precision == -1); + + if (flags & PRINT_F_UNSIGNED) + uvalue = value; + else { + uvalue = (value >= 0) ? value : -value; + if (value < 0) + sign = '-'; + else if (flags & PRINT_F_PLUS) /* Do a sign. */ + sign = '+'; + else if (flags & PRINT_F_SPACE) + sign = ' '; + } + + pos = convert(uvalue, iconvert, sizeof(iconvert), base, + flags & PRINT_F_UP); + + if (flags & PRINT_F_NUM && uvalue != 0) { + /* + * C99 says: "The result is converted to an `alternative form'. + * For `o' conversion, it increases the precision, if and only + * if necessary, to force the first digit of the result to be a + * zero (if the value and precision are both 0, a single 0 is + * printed). For `x' (or `X') conversion, a nonzero result has + * `0x' (or `0X') prefixed to it." (7.19.6.1, 6) + */ + switch (base) { + case 8: + if (precision <= pos) + precision = pos + 1; + break; + case 16: + hexprefix = (flags & PRINT_F_UP) ? 'X' : 'x'; + break; + } + } + + if (separators) /* Get the number of group separators we'll print. */ + separators = getnumsep(pos); + + zpadlen = precision - pos - separators; + spadlen = width /* Minimum field width. */ + - separators /* Number of separators. */ + - MAX(precision, pos) /* Number of integer digits. */ + - ((sign != 0) ? 1 : 0) /* Will we print a sign? */ + - ((hexprefix != 0) ? 2 : 0); /* Will we print a prefix? */ + + if (zpadlen < 0) + zpadlen = 0; + if (spadlen < 0) + spadlen = 0; + + /* + * C99 says: "If the `0' and `-' flags both appear, the `0' flag is + * ignored. For `d', `i', `o', `u', `x', and `X' conversions, if a + * precision is specified, the `0' flag is ignored." (7.19.6.1, 6) + */ + if (flags & PRINT_F_MINUS) /* Left justify. */ + spadlen = -spadlen; + else if (flags & PRINT_F_ZERO && noprecision) { + zpadlen += spadlen; + spadlen = 0; + } + while (spadlen > 0) { /* Leading spaces. */ + OUTCHAR(str, *len, size, ' '); + spadlen--; + } + if (sign != 0) /* Sign. */ + OUTCHAR(str, *len, size, sign); + if (hexprefix != 0) { /* A "0x" or "0X" prefix. */ + OUTCHAR(str, *len, size, '0'); + OUTCHAR(str, *len, size, hexprefix); + } + while (zpadlen > 0) { /* Leading zeros. */ + OUTCHAR(str, *len, size, '0'); + zpadlen--; + } + while (pos > 0) { /* The actual digits. */ + pos--; + OUTCHAR(str, *len, size, iconvert[pos]); + if (separators > 0 && pos > 0 && pos % 3 == 0) + printsep(str, len, size); + } + while (spadlen < 0) { /* Trailing spaces. */ + OUTCHAR(str, *len, size, ' '); + spadlen++; + } +} + +static void +fmtflt(char *str, size_t *len, size_t size, LDOUBLE fvalue, int width, + int precision, int flags, int *overflow) +{ + LDOUBLE ufvalue; + UINTMAX_T intpart; + UINTMAX_T fracpart; + UINTMAX_T mask; + const char *infnan = NULL; + char iconvert[MAX_CONVERT_LENGTH]; + char fconvert[MAX_CONVERT_LENGTH]; + char econvert[4]; /* "e-12" (without nul-termination). */ + char esign = 0; + char sign = 0; + int leadfraczeros = 0; + int exponent = 0; + int emitpoint = 0; + int omitzeros = 0; + int omitcount = 0; + int padlen = 0; + int epos = 0; + int fpos = 0; + int ipos = 0; + int separators = (flags & PRINT_F_QUOTE); + int estyle = (flags & PRINT_F_TYPE_E); +#if HAVE_LOCALECONV && HAVE_LCONV_DECIMAL_POINT + struct lconv *lc = localeconv(); +#endif /* HAVE_LOCALECONV && HAVE_LCONV_DECIMAL_POINT */ + + /* + * AIX' man page says the default is 0, but C99 and at least Solaris' + * and NetBSD's man pages say the default is 6, and sprintf(3) on AIX + * defaults to 6. + */ + if (precision == -1) + precision = 6; + + if (fvalue < 0.0) + sign = '-'; + else if (flags & PRINT_F_PLUS) /* Do a sign. */ + sign = '+'; + else if (flags & PRINT_F_SPACE) + sign = ' '; + + if (ISNAN(fvalue)) + infnan = (flags & PRINT_F_UP) ? "NAN" : "nan"; + else if (ISINF(fvalue)) + infnan = (flags & PRINT_F_UP) ? "INF" : "inf"; + + if (infnan != NULL) { + if (sign != 0) + iconvert[ipos++] = sign; + while (*infnan != '\0') + iconvert[ipos++] = *infnan++; + fmtstr(str, len, size, iconvert, width, ipos, flags); + return; + } + + /* "%e" (or "%E") or "%g" (or "%G") conversion. */ + if (flags & PRINT_F_TYPE_E || flags & PRINT_F_TYPE_G) { + if (flags & PRINT_F_TYPE_G) { + /* + * For "%g" (and "%G") conversions, the precision + * specifies the number of significant digits, which + * includes the digits in the integer part. The + * conversion will or will not be using "e-style" (like + * "%e" or "%E" conversions) depending on the precision + * and on the exponent. However, the exponent can be + * affected by rounding the converted value, so we'll + * leave this decision for later. Until then, we'll + * assume that we're going to do an "e-style" conversion + * (in order to get the exponent calculated). For + * "e-style", the precision must be decremented by one. + */ + precision--; + /* + * For "%g" (and "%G") conversions, trailing zeros are + * removed from the fractional portion of the result + * unless the "#" flag was specified. + */ + if (!(flags & PRINT_F_NUM)) + omitzeros = 1; + } + exponent = getexponent(fvalue); + estyle = 1; + } + +again: + /* + * Sorry, we only support 9, 19, or 38 digits (that is, the number of + * digits of the 32-bit, the 64-bit, or the 128-bit UINTMAX_MAX value + * minus one) past the decimal point due to our conversion method. + */ + switch (sizeof(UINTMAX_T)) { + case 16: + if (precision > 38) + precision = 38; + break; + case 8: + if (precision > 19) + precision = 19; + break; + default: + if (precision > 9) + precision = 9; + break; + } + + ufvalue = (fvalue >= 0.0) ? fvalue : -fvalue; + if (estyle) /* We want exactly one integer digit. */ + ufvalue /= mypow10(exponent); + + if ((intpart = cast(ufvalue)) == UINTMAX_MAX) { + *overflow = 1; + return; + } + + /* + * Factor of ten with the number of digits needed for the fractional + * part. For example, if the precision is 3, the mask will be 1000. + */ + mask = mypow10(precision); + /* + * We "cheat" by converting the fractional part to integer by + * multiplying by a factor of ten. + */ + if ((fracpart = myround(mask * (ufvalue - intpart))) >= mask) { + /* + * For example, ufvalue = 2.99962, intpart = 2, and mask = 1000 + * (because precision = 3). Now, myround(1000 * 0.99962) will + * return 1000. So, the integer part must be incremented by one + * and the fractional part must be set to zero. + */ + intpart++; + fracpart = 0; + if (estyle && intpart == 10) { + /* + * The value was rounded up to ten, but we only want one + * integer digit if using "e-style". So, the integer + * part must be set to one and the exponent must be + * incremented by one. + */ + intpart = 1; + exponent++; + } + } + + /* + * Now that we know the real exponent, we can check whether or not to + * use "e-style" for "%g" (and "%G") conversions. If we don't need + * "e-style", the precision must be adjusted and the integer and + * fractional parts must be recalculated from the original value. + * + * C99 says: "Let P equal the precision if nonzero, 6 if the precision + * is omitted, or 1 if the precision is zero. Then, if a conversion + * with style `E' would have an exponent of X: + * + * - if P > X >= -4, the conversion is with style `f' (or `F') and + * precision P - (X + 1). + * + * - otherwise, the conversion is with style `e' (or `E') and precision + * P - 1." (7.19.6.1, 8) + * + * Note that we had decremented the precision by one. + */ + if (flags & PRINT_F_TYPE_G && estyle && + precision + 1 > exponent && exponent >= -4) { + precision -= exponent; + estyle = 0; + goto again; + } + + if (estyle) { + if (exponent < 0) { + exponent = -exponent; + esign = '-'; + } else + esign = '+'; + + /* + * Convert the exponent. The sizeof(econvert) is 4. So, the + * econvert buffer can hold e.g. "e+99" and "e-99". We don't + * support an exponent which contains more than two digits. + * Therefore, the following stores are safe. + */ + epos = convert(exponent, econvert, 2, 10, 0); + /* + * C99 says: "The exponent always contains at least two digits, + * and only as many more digits as necessary to represent the + * exponent." (7.19.6.1, 8) + */ + if (epos == 1) + econvert[epos++] = '0'; + econvert[epos++] = esign; + econvert[epos++] = (flags & PRINT_F_UP) ? 'E' : 'e'; + } + + /* Convert the integer part and the fractional part. */ + ipos = convert(intpart, iconvert, sizeof(iconvert), 10, 0); + if (fracpart != 0) /* convert() would return 1 if fracpart == 0. */ + fpos = convert(fracpart, fconvert, sizeof(fconvert), 10, 0); + + leadfraczeros = precision - fpos; + + if (omitzeros) { + if (fpos > 0) /* Omit trailing fractional part zeros. */ + while (omitcount < fpos && fconvert[omitcount] == '0') + omitcount++; + else { /* The fractional part is zero, omit it completely. */ + omitcount = precision; + leadfraczeros = 0; + } + precision -= omitcount; + } + + /* + * Print a decimal point if either the fractional part is non-zero + * and/or the "#" flag was specified. + */ + if (precision > 0 || flags & PRINT_F_NUM) + emitpoint = 1; + if (separators) /* Get the number of group separators we'll print. */ + separators = getnumsep(ipos); + + padlen = width /* Minimum field width. */ + - ipos /* Number of integer digits. */ + - epos /* Number of exponent characters. */ + - precision /* Number of fractional digits. */ + - separators /* Number of group separators. */ + - (emitpoint ? 1 : 0) /* Will we print a decimal point? */ + - ((sign != 0) ? 1 : 0); /* Will we print a sign character? */ + + if (padlen < 0) + padlen = 0; + + /* + * C99 says: "If the `0' and `-' flags both appear, the `0' flag is + * ignored." (7.19.6.1, 6) + */ + if (flags & PRINT_F_MINUS) /* Left justifty. */ + padlen = -padlen; + else if (flags & PRINT_F_ZERO && padlen > 0) { + if (sign != 0) { /* Sign. */ + OUTCHAR(str, *len, size, sign); + sign = 0; + } + while (padlen > 0) { /* Leading zeros. */ + OUTCHAR(str, *len, size, '0'); + padlen--; + } + } + while (padlen > 0) { /* Leading spaces. */ + OUTCHAR(str, *len, size, ' '); + padlen--; + } + if (sign != 0) /* Sign. */ + OUTCHAR(str, *len, size, sign); + while (ipos > 0) { /* Integer part. */ + ipos--; + OUTCHAR(str, *len, size, iconvert[ipos]); + if (separators > 0 && ipos > 0 && ipos % 3 == 0) + printsep(str, len, size); + } + if (emitpoint) { /* Decimal point. */ +#if HAVE_LOCALECONV && HAVE_LCONV_DECIMAL_POINT + if (lc->decimal_point != NULL && *lc->decimal_point != '\0') + OUTCHAR(str, *len, size, *lc->decimal_point); + else /* We'll always print some decimal point character. */ +#endif /* HAVE_LOCALECONV && HAVE_LCONV_DECIMAL_POINT */ + OUTCHAR(str, *len, size, '.'); + } + while (leadfraczeros > 0) { /* Leading fractional part zeros. */ + OUTCHAR(str, *len, size, '0'); + leadfraczeros--; + } + while (fpos > omitcount) { /* The remaining fractional part. */ + fpos--; + OUTCHAR(str, *len, size, fconvert[fpos]); + } + while (epos > 0) { /* Exponent. */ + epos--; + OUTCHAR(str, *len, size, econvert[epos]); + } + while (padlen < 0) { /* Trailing spaces. */ + OUTCHAR(str, *len, size, ' '); + padlen++; + } +} + +static void +printsep(char *str, size_t *len, size_t size) +{ +#if HAVE_LOCALECONV && HAVE_LCONV_THOUSANDS_SEP + struct lconv *lc = localeconv(); + int i; + + if (lc->thousands_sep != NULL) + for (i = 0; lc->thousands_sep[i] != '\0'; i++) + OUTCHAR(str, *len, size, lc->thousands_sep[i]); + else +#endif /* HAVE_LOCALECONV && HAVE_LCONV_THOUSANDS_SEP */ + OUTCHAR(str, *len, size, ','); +} + +static int +getnumsep(int digits) +{ + int separators = (digits - ((digits % 3 == 0) ? 1 : 0)) / 3; +#if HAVE_LOCALECONV && HAVE_LCONV_THOUSANDS_SEP + int strln; + struct lconv *lc = localeconv(); + + /* We support an arbitrary separator length (including zero). */ + if (lc->thousands_sep != NULL) { + for (strln = 0; lc->thousands_sep[strln] != '\0'; strln++) + continue; + separators *= strln; + } +#endif /* HAVE_LOCALECONV && HAVE_LCONV_THOUSANDS_SEP */ + return separators; +} + +static int +getexponent(LDOUBLE value) +{ + LDOUBLE tmp = (value >= 0.0) ? value : -value; + int exponent = 0; + + /* + * We check for 99 > exponent > -99 in order to work around possible + * endless loops which could happen (at least) in the second loop (at + * least) if we're called with an infinite value. However, we checked + * for infinity before calling this function using our ISINF() macro, so + * this might be somewhat paranoid. + */ + while (tmp < 1.0 && tmp > 0.0 && --exponent > -99) + tmp *= 10; + while (tmp >= 10.0 && ++exponent < 99) + tmp /= 10; + + return exponent; +} + +static int +convert(UINTMAX_T value, char *buf, size_t size, int base, int caps) +{ + const char *digits = caps ? "0123456789ABCDEF" : "0123456789abcdef"; + size_t pos = 0; + + /* We return an unterminated buffer with the digits in reverse order. */ + do { + buf[pos++] = digits[value % base]; + value /= base; + } while (value != 0 && pos < size); + + return (int)pos; +} + +static UINTMAX_T +cast(LDOUBLE value) +{ + UINTMAX_T result; + + /* + * We check for ">=" and not for ">" because if UINTMAX_MAX cannot be + * represented exactly as an LDOUBLE value (but is less than LDBL_MAX), + * it may be increased to the nearest higher representable value for the + * comparison (cf. C99: 6.3.1.4, 2). It might then equal the LDOUBLE + * value although converting the latter to UINTMAX_T would overflow. + */ + if (value >= UINTMAX_MAX) + return UINTMAX_MAX; + + result = value; + /* + * At least on NetBSD/sparc64 3.0.2 and 4.99.30, casting long double to + * an integer type converts e.g. 1.9 to 2 instead of 1 (which violates + * the standard). Sigh. + */ + return (result <= value) ? result : result - 1; +} + +static UINTMAX_T +myround(LDOUBLE value) +{ + UINTMAX_T intpart = cast(value); + + return ((value -= intpart) < 0.5) ? intpart : intpart + 1; +} + +static LDOUBLE +mypow10(int exponent) +{ + LDOUBLE result = 1; + + while (exponent > 0) { + result *= 10; + exponent--; + } + while (exponent < 0) { + result /= 10; + exponent++; + } + return result; +} +#endif /* !HAVE_VSNPRINTF */ + +#if !defined HAVE_VASPRINTF || !HAVE_VASPRINTF +#if defined NEED_MYMEMCPY && NEED_MYMEMCPY +void * +mymemcpy(void *dst, void *src, size_t len) +{ + const char *from = src; + char *to = dst; + + /* No need for optimization, we use this only to replace va_copy(3). */ + while (len-- > 0) + *to++ = *from++; + return dst; +} +#endif /* NEED_MYMEMCPY */ + +int +rpl_vasprintf(char **ret, const char *format, va_list ap) +{ + size_t size; + int len; + va_list aq; + + VA_COPY(aq, ap); + len = vsnprintf(NULL, 0, format, aq); + VA_END_COPY(aq); + if (len < 0 || (*ret = malloc(size = len + 1)) == NULL) + return -1; + return vsnprintf(*ret, size, format, ap); +} +#endif /* !HAVE_VASPRINTF */ + +#if !defined HAVE_SNPRINTF +#if HAVE_STDARG_H +int +rpl_snprintf(char *str, size_t size, const char *format, ...) +#else +int +rpl_snprintf(va_alist) va_dcl +#endif /* HAVE_STDARG_H */ +{ +#if !HAVE_STDARG_H + char *str; + size_t size; + char *format; +#endif /* HAVE_STDARG_H */ + va_list ap; + int len; + + VA_START(ap, format); + VA_SHIFT(ap, str, char *); + VA_SHIFT(ap, size, size_t); + VA_SHIFT(ap, format, const char *); + len = vsnprintf(str, size, format, ap); + va_end(ap); + return len; +} +#endif /* !HAVE_SNPRINTF */ + +#if !defined HAVE_ASPRINTF || !HAVE_ASPRINTF +#if HAVE_STDARG_H +int +rpl_asprintf(char **ret, const char *format, ...) +#else +int +rpl_asprintf(va_alist) va_dcl +#endif /* HAVE_STDARG_H */ +{ +#if !HAVE_STDARG_H + char **ret; + char *format; +#endif /* HAVE_STDARG_H */ + va_list ap; + int len; + + VA_START(ap, format); + VA_SHIFT(ap, ret, char **); + VA_SHIFT(ap, format, const char *); + len = vasprintf(ret, format, ap); + va_end(ap); + return len; +} +#endif /* !HAVE_ASPRINTF */ +#else /* Dummy declaration to avoid empty translation unit warnings. */ +int main(void); +#endif /* !HAVE_SNPRINTF || !HAVE_VSNPRINTF || !HAVE_ASPRINTF || [...] */ + +#if TEST_SNPRINTF +int +main(void) +{ + const char *float_fmt[] = { + /* "%E" and "%e" formats. */ +#if HAVE_LONG_LONG_INT && !OS_BSD && !OS_IRIX + "%.16e", + "%22.16e", + "%022.16e", + "%-22.16e", + "%#+'022.16e", +#endif /* HAVE_LONG_LONG_INT && !OS_BSD && !OS_IRIX */ + "foo|%#+0123.9E|bar", + "%-123.9e", + "%123.9e", + "%+23.9e", + "%+05.8e", + "%-05.8e", + "%05.8e", + "%+5.8e", + "%-5.8e", + "% 5.8e", + "%5.8e", + "%+4.9e", +#if !OS_LINUX /* glibc sometimes gets these wrong. */ + "%+#010.0e", + "%#10.1e", + "%10.5e", + "% 10.5e", + "%5.0e", + "%5.e", + "%#5.0e", + "%#5.e", + "%3.2e", + "%3.1e", + "%-1.5e", + "%1.5e", + "%01.3e", + "%1.e", + "%.1e", + "%#.0e", + "%+.0e", + "% .0e", + "%.0e", + "%#.e", + "%+.e", + "% .e", + "%.e", + "%4e", + "%e", + "%E", +#endif /* !OS_LINUX */ + /* "%F" and "%f" formats. */ +#if !OS_BSD && !OS_IRIX + "% '022f", + "%+'022f", + "%-'22f", + "%'22f", +#if HAVE_LONG_LONG_INT + "%.16f", + "%22.16f", + "%022.16f", + "%-22.16f", + "%#+'022.16f", +#endif /* HAVE_LONG_LONG_INT */ +#endif /* !OS_BSD && !OS_IRIX */ + "foo|%#+0123.9F|bar", + "%-123.9f", + "%123.9f", + "%+23.9f", + "%+#010.0f", + "%#10.1f", + "%10.5f", + "% 10.5f", + "%+05.8f", + "%-05.8f", + "%05.8f", + "%+5.8f", + "%-5.8f", + "% 5.8f", + "%5.8f", + "%5.0f", + "%5.f", + "%#5.0f", + "%#5.f", + "%+4.9f", + "%3.2f", + "%3.1f", + "%-1.5f", + "%1.5f", + "%01.3f", + "%1.f", + "%.1f", + "%#.0f", + "%+.0f", + "% .0f", + "%.0f", + "%#.f", + "%+.f", + "% .f", + "%.f", + "%4f", + "%f", + "%F", + /* "%G" and "%g" formats. */ +#if !OS_BSD && !OS_IRIX && !OS_LINUX + "% '022g", + "%+'022g", + "%-'22g", + "%'22g", +#if HAVE_LONG_LONG_INT + "%.16g", + "%22.16g", + "%022.16g", + "%-22.16g", + "%#+'022.16g", +#endif /* HAVE_LONG_LONG_INT */ +#endif /* !OS_BSD && !OS_IRIX && !OS_LINUX */ + "foo|%#+0123.9G|bar", + "%-123.9g", + "%123.9g", + "%+23.9g", + "%+05.8g", + "%-05.8g", + "%05.8g", + "%+5.8g", + "%-5.8g", + "% 5.8g", + "%5.8g", + "%+4.9g", +#if !OS_LINUX /* glibc sometimes gets these wrong. */ + "%+#010.0g", + "%#10.1g", + "%10.5g", + "% 10.5g", + "%5.0g", + "%5.g", + "%#5.0g", + "%#5.g", + "%3.2g", + "%3.1g", + "%-1.5g", + "%1.5g", + "%01.3g", + "%1.g", + "%.1g", + "%#.0g", + "%+.0g", + "% .0g", + "%.0g", + "%#.g", + "%+.g", + "% .g", + "%.g", + "%4g", + "%g", + "%G", +#endif /* !OS_LINUX */ + NULL + }; + double float_val[] = { + -4.136, + -134.52, + -5.04030201, + -3410.01234, + -999999.999999, + -913450.29876, + -913450.2, + -91345.2, + -9134.2, + -913.2, + -91.2, + -9.2, + -9.9, + 4.136, + 134.52, + 5.04030201, + 3410.01234, + 999999.999999, + 913450.29876, + 913450.2, + 91345.2, + 9134.2, + 913.2, + 91.2, + 9.2, + 9.9, + 9.96, + 9.996, + 9.9996, + 9.99996, + 9.999996, + 9.9999996, + 9.99999996, + 0.99999996, + 0.99999999, + 0.09999999, + 0.00999999, + 0.00099999, + 0.00009999, + 0.00000999, + 0.00000099, + 0.00000009, + 0.00000001, + 0.0000001, + 0.000001, + 0.00001, + 0.0001, + 0.001, + 0.01, + 0.1, + 1.0, + 1.5, + -1.5, + -1.0, + -0.1, +#if !OS_BSD /* BSD sometimes gets these wrong. */ +#ifdef INFINITY + INFINITY, + -INFINITY, +#endif /* defined(INFINITY) */ +#ifdef NAN + NAN, +#endif /* defined(NAN) */ +#endif /* !OS_BSD */ + 0 + }; + const char *long_fmt[] = { + "foo|%0123ld|bar", +#if !OS_IRIX + "% '0123ld", + "%+'0123ld", + "%-'123ld", + "%'123ld", +#endif /* !OS_IRiX */ + "%123.9ld", + "% 123.9ld", + "%+123.9ld", + "%-123.9ld", + "%0123ld", + "% 0123ld", + "%+0123ld", + "%-0123ld", + "%10.5ld", + "% 10.5ld", + "%+10.5ld", + "%-10.5ld", + "%010ld", + "% 010ld", + "%+010ld", + "%-010ld", + "%4.2ld", + "% 4.2ld", + "%+4.2ld", + "%-4.2ld", + "%04ld", + "% 04ld", + "%+04ld", + "%-04ld", + "%5.5ld", + "%+22.33ld", + "%01.3ld", + "%1.5ld", + "%-1.5ld", + "%44ld", + "%4ld", + "%4.0ld", + "%4.ld", + "%.44ld", + "%.4ld", + "%.0ld", + "%.ld", + "%ld", + NULL + }; + long int long_val[] = { +#ifdef LONG_MAX + LONG_MAX, +#endif /* LONG_MAX */ +#ifdef LONG_MIN + LONG_MIN, +#endif /* LONG_MIN */ + -91340, + 91340, + 341, + 134, + 0203, + -1, + 1, + 0 + }; + const char *ulong_fmt[] = { + /* "%u" formats. */ + "foo|%0123lu|bar", +#if !OS_IRIX + "% '0123lu", + "%+'0123lu", + "%-'123lu", + "%'123lu", +#endif /* !OS_IRiX */ + "%123.9lu", + "% 123.9lu", + "%+123.9lu", + "%-123.9lu", + "%0123lu", + "% 0123lu", + "%+0123lu", + "%-0123lu", + "%5.5lu", + "%+22.33lu", + "%01.3lu", + "%1.5lu", + "%-1.5lu", + "%44lu", + "%lu", + /* "%o" formats. */ + "foo|%#0123lo|bar", + "%#123.9lo", + "%# 123.9lo", + "%#+123.9lo", + "%#-123.9lo", + "%#0123lo", + "%# 0123lo", + "%#+0123lo", + "%#-0123lo", + "%#5.5lo", + "%#+22.33lo", + "%#01.3lo", + "%#1.5lo", + "%#-1.5lo", + "%#44lo", + "%#lo", + "%123.9lo", + "% 123.9lo", + "%+123.9lo", + "%-123.9lo", + "%0123lo", + "% 0123lo", + "%+0123lo", + "%-0123lo", + "%5.5lo", + "%+22.33lo", + "%01.3lo", + "%1.5lo", + "%-1.5lo", + "%44lo", + "%lo", + /* "%X" and "%x" formats. */ + "foo|%#0123lX|bar", + "%#123.9lx", + "%# 123.9lx", + "%#+123.9lx", + "%#-123.9lx", + "%#0123lx", + "%# 0123lx", + "%#+0123lx", + "%#-0123lx", + "%#5.5lx", + "%#+22.33lx", + "%#01.3lx", + "%#1.5lx", + "%#-1.5lx", + "%#44lx", + "%#lx", + "%#lX", + "%123.9lx", + "% 123.9lx", + "%+123.9lx", + "%-123.9lx", + "%0123lx", + "% 0123lx", + "%+0123lx", + "%-0123lx", + "%5.5lx", + "%+22.33lx", + "%01.3lx", + "%1.5lx", + "%-1.5lx", + "%44lx", + "%lx", + "%lX", + NULL + }; + unsigned long int ulong_val[] = { +#ifdef ULONG_MAX + ULONG_MAX, +#endif /* ULONG_MAX */ + 91340, + 341, + 134, + 0203, + 1, + 0 + }; + const char *llong_fmt[] = { + "foo|%0123lld|bar", + "%123.9lld", + "% 123.9lld", + "%+123.9lld", + "%-123.9lld", + "%0123lld", + "% 0123lld", + "%+0123lld", + "%-0123lld", + "%5.5lld", + "%+22.33lld", + "%01.3lld", + "%1.5lld", + "%-1.5lld", + "%44lld", + "%lld", + NULL + }; + LLONG llong_val[] = { +#ifdef LLONG_MAX + LLONG_MAX, +#endif /* LLONG_MAX */ +#ifdef LLONG_MIN + LLONG_MIN, +#endif /* LLONG_MIN */ + -91340, + 91340, + 341, + 134, + 0203, + -1, + 1, + 0 + }; + const char *string_fmt[] = { + "foo|%10.10s|bar", + "%-10.10s", + "%10.10s", + "%10.5s", + "%5.10s", + "%10.1s", + "%1.10s", + "%10.0s", + "%0.10s", + "%-42.5s", + "%2.s", + "%.10s", + "%.1s", + "%.0s", + "%.s", + "%4s", + "%s", + NULL + }; + const char *string_val[] = { + "Hello", + "Hello, world!", + "Sound check: One, two, three.", + "This string is a little longer than the other strings.", + "1", + "", + NULL + }; +#if !OS_SYSV /* SysV uses a different format than we do. */ + const char *pointer_fmt[] = { + "foo|%p|bar", + "%42p", + "%p", + NULL + }; + const char *pointer_val[] = { + *pointer_fmt, + *string_fmt, + *string_val, + NULL + }; +#endif /* !OS_SYSV */ + char buf1[1024], buf2[1024]; + double value, digits = 9.123456789012345678901234567890123456789; + int i, j, r1, r2, failed = 0, num = 0; + +/* + * Use -DTEST_NILS in order to also test the conversion of nil values. Might + * segfault on systems which don't support converting a NULL pointer with "%s" + * and lets some test cases fail against BSD and glibc due to bugs in their + * implementations. + */ +#ifndef TEST_NILS +#define TEST_NILS 0 +#else +#if TEST_NILS +#undef TEST_NILS +#define TEST_NILS 1 +#endif /* TEST_NILS */ +#endif /* !defined(TEST_NILS) */ +#ifdef TEST +#undef TEST +#endif /* defined(TEST) */ +#define TEST(fmt, val) \ +do { \ + for (i = 0; fmt[i] != NULL; i++) \ + for (j = 0; j == 0 || val[j - TEST_NILS] != 0; j++) { \ + r1 = sprintf(buf1, fmt[i], val[j]); \ + r2 = snprintf(buf2, sizeof(buf2), fmt[i], val[j]); \ + if (strcmp(buf1, buf2) != 0 || r1 != r2) { \ + (void)printf("Results don't match, " \ + "format string: %s\n" \ + "\t sprintf(3): [%s] (%d)\n" \ + "\tsnprintf(3): [%s] (%d)\n", \ + fmt[i], buf1, r1, buf2, r2); \ + failed++; \ + } \ + num++; \ + } \ +} while (/* CONSTCOND */ 0) + +#if HAVE_LOCALE_H + (void)setlocale(LC_ALL, ""); +#endif /* HAVE_LOCALE_H */ + + (void)puts("Testing our snprintf(3) against your system's sprintf(3)."); + TEST(float_fmt, float_val); + TEST(long_fmt, long_val); + TEST(ulong_fmt, ulong_val); + TEST(llong_fmt, llong_val); + TEST(string_fmt, string_val); +#if !OS_SYSV /* SysV uses a different format than we do. */ + TEST(pointer_fmt, pointer_val); +#endif /* !OS_SYSV */ + (void)printf("Result: %d out of %d tests failed.\n", failed, num); + + (void)fputs("Checking how many digits we support: ", stdout); + for (i = 0; i < 100; i++) { + value = pow(10, i) * digits; + (void)sprintf(buf1, "%.1f", value); + (void)snprintf(buf2, sizeof(buf2), "%.1f", value); + if (strcmp(buf1, buf2) != 0) { + (void)printf("apparently %d.\n", i); + break; + } + } + return (failed == 0) ? 0 : 1; +} +#endif /* TEST_SNPRINTF */ + +/* vim: set joinspaces textwidth=80: */ diff --git a/src/port/snprintf.h b/src/port/snprintf.h new file mode 100644 index 0000000..158e5fb --- /dev/null +++ b/src/port/snprintf.h @@ -0,0 +1,59 @@ +#ifndef __SNPRINTF_H +#define __SNPRINTF_H + +#include "sys.h" + +#define HAVE_CONFIG_H 0 +#define TEST_SNPRINTF 0 +#define HAVE_STDARG_H 1 +#define HAVE_STDDEF_H 1 +#define HAVE_SYS_TYPES_H 1 +#define HAVE_STDLIB_H 1 +#define HAVE_VA_COPY 1 + +#if HAVE_STDARG_H +#include /* for va_list */ +#endif + +#if HAVE_SYS_TYPES_H +#include /* for size_t */ +#endif + +#if !defined HAVE_VSNPRINTF +#define vsnprintf rpl_vsnprintf +#if HAVE_STDARG_H +extern int rpl_vsnprintf(char *str, size_t size, const char *format, va_list args); +#else + #error va_list required for vsnprintf! +#endif /* HAVE_STDARG_H */ +#endif /* !defined HAVE_VSNPRINTF */ + +#if !defined HAVE_SNPRINTF +#define snprintf rpl_snprintf +#if HAVE_STDARG_H +extern int +rpl_snprintf(char *str, size_t size, const char *format, ...); +#else +extern int +rpl_snprintf(va_alist) va_dcl; +#endif /* HAVE_STDARG_H */ +#endif /* ! defined HAVE_SNPRINTF */ + +#if !defined HAVE_VASPRINTF +#define vasprintf rpl_vasprintf +extern int +rpl_vasprintf(char **ret, const char *format, va_list ap); +#endif /* ! defined HAVE_VASPRINTF */ + +#if !defined HAVE_ASPRINTF +#define asprintf rpl_asprintf +#if HAVE_STDARG_H +extern int +rpl_asprintf(char **ret, const char *format, ...); +#else +extern int +rpl_asprintf(va_alist) va_dcl; +#endif /* HAVE_STDARG_H */ +#endif /* ! defined HAVE_ASPRINTF */ + +#endif /* ifndef __SNPRINTF_H */ diff --git a/src/port/stdbool.h b/src/port/stdbool.h new file mode 100644 index 0000000..9c011e4 --- /dev/null +++ b/src/port/stdbool.h @@ -0,0 +1,35 @@ +#ifndef __STDBOOL_H +#define __STDBOOL_H + +#include "sys.h" + +/* C99 Boolean types for compilers without C99 support, see + * http://www.opengroup.org/onlinepubs/009695399/basedefs/stdbool.h.html */ + +/* don't define bool in C++, it exists already! */ +#ifndef __cplusplus + +#ifdef HAVE_STDBOOL_H +#include +#else + +#if !defined HAVE_ENUM_BOOL +typedef enum { + _Bool_must_promote_to_int = -1, + false = 0, + true = 1 +} _Bool; +#endif /* !defined HAVE_ENUM_BOOL */ + +#define bool _Bool + +#define true 1 +#define false 0 + +#define __bool_true_false_are_defined 1 + +#endif /* ifdef HAVE_STDBOOL_H */ + +#endif /* ifndef __cplusplus */ + +#endif /* ifndef STDBOOL_H */ diff --git a/src/port/stdint.h b/src/port/stdint.h new file mode 100644 index 0000000..d5d830a --- /dev/null +++ b/src/port/stdint.h @@ -0,0 +1,15 @@ +#ifndef __STDINT_H +#define __STDINT_H + +#include "sys.h" + +#if defined HAVE_STDINT_H +#include +#endif /* defined HAVE_STDINT_H */ + +/* Solaris 8 weirdness */ +#if defined HAVE_LINK_H +#include +#endif /* defined HAVE_LINK_H */ + +#endif /* ifndef __STDINT_H */ diff --git a/src/port/stdio.h b/src/port/stdio.h new file mode 100644 index 0000000..59a7d67 --- /dev/null +++ b/src/port/stdio.h @@ -0,0 +1,10 @@ +#ifndef __STDIO_H +#define __STDIO_H + +#include "sys.h" + +#include + +#include "snprintf.h" + +#endif /* ifndef __STDIO_H */ diff --git a/src/port/string.h b/src/port/string.h new file mode 100644 index 0000000..8f96466 --- /dev/null +++ b/src/port/string.h @@ -0,0 +1,16 @@ +#ifndef __STRING_H +#define __STRING_H + +#include "sys.h" + +#include + +#include + +#ifdef HAVE_STRERROR_R +#else +/* TODO: port correctly! */ +#define strerror_r( errnum, buf, buflen ) strncpy( buf, strerror( errnum ), buflen ) +#endif + +#endif /* ifndef __STRING_H */ diff --git a/src/port/sys.h b/src/port/sys.h new file mode 100644 index 0000000..65b68e2 --- /dev/null +++ b/src/port/sys.h @@ -0,0 +1,119 @@ +#if defined LINUX +#if OS_MAJOR_VERSION == 2 +#if OS_MINOR_VERSION == 6 +#define _XOPEN_SOURCE 600 +#define HAVE_STDBOOL_H +#define HAVE_STDINT_H +#define HAVE_VSNPRINTF +#define HAVE_SNPRINTF +#define HAVE_VASPRINTF +#define HAVE_ASPRINTF +#define HAVE_STRDUP +#define HAVE_LOCKF +#else + #error unknown platform +#endif /* defined OS_MINOR_VERSION == 6 */ +#else + #error unknown platform +#endif /* defined OS_MAJOR_VERSION == 2 */ +#endif /* defined LINUX */ + +#if defined FREEBSD +#if OS_MAJOR_VERSION == 7 +#if OS_MINOR_VERSION == 0 +#define _XOPEN_SOURCE 600 +#define HAVE_STDBOOL_H +#define HAVE_STDINT_H +#define HAVE_VSNPRINTF +#define HAVE_SNPRINTF +#define HAVE_VASPRINTF +#define HAVE_ASPRINTF +#define HAVE_STRDUP +#define HAVE_LOCKF +#else + #error unknown platform +#endif /* defined OS_MINOR_VERSION == 0 */ +#else +#if OS_MAJOR_VERSION == 6 +#if OS_MINOR_VERSION == 2 +#define _XOPEN_SOURCE 600 +#define HAVE_STDBOOL_H +#define HAVE_STDINT_H +#define HAVE_VSNPRINTF +#define HAVE_SNPRINTF +#define HAVE_STRDUP +#define HAVE_LOCKF +#else + #error unknown platform +#endif /* defined OS_MINOR_VERSION == 2 */ +#else + #error unknown platform +#endif /* defined OS_MAJOR_VERSION == 6 */ +#endif /* defined OS_MAJOR_VERSION == 7 */ +#endif /* defined FREEBSD */ + +#if defined OPENBSD +#if OS_MAJOR_VERSION == 4 +#if OS_MINOR_VERSION >= 2 && OS_MINOR_VERSION <= 3 +#define _XOPEN_SOURCE 600 +#define HAVE_STDBOOL_H +#define HAVE_STDINT_H +#define HAVE_VSNPRINTF +#define HAVE_SNPRINTF +#define HAVE_VASPRINTF +#define HAVE_ASPRINTF +#define HAVE_STRDUP +#define HAVE_LOCKF +#else + #error unknown platform +#endif /* defined OS_MINOR_VERSION >= 2 && OS_MINOR_VERSION <= 3 */ +#else + #error unknown platform +#endif /* defined OS_MAJOR_VERSION == 4 */ +#endif /* defined OPENBSD */ + +#if defined SUNOS +#if OS_MAJOR_VERSION == 5 +#if OS_MINOR_VERSION == 8 +#if !defined __cplusplus +#define _XOPEN_SOURCE 600 +#define __EXTENSIONS__ +#endif +#define HAVE_SNPRINTF +#define HAVE_VSNPRINTF +#define HAVE_LOCKF +#define HAVE_ENUM_BOOL +#define HAVE_LINK_H +#else +#if OS_MINOR_VERSION == 10 +#if !defined __cplusplus +#define _XOPEN_SOURCE 600 +#define __EXTENSIONS__ +#endif +#define HAVE_SNPRINTF +#define HAVE_VSNPRINTF +#define HAVE_LOCKF +#define HAVE_STDBOOL_H +#define HAVE_STDINT_H +#define HAVE_STRERROR_R +#else + #error unknown platform +#endif /* OS_MINOR_VERSION == 10 */ +#endif /* OS_MINOR_VERSION == 8 */ +#else + #error unknown platform +#endif /* OS_MAJOR_VERSION == 5 */ +#endif /* defined SUNOS */ + +#if defined CYGWIN +#if OS_MAJOR_VERSION == 5 +#if OS_MINOR_VERSION == 0 +#define _XOPEN_SOURCE 600 +#define HAVE_ENUM_BOOL +#else + #error unknown platform +#endif /* OS_MINOR_VERSION == 0 */ +#else + #error unknown platform +#endif /* OS_MAJOR_VERSION == 5 */ +#endif /* defined CYGWIN */ diff --git a/src/port/unistd.h b/src/port/unistd.h new file mode 100644 index 0000000..712f851 --- /dev/null +++ b/src/port/unistd.h @@ -0,0 +1,12 @@ +#ifndef __UNISTD_H +#define __UNISTD_H + +#include "sys.h" + +#include + +#if !defined HAVE_LOCKF +#include "port/lockf.h" +#endif + +#endif /* ifndef __UNISTD_H */ diff --git a/src/port/unused.h b/src/port/unused.h new file mode 100644 index 0000000..c7692f6 --- /dev/null +++ b/src/port/unused.h @@ -0,0 +1,8 @@ +#ifndef __UNUSED_H +#define __UNUSED_H + +#include "port/sys.h" + +#define UNUSED( x ) if( 0 && (x) ) { } + +#endif /* ifndef __UNUSED_H */ diff --git a/src/signals.c b/src/signals.c new file mode 100644 index 0000000..02cbb65 --- /dev/null +++ b/src/signals.c @@ -0,0 +1,560 @@ +#include "signals.h" +#include "daemon.h" + +#include "port/string.h" /* for memset */ + +#include /* for getpid, pipe, write, read */ +#include /* for errno */ +#include /* for ssize_t */ +#include /* for FD_XX and select */ + +#include "errors.h" +#include "log.h" + +#include "port/unused.h" + +const char *signal_get_short_name( int sig ) { + switch( sig ) { + case SIGHUP: return "SIGHUP"; + case SIGINT: return "SIGINT"; + case SIGQUIT: return "SIGQUIT"; + case SIGILL: return "SIGILL"; + case SIGTRAP: return "SIGTRAP"; + case SIGABRT: return "SIGABRT"; +#if defined( SIGIOT ) +#if SIGIOT != SIGABRT + case SIGIOT: return "SIGIOT"; +#endif +#endif +#if defined( SIGEMT ) + case SIGEMT: return "SIGEMT"; +#endif + case SIGBUS: return "SIGBUS"; + case SIGFPE: return "SIGFPE"; + case SIGKILL: return "SIGKILL"; + case SIGUSR1: return "SIGUSR1"; + case SIGSEGV: return "SIGSEGV"; + case SIGUSR2: return "SIGUSR2"; + case SIGPIPE: return "SIGPIPE"; + case SIGALRM: return "SIGALRM"; + case SIGTERM: return "SIGTERM"; +#if defined( SIGSTKFLT ) + case SIGSTKFLT: return "SIGSTKFLT"; +#endif +#if defined( SIGCLD ) +#if SIGCLD != SIGCHLD + /* the SysV version */ + case SIGCLD: return "SIGCLD"; +#endif +#endif + /* the POSIX version */ + case SIGCHLD: return "SIGCHLD"; + case SIGCONT: return "SIGCONT"; + case SIGSTOP: return "SIGSTOP"; + case SIGTSTP: return "SIGTSTP"; + case SIGTTIN: return "SIGTTIN"; + case SIGTTOU: return "SIGTTOU"; + case SIGURG: return "SIGURG"; + case SIGXCPU: return "SIGXCPU"; + case SIGXFSZ: return "SIGXFSZ"; + case SIGVTALRM: return "SIGVTALRM"; + case SIGPROF: return "SIGPROF"; +#if defined( SIGWINCH ) + case SIGWINCH: return "SIGWINCH"; +#endif +#if defined( SIGPOLL ) +#if SIGPOLL != SIGIO + /* SysV version of SIGIO */ + case SIGPOLL: return "SIGPOLL"; +#endif +#endif +#if defined( SIGIO ) + case SIGIO: return "SIGIO"; +#endif +#if defined( SIGPWR ) + case SIGPWR: return "SIGPWR"; +#endif +#if defined( SIGSYS ) + case SIGSYS: return "SIGSYS"; +#endif + + default: return ""; + } +} + +const char *signal_get_long_name( int sig ) { + /* we don't trust strsignal, needs _GNU_SOURCE, + * naming comes from bits/signum.h on Linux 2.6 + */ + switch( sig ) { + case SIGHUP: return "Hangup"; + case SIGINT: return "Interrupt"; + case SIGQUIT: return "Quit"; + case SIGILL: return "Illegal instruction"; + case SIGTRAP: return "Trace trap"; + case SIGABRT: return "Abort"; +#if defined( SIGIOT ) +#if SIGIOT != SIGABRT + case SIGIOT: return "IOT trap"; +#endif +#endif +#if defined( SIGEMT ) + case SIGEMT: return "EMT instruction"; +#endif + case SIGBUS: return "BUS error"; + case SIGFPE: return "Floating-point exception"; + case SIGKILL: return "Kill"; + case SIGUSR1: return "User-defined signal 1"; + case SIGSEGV: return "Segmentation violation"; + case SIGUSR2: return "User-defined signal 2"; + case SIGPIPE: return "Broken pipe"; + case SIGALRM: return "Alarm clock"; + case SIGTERM: return "Termination"; +#if defined( SIGSTKFLT ) + case SIGSTKFLT: return "Stack fault"; +#endif +#if defined( SIGCLD ) +#if SIGCLD != SIGCHLD + case SIGCLD: return "Child status has changed"; +#endif +#endif + case SIGCHLD: return "Child status has changed"; + case SIGCONT: return "Continue"; + case SIGSTOP: return "Stop"; + case SIGTSTP: return "Keyboard stop"; + case SIGTTIN: return "Background read from tty"; + case SIGTTOU: return "Background write to tty"; + case SIGURG: return "Urgent condition on socket"; + case SIGXCPU: return "CPU limit exceeded"; + case SIGXFSZ: return "File size limit exceeded"; + case SIGVTALRM: return "Virtual alarm clock"; + case SIGPROF: return "Profiling alarm clock"; +#if defined( SIGWINCH ) + case SIGWINCH: return "Window size change"; +#endif +#if defined( SIGPOLL ) +#if SIGPOLL != SIGIO + case SIGPOLL: return "Pollable event occurred"; +#endif +#endif +#if defined( SIGIO ) + case SIGIO: return "I/O now possible"; +#endif +#if defined( SIGPWR ) + case SIGPWR: return "Power failure restart"; +#endif +#if defined( SIGSYS ) + case SIGSYS: return "Bad system call"; +#endif + + default: return ""; + } +} + +static error_t vsignal_install_ignore( int sig, va_list ap ) { + struct sigaction sa; + char errbuf[1024]; + +install_ignore_again: + memset( &sa, 0, sizeof( struct sigaction ) ); + sa.sa_handler = SIG_IGN; + + if( sigaction( sig, &sa, NULL ) < 0 ) { + (void)strerror_r( errno, errbuf, 1024 ); + LOG( LOG_CRIT, "Can't ignore signal handler for signal '%s' (%s, %d): %s (errno: %d)", + signal_get_long_name( sig ), + signal_get_short_name( sig ), + sig, + errbuf, + errno ); + return ERR_PROGRAMMING; + } + LOG( LOG_DEBUG, "Ignoring signal handler for signal '%s' (%s)", + signal_get_long_name( sig ), + signal_get_short_name( sig ) ); + sig = va_arg( ap, int ); + if( sig > 0 ) goto install_ignore_again; + + return OK; +} + +/* some signals we should not ignore, like SIGPIPE or SIGC(H)LD, because + * results of functions like write and some process data gets lost in + * forked children and waitpid. + */ +static void empty_handler( int sig ) { + UNUSED( sig ); +} + +static error_t vsignal_install_empty( int sig, va_list ap ) { + struct sigaction sa; + char errbuf[1024]; + +install_empty_again: + memset( &sa, 0, sizeof( struct sigaction ) ); + sa.sa_handler = empty_handler; + + if( sigaction( sig, &sa, NULL ) < 0 ) { + (void)strerror_r( errno, errbuf, 1024 ); + LOG( LOG_CRIT, "Can't install empty signal handler for signal '%s' (%s, %d): %s (errno: %d)", + signal_get_long_name( sig ), + signal_get_short_name( sig ), + sig, + errbuf, + errno ); + return ERR_PROGRAMMING; + } + LOG( LOG_DEBUG, "Installed empty signal handler for signal '%s' (%s)", + signal_get_long_name( sig ), + signal_get_short_name( sig ) ); + sig = va_arg( ap, int ); + if( sig > 0 ) goto install_empty_again; + + return OK; +} + +static void fatal_handler( int sig ) { + struct sigaction sa; + + /* reset default behaviour of the signal */ + memset( &sa, 0, sizeof( struct sigaction ) ); + sa.sa_handler = SIG_DFL; + (void)sigaction( sig, &sa, NULL ); + + /* log what happened */ + LOG( LOG_ALERT, "Got signal '%s' (%s)", + signal_get_long_name( sig ), + signal_get_short_name( sig ) ); + + /* we are in one thread when we got the signal, now inform + * all other threads too (see Apache mpm_common.c for this + * trick) + */ + kill( getpid( ), sig ); +} + +static error_t vsignal_install_func( signal_func_t func, int sig, va_list ap ) { + struct sigaction sa; + char errbuf[1024]; + +install_func_again: + memset( &sa, 0, sizeof( struct sigaction ) ); + sa.sa_handler = func; + sa.sa_flags = SA_RESTART; + + if( sigaction( sig, &sa, NULL ) < 0 ) { + (void)strerror_r( errno, errbuf, 1024 ); + LOG( LOG_CRIT, "Can't install signal handler for signal '%s' (%s, %d): %s (errno: %d)", + signal_get_long_name( sig ), + signal_get_short_name( sig ), + sig, + errbuf, + errno ); + return ERR_PROGRAMMING; + } + LOG( LOG_DEBUG, "Installed signal handler for signal '%s' (%s)", + signal_get_long_name( sig ), + signal_get_short_name( sig ) ); + sig = va_arg( ap, int ); + if( sig > 0 ) goto install_func_again; + + return OK; +} + +error_t signal_install_func( signal_func_t func, int sig, ... ) { + va_list ap; + error_t error; + va_start( ap, sig ); + error = vsignal_install_func( func, sig, ap ); + va_end( ap ); + return error; +} + +#define INSTALL_SIGNAL( MODE ) \ +error_t signal_install_##MODE( int sig, ... ) { \ + va_list ap; \ + error_t error; \ + va_start( ap, sig ); \ + error = vsignal_install_##MODE( sig, ap ); \ + va_end( ap ); \ + return error; \ +} + +INSTALL_SIGNAL( ignore ) +INSTALL_SIGNAL( empty ) + +#define INSTALL_SIGNAL_FUNC( MODE, FUNC ) \ +error_t signal_install_##MODE( int sig, ... ) { \ + va_list ap; \ + error_t error; \ + va_start( ap, sig ); \ + error = vsignal_install_func( FUNC, sig, ap ); \ + va_end( ap ); \ + return error; \ +} + +INSTALL_SIGNAL_FUNC( fatal, fatal_handler ) + +int daemon_signal_pipe[2] = { -1, -1 }; + +static void notify_parent_handler( int sig ) { + if( daemon_parent_pipe[1] != -1 ) + (void)write( daemon_parent_pipe[1], &sig, sizeof( int ) ); +} + +static void notify_handler( int sig ) { + if( daemon_signal_pipe[1] != -1 ) + (void)write( daemon_signal_pipe[1], &sig, sizeof( int ) ); +} + +INSTALL_SIGNAL_FUNC( notify, notify_handler ) +INSTALL_SIGNAL_FUNC( notify_parent, notify_parent_handler ) + +error_t signal_initialize( void ) { + int res; + char errbuf[1024]; + + if( daemon_signal_pipe[0] != -1 || + daemon_signal_pipe[1] != -1 ) { + return ERR_INVALID_STATE; + } + + res = pipe( daemon_signal_pipe ); + if( res < 0 ) { + (void)strerror_r( errno, errbuf, 1024 ); + LOG( LOG_EMERG, "Unable to create signal pipe: %s (%d)", + errbuf, errno ); + return ERR_INTERNAL; + } + LOG( LOG_DEBUG, "Created signal pipe (%d,%d)", daemon_signal_pipe[0], daemon_signal_pipe[1] ); + + return OK; +} + +void signal_terminate( void ) { + (void)close( daemon_signal_pipe[0] ); + (void)close( daemon_signal_pipe[1] ); +} + +int signal_suspend( int timeout, error_t *error ) { + struct timeval tv; + fd_set fds; + ssize_t res; + int sig; + char errbuf[1024]; + + if( daemon_signal_pipe[0] == -1 ) { + *error = ERR_INVALID_STATE; + return -1; + } + + tv.tv_sec = timeout; + tv.tv_usec = 0; + + FD_ZERO( &fds ); + FD_SET( daemon_signal_pipe[0], &fds ); + +signal_select_again: + res = select( daemon_signal_pipe[0] + 1, &fds, 0, 0, &tv ); + switch( res ) { + case 1: /* ready on pipe */ + break; + + case 0: /* timeout */ + *error = ERR_TIMEOUT; + return 0; + + case -1: /* error */ + if( errno == EINTR ) goto signal_select_again; + /* gdb no-brainer when pressing Ctrl-C (at least Linux) */ + if( errno == 514 ) goto signal_select_again; + (void)strerror_r( errno, errbuf, 1024 ); + LOG( LOG_EMERG, "Error in select when waiting for signal from pipe: %s (%d)", + errbuf, errno ); + *error = ERR_INTERNAL; + return -1; + } + + res = read( daemon_signal_pipe[0], &sig, sizeof( int ) ); + switch( res ) { + case sizeof( int ): /* got a signal */ + break; + + case -1: /* error */ + (void)strerror_r( errno, errbuf, 1024 ); + LOG( LOG_EMERG, "Error while reading a signal from the pipe: %s (%d)", + errbuf, errno ); + *error = ERR_INTERNAL; + return -1; + + default: /* unexpected result on atomic read */ + LOG( LOG_EMERG, "Unexpected error in read: result is %d", res ); + *error = ERR_PROGRAMMING; + return -1; + } + + *error = OK; + return sig; +} + +error_t signal_install_handlers_parent( void ) { + error_t error; + + /* signals to ignore */ + if( ( error = signal_install_ignore( + SIGPIPE, + SIGTSTP, + SIGTTIN, + SIGTTOU, + SIGURG, + SIGXCPU, /* later: throthling */ + SIGXFSZ, /* later: close connections, reduce channels */ + SIGVTALRM, + SIGPROF, +#if defined( SIGWINCH ) + SIGWINCH, +#endif +#if defined( SIGPOLL ) + SIGPOLL, +#endif +#if defined( SIGIO ) +#if SIGIO != SIGPOLL + SIGIO, +#endif +#endif +#if defined( SIGPWR ) + SIGPWR, +#endif + SIGTERM, + SIGINT, + SIGHUP, + SIGUSR1, + SIGUSR2, + 0 ) ) != OK ) return error; + + /* signals for empty handlers */ + if( ( error = signal_install_empty( +#if defined( SIGCHLD ) && defined( SIGCLD ) +#if SIGCLD != SIGCHLD + SIGCHLD, +#else +#if defined( SIGCHLD ) + SIGCHLD, +#endif +#if defined( SIGCLD ) + SIGCLD, +#endif +#endif +#endif + 0 ) ) != OK ) return error; + + /* fatal signal handlers, make sure the parent can read the + * signal and clean up + */ + if( ( error = signal_install_notify_parent( + SIGILL, + SIGABRT, +#if defined( SIGIOT ) +#if SIGIOT != SIGABRT + SIGIOT, +#endif +#endif + SIGBUS, + SIGFPE, + SIGSEGV, + SIGVTALRM, +#if defined( SIGSTKFLT ) + SIGSTKFLT, +#endif +#if defined( SIGSYS ) + SIGSYS, +#endif + SIGALRM, /* we don't use it, but maybe a plugin */ + 0 ) ) != OK ) return error; + + return OK; +} + + +error_t signal_install_handlers_daemon( void ) { + error_t error; + + /* signals to ignore */ + if( ( error = signal_install_ignore( + SIGPIPE, + SIGTSTP, + SIGTTIN, + SIGTTOU, + SIGURG, + SIGXCPU, /* later: throthling */ + SIGXFSZ, /* later: close connections, reduce channels */ + SIGVTALRM, + SIGPROF, +#if defined( SIGWINCH ) + SIGWINCH, +#endif +#if defined( SIGPOLL ) + SIGPOLL, +#endif +#if defined SIGIO +#if SIGIO != SIGPOLL + SIGIO, +#endif +#endif +#if defined( SIGPWR ) + SIGPWR, +#endif + 0 ) ) != OK ) return error; + + /* signals for empty handlers */ + if( ( error = signal_install_empty( +#if defined( SIGCHLD ) && defined( SIGCLD ) +#if SIGCLD != SIGCHLD + SIGCHLD, +#else +#if defined( SIGCHLD ) + SIGCHLD, +#endif +#if defined( SIGCLD ) + SIGCLD, +#endif +#endif +#endif + 0 ) ) != OK ) return error; + + /* fatal signal handlers, log the exception and continue with + * default behaviour of the system + */ + if( ( error = signal_install_fatal( + SIGILL, + SIGABRT, +#if defined( SIGIOT ) +#if SIGIOT != SIGABRT + SIGIOT, +#endif +#endif + SIGBUS, + SIGFPE, + SIGSEGV, + SIGVTALRM, +#if defined( SIGSTKFLT ) + SIGSTKFLT, +#endif +#if defined( SIGSYS ) + SIGSYS, +#endif + SIGALRM, /* we don't use it, but maybe a plugin */ + 0 ) ) != OK ) return error; + + /* notify the following signals to the main loop */ + if( ( error = signal_install_notify( + SIGTERM, + SIGINT, + SIGHUP, + SIGUSR1, + SIGUSR2, + 0 ) ) != OK ) return error; + + return OK; +} + diff --git a/src/signals.h b/src/signals.h new file mode 100644 index 0000000..33f9dab --- /dev/null +++ b/src/signals.h @@ -0,0 +1,46 @@ +#ifndef __SIGNALS_H +#define __SIGNALS_H + +#include "errors.h" + +#include /* for variable arguments */ +#include /* for signal constants, + sigaction, kill, etc. */ + +#ifdef __cplusplus +extern "C" { +#endif + +const char *signal_get_short_name( int sig ); + +const char *signal_get_long_name( int sig ); + +error_t signal_install_ignore( int sig, ... ); + +error_t signal_install_empty( int sig, ... ); + +typedef void (*signal_func_t)( int ); + +error_t signal_install_func( signal_func_t func, int sig, ... ); + +error_t signal_install_fatal( int sig, ... ); + +error_t signal_install_notify( int sig, ... ); + +error_t signal_install_notify_parent( int sig, ... ); + +error_t signal_initialize( void ); + +int signal_suspend( int timeout, error_t *error ); + +void signal_terminate( void ); + +error_t signal_install_handlers_parent( void ); + +error_t signal_install_handlers_daemon( void ); + +#ifdef __cplusplus +} +#endif + +#endif /* ifndef __SIGNALS_H */ diff --git a/src/testd.c b/src/testd.c new file mode 100644 index 0000000..5138b12 --- /dev/null +++ b/src/testd.c @@ -0,0 +1,149 @@ +#include "port/stdbool.h" /* for bool */ +#include "port/string.h" /* for memset */ + +#include /* for pid_t */ +#include /* for exit, unistd, getuid, getppid */ +#include /* for EXIT_FAILURE */ + +#include "errors.h" /* global error codes */ +#include "cmdline.h" /* for command line and option parsing (gengetopt) */ +#include "log.h" /* logging facility */ +#include "daemon.h" /* Unix daemonizing code */ +#include "signals.h" /* signal supension */ + +#include "port/unused.h" + +#define DEFAULT_CONFIG_FILE "/etc/" CMDLINE_PARSER_PACKAGE ".conf" + +static int parse_options_and_arguments( int argc, char *argv[], struct gengetopt_args_info *args_info ) { + cmdline_parser_init( args_info ); + + if( cmdline_parser2( argc, argv, args_info, 1, 0, 1 ) != 0 ) { + cmdline_parser_free( args_info ); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + +static int test_config( const char *filename ) { + UNUSED( filename ); + return EXIT_SUCCESS; +} + +static int read_config( const char *filename ) { + UNUSED( filename ); + return EXIT_SUCCESS; +} + +int main( int argc, char *argv[] ) { + struct gengetopt_args_info args_info; + error_t error; + daemon_params_t daemon_params; + daemon_p demon = NULL; + int sig = 0; + + if( parse_options_and_arguments( argc, argv, &args_info ) == EXIT_FAILURE ) { + exit( EXIT_FAILURE ); + } + + if( read_config( args_info.config_file_given ? + args_info.config_file_arg : DEFAULT_CONFIG_FILE ) == EXIT_FAILURE ) { + exit( EXIT_FAILURE ); + } + + if( args_info.test_given ) { + cmdline_parser_free( &args_info ); + exit( test_config( args_info.config_file_given ? + args_info.config_file_arg : DEFAULT_CONFIG_FILE ) ); + } + + openlogtostderr( LOG_DEBUG - 1 + (int)args_info.debug_given ); + if( args_info.logfile_given ) + openlogtofile( args_info.logfile_arg, + args_info.logfile_level_given ? + log_str_to_level( args_info.logfile_level_arg ) : LOG_NOTICE ); + + if( !args_info.foreground_given ) { + openlogtosyslog( CMDLINE_PARSER_PACKAGE, + args_info.syslog_facility_given ? + log_str_to_syslog_facility( args_info.syslog_facility_arg ) : LOG_DAEMON, + args_info.syslog_level_given ? + log_str_to_level( args_info.syslog_level_arg ) : LOG_NOTICE ); + + memset( &daemon_params, 0, sizeof( daemon_params ) ); + daemon_params.daemon_name = CMDLINE_PARSER_PACKAGE; + daemon_params.pid_filename = args_info.pidfile_given ? + args_info.pidfile_arg : NULL; + daemon_params.group_name = args_info.group_given ? + args_info.group_arg : NULL; + daemon_params.user_name = args_info.user_given ? + args_info.user_arg : NULL; + + demon = daemon_new( daemon_params, &error ); + if( demon == NULL ) { + cmdline_parser_free( &args_info ); + exit( EXIT_FAILURE ); + } + + if( ( error = daemon_start( demon ) ) != OK ) { + cmdline_parser_free( &args_info ); + daemon_exit( demon ); + } + } else { + if( ( error = signal_initialize( ) ) != OK ) { + cmdline_parser_free( &args_info ); + exit( EXIT_FAILURE ); + } + if( ( error = signal_install_handlers_daemon( ) ) != OK ) { + signal_terminate( ); + cmdline_parser_free( &args_info ); + exit( EXIT_FAILURE ); + } + } + + LOG( LOG_NOTICE, "Started %s daemon", CMDLINE_PARSER_PACKAGE ); + while( ( sig != SIGTERM ) && ( sig != SIGINT ) && ( sig != -1 ) ) { + sig = signal_suspend( 60, &error ); + switch( sig ) { + case 0: /* timeout */ + break; + + case -1: /* internal error */ + break; + + case SIGHUP: + LOG( LOG_NOTICE, "Rereading configuration" ); + break; + + case SIGTERM: + case SIGINT: + LOG( LOG_NOTICE, "Got termination signal, shutting down the daemon" ); + break; + + case SIGUSR1: + break; + + case SIGUSR2: + break; + + default: + LOG( LOG_ERR, "Unexpected signal '%s' (%s, %d)", + signal_get_long_name( sig ), + signal_get_short_name( sig ), + sig ); + } + } + LOG( LOG_NOTICE, "Stopped %s daemon", CMDLINE_PARSER_PACKAGE ); + + if( !args_info.foreground_given ) { + cmdline_parser_free( &args_info ); + daemon_exit( demon ); + } else { + cmdline_parser_free( &args_info ); + signal_terminate( ); + exit( EXIT_SUCCESS ); + } + + exit( EXIT_FAILURE ); +} diff --git a/tests/GNUmakefile b/tests/GNUmakefile new file mode 100644 index 0000000..caf20e7 --- /dev/null +++ b/tests/GNUmakefile @@ -0,0 +1,31 @@ +TOPDIR = .. + +SUBDIRS = + +INCLUDE_DIRS = -I. + +BINS = \ + stdargs_for_signal_functions$(EXE) + +OBJS = + +-include $(TOPDIR)/makefiles/sub.mk + +test: all + @./stdargs_for_signal_functions + +# TODO:still a little bit unrealiable with fakeroot +# @fakeroot $(TOPDIR)/src/testd -d --pidfile /tmp/testd.pid && \ +# sleep 3 && \ +# ls -altr /tmp/testd* && \ +# cat /tmp/testd.pid +# -@ps -alef | grep test | grep -v grep +# @sleep 1 +# @pkill testd +# -@ls -altr /tmp/testd* + +local_all: + +local_clean: + +local_distclean: diff --git a/tests/stdargs_for_signal_functions.c b/tests/stdargs_for_signal_functions.c new file mode 100644 index 0000000..f752630 --- /dev/null +++ b/tests/stdargs_for_signal_functions.c @@ -0,0 +1,33 @@ +#include +#include + +static void vf2( const char *s, int a, va_list ap ) { + int v; + + printf( "ap: %s %d", s, a ); + v = va_arg( ap, int ); + while( v != 0 ) { + printf( "%d ", v ); + v = va_arg( ap, int ); + } + puts( "" ); +} + +static void f2( const char *s, int a, ... ) { + va_list ap; + va_start( ap, a ); + vf2( s, a, ap ); + va_end( ap ); +} + +static void f( int a, ... ) { + va_list ap; + va_start( ap, a ); + vf2( "f", a, ap ); + va_end( ap ); +} + +int main( void ) { + f2( "f2", 1, 2, 3, 4, 0 ); + f( 1, 2, 3, 4, 0 ); +} -- cgit v1.2.3-54-g00ecf