diff options
author | Andreas Baumann <abaumann@yahoo.com> | 2008-08-25 21:36:24 +0200 |
---|---|---|
committer | Andreas Baumann <abaumann@yahoo.com> | 2008-08-25 21:36:24 +0200 |
commit | abbdd96699bd8ba0b1b388a598ddbd249b9d3dd6 (patch) | |
tree | 3d1784d3b7a039b01d627bb67971125f83a135fb | |
download | wolfbones-abbdd96699bd8ba0b1b388a598ddbd249b9d3dd6.tar.gz wolfbones-abbdd96699bd8ba0b1b388a598ddbd249b9d3dd6.tar.bz2 |
checked in initial version from SVN (as SVN is dead!)
44 files changed, 7256 insertions, 0 deletions
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 @@ +<html><head><!-- +--> + + + +<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-9"> +<meta http-equiv="Description" content="Unix Daemon Server Programming"> +<meta http-equiv="Keywords" content="Levent, Karakaž, Karakas, Unix, Daemon, Server, Programming"><title>Unix Daemon Server Programming</title> + +<style type="text/css"> +<!-- +BODY, TD { + background : #FFFFFF; + color : #000000; + font-family : normal arial, helvetica; + font-size: 12px; + text-align: justify; +} +H1 { + font-size : 20px; + text-align : center; +} +H2 { + font-size : 16px; + text-align : left; +} +A:link, A:visited { + color: #0000FF; + text-decoration: none; +} +//--> +</style></head><body> + +<div align="center"> + +<table border="0" cellpadding="0" cellspacing="0" width="600"> + <tbody><tr> + <td> + +<h1>Unix Daemon Server Programming</h1> + +<h2>Introduction</h2> + +<p>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.</p> + +<p>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.</p> + + +<h2>1) Daemonizing (programming to operate in background) [fork]</h2> + +<p>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.</p> + +<pre> i=fork(); + if (i<0) exit(1); /* fork error */ + if (i>0) exit(0); /* parent exits */ + /* child (daemon) continues */ +</pre> + +<h2>2) Process Independency [setsid]</h2> + +<p>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.</p> + +<p>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.</p> + +<pre> setsid() /* obtain a new process group */ +</pre> + +<p>This call will place the server in a new process group and session +and detach its controlling terminal. (setpgrp() is an alternative for +this)</p> + +<h2>3) Inherited Descriptors and Standart I/0 Descriptors [gettablesize,fork,open,close,dup,stdio.h]</h2> + +<p>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.</p> + +<pre> for (i=getdtablesize();i>=0;--i) close(i); /* close all descriptors */ +</pre> + +<p>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).</p> + +<pre> i=open("/dev/null",O_RDWR); /* open stdin */ + dup(i); /* stdout */ + dup(i); /* stderr */ +</pre> + +<p>As Unix assigns descriptors sequentially, fopen call will open stdin and dup calls will provide a copy for stdout and stderr.</p> + +<h2>4) File Creation Mask [umask]</h2> + +<p>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.</p> + +<pre> umask(027); +</pre> + +<p>This will restrict file creation mode to 750 (complement of 027).</p> + +<h2>5) Running Directory [chdir]</h2> + +<p>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.</p> + +<pre> chdir("/servers/"); +</pre> + +<p>The root "/" directory may not be appropriate for every server, it +should be choosen carefully depending on the type of the server.</p> + +<h2>6) Mutual Exclusion and Running a Single Copy [open,lockf,getpid]</h2> + +<p>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'</p> + +<pre> 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 */ +</pre> + +<h2>7) Catching Signals [signal,sys/signal.h]</h2> + +<p>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.</p> + +<pre> signal(SIG_IGN,SIGCHLD); /* child terminate signal */ +</pre> + +<p>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.</p> + +<pre> 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 */ +</pre> + +<p>First we construct a signal handling function and then tie up signals to that function.</p> + +<h2>8) Logging [syslogd,syslog.conf,openlog,syslog,closelog]</h2> + +<p>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.</p> + +<p><b>Redirecting all output to standard I/O : </b>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).</p> + +<pre> # mydaemon 2> error.log +</pre> + +<p>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.</p> +<p><b>Log file method : </b>All messages are logged to files (to different files as needed). There is a sample logging function below.</p> + +<pre> 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"); +</pre> + +<p><b>Log server method : </b>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.</p> + +<pre> openlog("mydaemon",LOG_PID,LOG_DAEMON) + syslog(LOG_INFO, "Connection from host %d", callinghostname); + syslog(LOG_ALERT, "Database Error !"); + closelog(); +</pre> + +<p>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 :</p> + +<dl> +<dd>Message classes : LOG_USER, LOG_DAEMON, LOG_LOCAL0 to LOG_LOCAL7</dd> +<dd>Log options : LOG_PID, LOG_CONS, LOG_PERROR</dd> +<dd>Priority levels : LOG_EMERG, LOG_ALERT, LOG_ERR, LOG_WARNING, LOG_INFO</dd> +</dl> + +<h2>About</h2> + +<p>This text is written by Levent Karakas <a href="mailto:levent%20at%20mektup%20dot%20at"><levent at mektup dot at ></a>. +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 : <a href="http://www.enderunix.org/documents/eng/exampled.c">exampled.c</a>. Hope you find this document useful. We do love Unix.</p> + +<pre> +/* +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 */ + +</pre> + +<p> </p> +<p>Last Update : 16.05.2001</p> +<p> </p> + + + </td> + </tr> +</tbody></table><br> + +</div> + + +</body></html>
\ 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 @@ +<html><head><!-- Title: gatekeeper --><!--SiteCatalyst code version: G.7. Copyright 1997-2004 Omniture, Inc.
+More info available at http://www.omniture.com
+--> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +<script language="JavaScript"> +<!-- +var s_pageName=""; +var s_server=""; +var s_channel=""; +var s_pageType=""; +var s_prop1=""; +var s_prop2=""; +var s_prop3=" | 184406479 | Dependency Management"; +var s_prop4="Dependency Management"; +var s_prop5="Portal | Architecture & Design"; +var s_prop6=""; +var s_prop7="John Graham-Cumming"; +var s_prop8="217.162.109.174 | Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.12) Gecko/20080211 Firefox/2.0.0.12"; +var s_prop9=""; +var s_prop10=""; +var s_prop11=""; +var s_prop12=""; +var s_prop13="http://www.ddj.com/linux-open-source/dhandler.jhtml"; +var s_prop14=""; +var s_prop15=""; +var s_prop16=""; + +/* E-commerce Variables */ +var s_campaign=""; +var s_state=""; +var s_zip=""; +var s_events="event5"; +var s_products=""; +var s_purchaseID=""; +var s_eVar1=""; +var s_eVar2=""; +var s_eVar3=""; +var s_eVar4=""; +var s_eVar5=""; +//--> +</script><title>Dr. Dobb's | Dependency Management | April 1, 2006</title> + + + <meta name="description" content="John explores the irony of Make."> + <meta name="keywords" content="john,explores,irony,make"> + <meta name="headline" content="Dependency Management"> + <meta name="created" content=""> + + +<link rel="Stylesheet" rev="Stylesheet" href="makedepend_files/Layout.css" type="text/css"> +<link rel="Stylesheet" rev="Stylesheet" href="makedepend_files/FontStyles.css" type="text/css"> +<link rel="Stylesheet" rev="Stylesheet" href="makedepend_files/newarticle.css" type="text/css"> +<script src="makedepend_files/popwindow.js"></script> +<script language="javascript"> +<!-- +function jobsopen(){ + document.getElementById("jobscontainer").style.height = ''; + } + +function jobsclose(){ + document.getElementById("jobscontainer").style.height = "41px"; + } + +//--> +</script> +<script language="JavaScript" type="text/javascript"> +var addtoMethod=1; +var AddURL = document.location.href; +var AddTitle = escape(document.title); + +</script> +<script language="JavaScript" src="makedepend_files/add2.js" type="text/javascript"></script></head><body topmargin="0" leftmargin="0" bgcolor="#ffffff" link="#006699" marginheight="0" marginwidth="0" vlink="#006699"><script type="text/javascript" src="makedepend_files/al.html"></script><script type="text/javascript" src="makedepend_files/al_002.html"></script><script type="text/javascript" src="makedepend_files/spit.js"></script><script type="text/javascript" src="makedepend_files/chunks_002.js"></script><script type="text/javascript" src="makedepend_files/chunks_003.js"></script><script type="text/javascript" src="makedepend_files/chunks.js"></script><script type="text/javascript" src="makedepend_files/chunks_004.js"></script><script type="text/javascript" src="makedepend_files/func_200802221208.js"></script><script type="text/javascript" src="makedepend_files/door.js"></script><script type="text/javascript" src="makedepend_files/tsc.js"></script> +<table cellpadding="0" cellspacing="0"> +<tbody><tr> +<td><script> var ckRef=document.referrer; if(ckRef && ckRef.indexOf('/as5/redirect/')==-1 || !ckRef) { document.write('<script language="JavaScript" src="http://i.cmpnet.com/shared/omniture/s_code_remote.js"><\/script>'); document.close(); }</script><script language="JavaScript" src="makedepend_files/s_code_remote.js"></script><img src="makedepend_files/s92783574014196.gif" name="s_i_cmpglobalvista" alt="" border="0" height="1" width="1"></td> +</tr> +</tbody></table> +<!-- End SiteCatalyst code version: G.7. --> + + + + + + + <!-- http://as.cmpnet.com/html.ng/pagepos=fullpage&affiliate=ddj&site=sdmg&country=switzerland&server=atg&target=/linux-open-source/184406479 --> +<a target="_parent" href="http://as.cmpnet.com/event.ng/Type=click&FlightID=75640&AdID=125688&TargetID=2878&Segments=3108,3448,4875,11070,12139&Targets=2625,2878,8607&Values=34,46,51,63,77,87,91,102,140,442,656,944,945,975,1311,1603,1716,1765,1767,1785,1925,1970,2179,2299,2310,2326,2352,2678,2735,2761,2767,2862,2942,3052,3082,3216,3392,3890,3904,4080,4799,6193,6293,6332,6392,6393,6422&RawValues=IP,66.77.24.210,&Redirect=http://www.cmp.com"><img src="makedepend_files/iopsblank.gif" alt="" border="0" height="1" width="1"></a><img src="makedepend_files/TypecountClientType2AdID125688FlightID75640TargetID2878S_004.gif" border="0" height="1" width="1"> + + + + +<!-- CMP LOGO and AD BANNER AT TOP --> +<div id="TopAdContainer"> + <img src="makedepend_files/logo_ThinkServices.gif" alt="" align="left" border="0" height="57" hspace="15" vspace="0" width="125"> + + + + <!-- http://as.cmpnet.com/html.ng/pagepos=top&affiliate=ddj&site=sdmg&country=switzerland&server=atg&target=/linux-open-source/184406479 --> +<a target="_parent" href="http://as.cmpnet.com/event.ng/Type=click&FlightID=114710&AdID=188279&TargetID=1251&Segments=1551,3108,3317,3382,3448,4653,4875,5470,5750,5766,6111,6877,7717,10552&Targets=1251,2625,2878,4879&Values=34,46,51,63,77,87,91,102,140,203,442,656,944,945,975,1311,1603,1716,1765,1767,1785,1925,1970,2179,2299,2310,2326,2352,2678,2735,2761,2767,2862,2942,3052,3082,3392,3890,3904,4080,4799,6193,6293,6332,6392,6393,6422&RawValues=IP,66.77.24.210,&Redirect=http://www.realsoftware.com"><img src="makedepend_files/REAL_Software_Banner.gif" alt="" border="0" height="90" width="728"></a><img src="makedepend_files/TypecountClientType2AdID188279FlightID114710TargetID1251Site.gif" border="0" height="1" width="1"> + + +</div> +<!-- /CMP LOGO and AD BANNER AT TOP --> +<!-- LOGO and USER/PORTAL NAV and SEARCH --> +<table class="elfixo" border="0" cellpadding="0" cellspacing="0" width="990"> + <tbody><tr> + <td rowspan="2" bgcolor="#000000" width="1"><img src="makedepend_files/blank.gif" alt="" border="0" height="1" width="1"></td> + <td colspan="2" bgcolor="#000000" width="362"><img src="makedepend_files/blank.gif" alt="" border="0" height="1" width="362"></td> + + <td bgcolor="#000000" width="397"><img src="makedepend_files/blank.gif" alt="" border="0" height="1" width="397"></td> + <td rowspan="2" bgcolor="#000000" width="1"><img src="makedepend_files/blank.gif" alt="" border="0" height="1" width="1"></td> + </tr> + <tr> + <td align="left" bgcolor="#349f2c" valign="middle" width="362"> + <a href="http://www.ddj.com/;jsessionid=4CBKBE5CTME2YQSNDLOSKH0CJUNN2JVN"><img src="makedepend_files/logo_ddj.gif" alt="" border="0" hspace="3" vspace="5"></a></td> + <td align="left" bgcolor="#349f2c" valign="top" width="228"><span class="fphead">FOCAL POINTS: Sponsored Links</span><br> + + + + + <!-- http://as.cmpnet.com/html.ng/pagepos=focallink1&affiliate=ddj&site=sdmg&country=switzerland&server=atg&target=/linux-open-source/184406479 --> +<a class="fp" href="http://as.cmpnet.com/event.ng/Type=click&FlightID=110047&AdID=182226&TargetID=9101&Segments=3108,3448,4875,12765&Targets=2625,2878,9101&Values=34,46,51,63,77,87,91,102,140,442,656,897,944,945,975,1311,1603,1716,1765,1767,1785,1925,1970,2179,2299,2310,2326,2352,2678,2735,2761,2767,2862,2942,3052,3082,3392,3890,3904,4080,4799,6193,6293,6332,6392,6393,6422&RawValues=&Redirect=http://www.ddj.com/focal/msoffice-oba"> +Focus on Office Business Applications (OBA) +</a> +<br><img src="makedepend_files/TypecountClientType2AdID182226FlightID110047TargetID9101Site.gif" border="0" height="1" width="1"> + + + </td> + <td align="right" bgcolor="#349f2c" valign="middle"><!-- SEARCH --> + <form action="/TechSearch/searchResults.jhtml;jsessionid=4CBKBE5CTME2YQSNDLOSKH0CJUNN2JVN"> + <input name="queryText" value="" style="width: 290px; height: 20px;" size="11" class="searchInput" onfocus="this.value=''" type="text"> + <input src="makedepend_files/search.gif" alt="" align="absbottom" hspace="3" type="image" vspace="0"> + </form> + <!-- /SEARCH --> + + <a style="font-family: verdana,sans-serif bold; font-style: normal; font-variant: normal; font-weight: normal; font-size: 10px; line-height: normal; font-size-adjust: none; font-stretch: normal; color: rgb(255, 194, 14);" href="http://www.ddj.com/TechSearch/searchResults.jhtml;jsessionid=4CBKBE5CTME2YQSNDLOSKH0CJUNN2JVN">Site Archive (Complete)</a></td> + </tr> + <!-- /LOGO, and SEARCH --> + <tr> + <td colspan="5" bgcolor="#000000"><img src="makedepend_files/blank.gif" alt="" border="0" height="1" width="250"></td> + </tr> + <tr> + + +<td bgcolor="#000000"></td> +<td colspan="3" bgcolor="#ffb305"> + <div class="portalNAVPad"> + <span class="noLINE"> + <a href="http://www.ddj.com/about.html;jsessionid=4CBKBE5CTME2YQSNDLOSKH0CJUNN2JVN" class="portalNAV">ABOUT US</a> | + <a href="http://www.ddj.com/contact.html;jsessionid=4CBKBE5CTME2YQSNDLOSKH0CJUNN2JVN" class="portalNAV">CONTACT</a> | + <a href="http://www.ddj.com/advertise/;jsessionid=4CBKBE5CTME2YQSNDLOSKH0CJUNN2JVN" class="portalNAV">ADVERTISE</a> | + <a href="http://www.ddj.com/subscribe/;jsessionid=4CBKBE5CTME2YQSNDLOSKH0CJUNN2JVN" class="portalNAV">SUBSCRIBE</a> | + <!-- <a href="/pubs/" CLASS=portalNAV>ARCHIVES</a> | --> + <a href="http://www.ddj.com/code/;jsessionid=4CBKBE5CTME2YQSNDLOSKH0CJUNN2JVN" class="portalNAV">SOURCE CODE</a> | + <a href="http://www.ddj.com/currentIssuePage.html;jsessionid=4CBKBE5CTME2YQSNDLOSKH0CJUNN2JVN" class="portalNAV">CURRENT PRINT ISSUE</a> | + <!-- <a href="`request.getParameter("jiveDomain")+"/jive3/index.jspa"`" CLASS=portalNAV>
+ <param name="categoryID" value="param:ddjCategoryID">FORUMS
+ </a>| --> + <a href="http://www.ddj.com/newsletters/;jsessionid=4CBKBE5CTME2YQSNDLOSKH0CJUNN2JVN" class="portalNAV">NEWSLETTERS</a> + | + <a href="http://www.ddj.com/resources/;jsessionid=4CBKBE5CTME2YQSNDLOSKH0CJUNN2JVN" class="portalNAV">RESOURCES</a> + | + <a href="http://www.ddj.com/blog/;jsessionid=4CBKBE5CTME2YQSNDLOSKH0CJUNN2JVN" class="portalNAV">BLOGS</a> + | + <a href="http://www.ddj.com/mediaCenter/;jsessionid=4CBKBE5CTME2YQSNDLOSKH0CJUNN2JVN" class="portalNAV">PODCASTS</a> + | + <a href="http://www.techcareers.com/?affiliate=ddj" class="portalNAV">CAREERS</a> + </span> + </div> +</td> +<td bgcolor="#000000"></td> + </tr> + <tr> + <td colspan="5" bgcolor="#000000"><img src="makedepend_files/blank.gif" alt="" border="0" height="1" width="250"></td> + </tr> +</tbody></table> +<div style="height: 14px; width: 990px;"> + + + + + <!-- http://as.cmpnet.com/html.ng/pagepos=txtlink20&affiliate=ddj&site=sdmg&country=switzerland&server=atg&target=/linux-open-source/184406479 --> +<a target="_parent" href="http://as.cmpnet.com/event.ng/Type=click&FlightID=75640&AdID=125688&TargetID=2878&Segments=3108,3448,4875,12180&Targets=2625,2878,8689&Values=34,46,51,63,77,87,91,102,140,442,656,944,945,975,1311,1603,1646,1716,1765,1767,1785,1925,1970,2179,2299,2310,2326,2352,2678,2735,2761,2767,2862,2942,3052,3082,3392,3890,3904,4080,4799,6193,6293,6332,6392,6393,6422&RawValues=IP,66.77.24.210,&Redirect=http://www.cmp.com"><img src="makedepend_files/iopsblank.gif" alt="" border="0" height="1" width="1"></a><img src="makedepend_files/TypecountClientType2AdID125688FlightID75640TargetID2878S_002.gif" border="0" height="1" width="1"> + + +</div> + <table class="elfixo" border="0" cellpadding="0" cellspacing="0" width="900"> + + <tbody><tr> + <td valign="top"> + <!-- LEFT COL --> + + + + + + + + + + + + + + + + + +<script language="Javascript"> + function launcher(art_id,urlPrefix,siteUrl) { + uri = "/article/emailBox.jhtml?articleID=" +art_id+"&urlPrefix="+ urlPrefix+"&host_url="+siteUrl; + window.open(uri,"","toolbar=no,scrollbars=auto,location=no,status=no,width=480,height=415,resizable=1"); + } +</script> + + + + + + + + + + + +<div class="LColMargin"> + + <a href="http://www.ddj.com/linux-open-source/;jsessionid=4CBKBE5CTME2YQSNDLOSKH0CJUNN2JVN"><img src="makedepend_files/header_opensourcen.gif" alt="Open Source" border="0" hspace="0" vspace="0"></a> + <img src="makedepend_files/rule.gif" align="left" border="0" height="1" hspace="0" vspace="0" width="405"><br> + +</div> + +<div class="articlepadding"> + + + + + + + + + + + + + + + + + + + + + + + + <table class="elfixo" align="right" border="0" cellpadding="0" cellspacing="0" width="160"> + <tbody><tr> + <td rowspan="5" width="10"><img src="makedepend_files/blank.gif" border="0" height="1" width="10"></td> + <td width="75"><img src="makedepend_files/blank.gif" border="0" height="1" width="75"></td> + <td width="75"><img src="makedepend_files/blank.gif" border="0" height="1" width="75"></td> + </tr> + <tr><td valign="top"> + + <img src="makedepend_files/redsquare.gif" align="absmiddle" border="0" height="12" hspace="0" vspace="0" width="15"><a href="javascript:launcher(184406479,'/linux-open-source/','www.ddj.com')" title="Send As Email">Email</a><br> <!--
+ <img src="http://i.cmpnet.com/ddj/redsquare.gif" width="15" height="12" hspace="0" vspace="0" border="0" align="absmiddle"><a href="#articleComments">Discuss</a>
+ --> + </td> + <td valign="top"> + <img src="makedepend_files/redsquare.gif" align="absmiddle" border="0" height="12" hspace="0" vspace="0" width="15"><a href="http://www.ddj.com/article/printableArticle.jhtml;jsessionid=4CBKBE5CTME2YQSNDLOSKH0CJUNN2JVN?articleID=184406479&dept_url=/linux-open-source/">Print</a><br> + <img src="makedepend_files/redsquare.gif" align="absmiddle" border="0" height="12" hspace="0" vspace="0" width="15"><a href="http://www.cmpreprints.com/faxback.jhtml" target="_new">Reprint</a> + </td></tr> + <tr> + <td colspan="2" style="padding: 7px 0pt 0pt;" height="7" valign="middle"><div align="center"><img src="makedepend_files/dotted_line3.gif" border="0" height="1" hspace="0" vspace="0" width="140"><br><span class="add2header">add to:</span></div></td> + </tr> + <tr> + <td valign="top"><img src="makedepend_files/redsquare.gif" align="absmiddle" border="0" height="12" hspace="0" vspace="0" width="15"><span class="add2ddj" title="Add this page to Delicious" onclick="addto(2)"><u>Del.icio.us</u></span><br> + <img src="makedepend_files/redsquare.gif" align="absmiddle" border="0" height="12" hspace="0" vspace="0" width="15"><span class="add2ddj" title="Add this page to Digg" onclick="addto(3)"><u>Digg</u></span><br> + <img src="makedepend_files/redsquare.gif" align="absmiddle" border="0" height="12" hspace="0" vspace="0" width="15"><span class="add2ddj" title="Add this page to Google" onclick="addto(5)"><u>Google</u></span><br> + <img src="makedepend_files/redsquare.gif" align="absmiddle" border="0" height="12" hspace="0" vspace="0" width="15"><span class="add2ddj" title="Add this page to Spurl" onclick="addto(8)"><u>Spurl</u></span> + </td> + <td valign="top"> <img src="makedepend_files/redsquare.gif" align="absmiddle" border="0" height="12" hspace="0" vspace="0" width="15"><span class="add2ddj" title="Add this page to Slashdot" onclick="addto(6)"><u>Slashdot</u></span><br> + <img src="makedepend_files/redsquare.gif" align="absmiddle" border="0" height="12" hspace="0" vspace="0" width="15"><span class="add2ddj" title="Add this page to Yahoo! MyWeb" onclick="addto(7)"><u>Y! MyWeb</u></span><br> + <img src="makedepend_files/redsquare.gif" align="absmiddle" border="0" height="12" hspace="0" vspace="0" width="15"><span class="add2ddj" title="Add this page to Blink" onclick="addto(1)"><u>Blink</u></span><br> + <img src="makedepend_files/redsquare.gif" align="absmiddle" border="0" height="12" hspace="0" vspace="0" width="15"><span class="add2ddj" title="Add this page to Furl" onclick="addto(4)"><u>Furl</u></span> + </td> + </tr> + + + + + + + + + + + + + + + </tbody></table> + <div class="Bspace4"> + <em>April 01, 2006</em><br> + + <h5>Dependency Management</h5> + <h2>The irony of Make</h2> + </div> + + + + + + + <div class="Bspace15"><em>John Graham-Cumming</em></div> + + + + + + + + + <!-- teaser --> + <div class="Bspace15"><span class="greenBlurb">John explores the irony of Make.</span></div> + <!-- end teaser --> + + + + +<p> +<i>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.</i> +</p><p> +</p><hr> +<p> + +</p><p> + +</p><p> + +</p><p> +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.</p> +<p> + +</p><p> +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 <a itxtdid="4050710" target="_blank" href="#" style="border-bottom: medium none; font-weight: bold; text-decoration: none; padding-bottom: 0px; color: darkblue; background-color: transparent; cursor: pointer;" classname="iAs" class="iAs"><nobr>files<img style="border: 0pt none ; margin: 0pt; padding: 0pt; height: 10px; width: 10px; position: relative; top: 1px; left: 1px; float: none; display: inline;" src="makedepend_files/3.gif"></nobr></a> with its familiar<i> target : prerequisite1 prerequisite2... </i>syntax.</p> +<p> + +</p><p> +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:</p> +<p> +</p><blockquote> +foo.o : foo.c header.h<br> +<p> +</p></blockquote> +<p> + +</p><p> +has target <i>foo.o</i> and prerequisites <i>foo.c</i> and <i>header.h</i>.</p> +<p> + +</p><p> +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. </p> +<p> + +</p><p> +Look at this GNU Make example (I use GNU Make throughout this article +because of its wide platform coverage and large set of features): </p> +<p> +</p><blockquote> +foo.o: foo.c header.h system.h<br> + @echo Compiling $@ from $<...<br> +<p> +</p></blockquote> +<p> + +</p><p> +which outputs:</p> +<p> +</p><blockquote> +Compiling foo.o from foo.c...<br> +<p> +</p></blockquote> +<p> + +</p><p> +Here, <i>foo.o</i> is built if <i>foo.c</i>, <i>header.h</i>, or <i>system.h</i> change, but the rule also states that <i>foo.o</i> is made from <i>foo.c</i> (in GNU Make terms, the target of the rule to the left side of the ":" is written <i>$@</i> and the first prerequisite is <i>$<</i>). If the example were written like this:</p> +<p> +</p><blockquote> +foo.o: foo.c<br> +foo.o: header.h system.h<br> + @echo Compiling $@ from $<...<br> +<p> +</p></blockquote> +<p> + +</p><p> +the output would be:</p> +<p> +</p><blockquote> +Compiling foo.o from header.h...<br> +<p> +</p></blockquote> +<p> + +</p><p> +which is clearly wrong. If you want to continue exploring Make's idiosyncrasies with <i>$<</i>, play around with this Makefile:</p> +<p> +</p><blockquote> +foo.o: header.h<br> +foo.o: system.h<br> +foo.o: foo.c<br> +foo.o:<br> + @echo Compiling $@ from $<...<br> +<p> +</p></blockquote> +<p> + +</p><p> +and try permuting the first three lines to see how Make's interpretation of the Makefile changes.</p> +<p> + +</p><p> +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:</p> +<p> +</p><blockquote> +.PHONY: all<br> + all: foo.o bar.o baz.o<br> +<br> + foo.o: foo.c foo.h common.h header.h<br> + bar.o: bar.c bar.h common.h header.h ba.h<br> + baz.o: baz.c baz.h common.h header.h ba.h<br> +<p> +</p></blockquote> +<p> + +</p><p> +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.</p> +<p> + +</p><p> +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.</p> +<p> + +</p><p> +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 <i>#include </i>statements, follows the <i>#include</i>s, and builds the dependency lines for you. A basic way of incorporating makedepend in a project is a special <i>depend</i> target:</p> +<p> +</p><blockquote> +.PHONY: all<br> + all: foo.o bar.o baz.o<br> +<br> + SRCS = foo.c bar.c baz.c<br> +<br> + DEPENDS = dependencies.mk<br> + .PHONY: depend<br> + depend:<br> + @makedepend -f - $(SRCS) > $(DEPENDS)<br> + -include $(DEPENDS)<br> +<p> +</p></blockquote> +<p> + +</p><p> +Doing <i>makedepend </i>with this Makefile causes the depend rule to execute, which runs makedepend on the sources (defined in the <i>SRCS</i> variable) and outputs the dependency lines to dependencies.mk (defined by the <i>DEPENDS</i> variable).</p> +<p> + +</p><p> +The Makefile includes the dependencies lines in its final line. dependencies.mk looks like this:</p> +<p> +</p><blockquote> +# DO NOT DELETE<br> +<br> +foo.o: foo.h header.h common.h<br> +bar.o: bar.h header.h common.h ba.h<br> +baz.o: baz.h header.h common.h ba.h<br> +<p> +</p></blockquote> +<p> + +</p><p> +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<i> </i>style: </p> +<p> + +</p><p> +</p><ul> + <li>Running makedepend can be slow, as every source file has to be searched even if there are no changes.</li> + <li>It's a manual step. Before every make, users have to do <i>makedepend</i> to ensure that the dependencies are correct. </li> +</ul> +<p> + +</p><p> + +</p><p> +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: </p> +<p> +</p><blockquote> +.PHONY: all<br> + all: foo.o bar.o baz.o<br> + SRCS = foo.c bar.c baz.c<br> + %.d : %.c<br> + @makedepend -f - $< | sed 's,\($*\.o\)[ :]*,\1<br> + $@ : ,g' > $@<br> + -include $(SRCS:.c=.d)<br> +<p> +</p></blockquote> +<p> + +</p><p> +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:</p> +<p> +</p><blockquote> +# DO NOT DELETE<br> +foo.o foo.d : foo.h header.h common.h<br> +<p> +</p></blockquote> +<p> + +</p><p> +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 (<i>%.d : %.c</i> +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 <i>sed</i> magic.</p> +<p> + +</p><p> +The final line of the Makefile includes all the .d files: The <i>$(SRCS:.c=.d)</i> macro transforms the list of sources in the <i>SRCS</i> variable by changing the extension from .c to .d. The <i>include</i> +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.</p> +<p> + +</p><p> +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:</p> +<p> +</p><blockquote> +No rule to make target 'header.h', <br> + needed by 'foo.d'.<br> +<p> +</p></blockquote> +<p> + +</p><p> +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 <i>$(wildcard)</i> +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:</p> +<p> +</p><blockquote> +# DO NOT DELETE<br> +foo.d : <br> + $(wildcard foo.h header.h common.h)<br> +foo.o : foo.h header.h common.h<br> +<p> +</p></blockquote> +<p> + +</p><p> +And here's the updated Makefile with a new invocation of <i>makedepend</i>, followed by a <i>sed</i> line that creates the modified .d file:</p> +<p> +</p><blockquote> +.PHONY: all<br> + all: foo.o bar.o baz.o<br> + SRCS = foo.c bar.c baz.c<br> + %.d : %.c<br> + @makedepend -f - $< | sed 's,<br> + \($*\.o\)[ :]*\(.*\),<br> + $@ : $$\(wildcard \2\)\n\1 : \2,g' > $@<br> + -include $(SRCS:.c=.d)<br> +<p> +</p></blockquote> +<p> + +</p><p> +Now removing a header file doesn't break the Make: When foo.d is parsed, the dependency line for foo.d is passed through <i>$(wildcard)</i>. When there are no globbing symbols such as "*" or "?" in the filename, <i>$(wildcard) </i>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: </p> +<p> +</p><blockquote> +foo.d : foo.h common.h<br> +<p> +</p></blockquote> +<p> + +</p><p> +and the Make would work correctly. This example Makefile now works when .c files are added (users just update <i>SRCS</i> and the new .d file is created automatically), when .c files are removed (users update <i>SRCS</i> +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 <i>$(wildcard) </i>hides the deletion and the .d file is regenerated.</p> +<p> + +</p><p> +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 <i>makedepend</i> occur at the same time as the compilation:</p> +<p> +</p><blockquote> +.PHONY: all<br> + all: foo.o bar.o baz.o<br> + SRCS = foo.c bar.c baz.c<br> + %.o : %.c<br> + @makedepend -f - $< | <br> + sed 's,\($*\.o\)[ :]*\(.*\),<br> + $@ : $$\(wildcard \2\)\n\1 : \2,g' > $*.d<br> + @$(COMPILE.c) -o $@ $<<br> + -include $(SRCS:.c=.d)<br> +<p> +</p></blockquote> +<p> + +</p><p> +This rule makes use of <i>$*</i>, another GNU Make variable. <i>$*</i> is the part of the pattern <i>%.c</i> that matches the %. If this rule is building foo.o from foo.c, then <i>$*</i> is just <i>foo</i>. <i>$*</i> +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.</p> +<p> + +</p><p> +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 <i>$(wildcard) </i>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 <i>Managing Projects with GNU Make </i>(O'Reilly & Associates, 2004). </p> +<p> + +</p><p> +Finally, it's possible to omit makedepend altogether if you are using the GNU GCC compiler. It has a <i>-MD</i> option that does the work of makedepend at the same time as the compilation:</p> +<p> +</p><blockquote> +.PHONY: all<br> + all: foo.o bar.o baz.o<br> + SRCS = foo.c bar.c baz.c<br> + %.o : %.c<br> + @$(COMPILE.c) -MD -o $@ $<<br> + @sed -i 's,\($*\.o\)[ :]*\(.*\),<br> + $@ : $$\(wildcard<br> + \2\)\n\1 : \2,g' $*.d<br> + -include $(SRCS:.c=.d)<br> +<p> +</p></blockquote> +<p> + +</p><p> +For example, the compilation step for foo.o will create foo.d from foo.c and then <i>sed</i> is run on the foo.d to add the extra line for foo.d containing the <i>$(wildcard)</i>.</p> +<p> + +</p><p> +The use of GCC <i>-MD</i> is an example of creating +dependencies without using makedepend. There are a number of other +possibilities. For example, GCC has <i>-M </i>and <i>-MM</i> options that just output dependency information (<i>-M</i> outputs all dependencies and <i>-MM</i> omits the system headers because they are unlikely to change).</p> +<p> + +</p><p> +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 <i>/showincludes </i>option to get include information and build dependency information.</p> +<p> + +</p><p> +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 <i>-D</i> defines so that it builds the right dependency information.</p> +<p> + +</p><p> +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.</p> +<p> + +</p><p> + +<b>DDJ</b></p> +<p> + +</p><p> + +</p><div class="Bspace15"> + <em><a href="http://www.ddj.com/authors/authorInfo.jhtml;jsessionid=4CBKBE5CTME2YQSNDLOSKH0CJUNN2JVN?authorID="><strong></strong></a> <a href="mailto:"><strong></strong></a></em> + </div> + + + <center> + + + + + + + + + + + + + + + + + + + + + </center> + + + + +<!-- ARTICLE COMMENTS--> +<!--
+<a name="articleComments"></a>
+<br>
+<droplet src="/article/articleComments.jhtml">
+<param name="articleTitle" value="param:element.headline">
+<param name="articleID" value="param:articleID">
+<param name="commentsOpen" value="true">
+</droplet>
+--> +<!-- AROUND THE WEB AND TOP 5 ARTICLES --> +<table class="elfixo" border="0" cellpadding="0" cellspacing="0" width="467"> +<tbody><tr> + <td width="229"><img src="makedepend_files/blank.gif" border="0" height="1" width="229"></td> + <td width="9"><img src="makedepend_files/blank.gif" border="0" height="1" width="9"></td> + <td width="229"><img src="makedepend_files/blank.gif" border="0" height="1" width="229"></td> +</tr> +<tr> + <td valign="top"> + <!-- AROUND THE WEB --> + <!-- AROUND THE WEB --> + <div id="GreenContainerBoxSM2"> + <div id="GreenBARSM2"><span class="BARTitles">RELATED ARTICLES</span></div> + + + + + + + + +<div class="PaddingStyle2"> + + + + + + + + + + + <div class="Bspace5"> + <a href="http://www.ddj.com/206902785;jsessionid=4CBKBE5CTME2YQSNDLOSKH0CJUNN2JVN"> + Wireless Emergency Communications Tests Looking Good</a> + </div> + + + + + + + + + + <div class="Bspace5"> + <a href="http://www.ddj.com/security/206902613;jsessionid=4CBKBE5CTME2YQSNDLOSKH0CJUNN2JVN"> + The Silverlight 2.0 Security Model</a> + </div> + + + + + + + + + + <div class="Bspace5"> + <a href="http://www.ddj.com/mobile/206901832;jsessionid=4CBKBE5CTME2YQSNDLOSKH0CJUNN2JVN"> + Nokia to Add Silverlight Support</a> + </div> + + + + + + + + + + <div class="Bspace5"> + <a href="http://www.ddj.com/embedded/206901624;jsessionid=4CBKBE5CTME2YQSNDLOSKH0CJUNN2JVN"> + Texture for Haptic Interfaces</a> + </div> + + + + + + + + + + <div class="Bspace5"> + <a href="http://www.ddj.com/hpc-high-performance-computing/206901618;jsessionid=4CBKBE5CTME2YQSNDLOSKH0CJUNN2JVN"> + Guibas Receives ACM/AAAI Award for Algorithm Development</a> + </div> + + +</div> + </div> + </td> + <td width="9"><img src="makedepend_files/blank.gif" alt="" border="0" height="1" width="9"></td> + <td valign="top"> + <!-- TOP 5 ARTICLES --> + <div id="GreenContainerBoxSM2"><div id="GreenBARSM2"><span class="BARTitles">TOP 5 ARTICLES</span></div> + <div class="PaddingStyle2"> + + + + + + + + + + +<div class="PaddingStyle2"> + + + + + + + + + + + + + + + + + + + + + + <div class="Bspace5"> + <a href="http://www.ddj.com/cpp/204202899;jsessionid=4CBKBE5CTME2YQSNDLOSKH0CJUNN2JVN"> + Building Your Own Plugin Framework: Part 1 + </a></div> + + + + + + + + + + + + + + + + + + + + + + + <div class="Bspace5"> + <a href="http://www.ddj.com/cpp/206503957;jsessionid=4CBKBE5CTME2YQSNDLOSKH0CJUNN2JVN"> + Building Your Own Plugin Framework: Part 5 + </a></div> + + + + + + + + + + + + + + + <div class="Bspace5"> + <a href="http://www.ddj.com/cpp/204702751;jsessionid=4CBKBE5CTME2YQSNDLOSKH0CJUNN2JVN"> + Building Your Own Plugin Framework: Part 2 + </a></div> + + + + + + + + + + + + + + + <div class="Bspace5"> + <a href="http://www.ddj.com/architect/186100398;jsessionid=4CBKBE5CTME2YQSNDLOSKH0CJUNN2JVN"> + Maven: Building Complex Systems + </a></div> + + + + + + + + + + + + + + + <div class="Bspace5"> + <a href="http://www.ddj.com/database/202802959;jsessionid=4CBKBE5CTME2YQSNDLOSKH0CJUNN2JVN"> + Query Anything with SQLite + </a></div> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +</div> + + </div> + </div></td> +</tr> +</tbody></table> +</div> + + + </td> + <td width="10"><img src="makedepend_files/blank.gif" alt="" border="0" height="1" width="10"></td> + <td valign="top"> + <!-- RIGHT COL --> + + + + +<!-- RIGHT COL --> + +<!-- ARTICLE contaier table for media center and departments --> +<table class="elfixo" border="0" cellpadding="0" cellspacing="0" width="575"> +<tbody><tr> <td rowspan="2" valign="top" width="400"> + +<!-- YET TO RECEIVE AD PARAMS--> + + <div class="clear"> + + + + <!-- http://as.cmpnet.com/html.ng/pagepos=sponsbox&affiliate=ddj&site=sdmg&country=switzerland&server=atg&target=/linux-open-source/184406479 --> +<div id="SponsorBox"> +<div id="SponsorBar"> + +<span class="miniBARTitles">LIFE 2.0 SUMMIT SPRING 2008</span> +</div> +<div class="SPONSORPad"> +Join us on the CMP Region for Life 2.0 Summit Spring, a virtual conference, happening in Second Life.<a target="_blank" href="http://as.cmpnet.com/event.ng/Type=click&FlightID=113304&AdID=186384&TargetID=9027&Segments=3108,3448,4875,7711,7716,12683&Targets=2625,2878,9027&Values=34,46,51,63,77,87,91,102,140,442,656,944,945,975,1311,1603,1716,1765,1767,1785,1925,1970,2179,2299,2310,2326,2352,2678,2735,2761,2767,2862,2942,3052,3082,3392,3544,3890,3904,4080,4799,6193,6293,6332,6392,6393,6422&RawValues=&Redirect=http://www.life20.net/register.php?event=l20spring08"> Register for your COMPLIMENTARY PASS today!</a></div> +</div><img src="makedepend_files/TypecountClientType2AdID186384FlightID113304TargetID9027Site.gif" border="0" height="1" width="1"> + + </div> + + +<!------------------------DDJ UNIVERSITY ---------------> +<!--
+<div id="SponsorBar" style="background-color: blue;color: white;">
+<span class="miniBARTitles">DR. DOBB'S UNIVERSITY</span>
+</div>
+<div style="width: 100%; padding: 5px 0px 5px 5px;border: 1px solid black;margin-bottom: 5px;">
+Dr. Dobb's new e-learning platform, Dr. Dobb's University, gives you access to select conference sessions from Dr. Dobb's world-class developer events. Available courses focus on such topics as <a href="http://www.ddj.com/architect/ddjuniversity.jhtml">Architecture & Design</a>, <a href="http://www.ddj.com/cpp/ddjuniversity.jhtml">C/C++</a>, <a href="http://www.ddj.com/database/ddjuniversity.jhtml">Database engineering</a>, <a href="http://www.ddj.com/development-tools/ddjuniversity.jhtml">Development tools</a>, <a href="http://www.ddj.com/embedded/ddjuniversity.jhtml">Embedded systems</a>, <a href="http://www.ddj.com/hpc-high-performance-computing/ddjuniversity.jhtml">HPC</a>, <a href="http://www.ddj.com/java/ddjuniversity.jhtml">Java</a>, <a href="http://www.ddj.com/mobile/ddjuniversity.jhtml">Mobility</a>, <a href="http://www.ddj.com/linux-open-source/ddjuniversity.jhtml">Open Source</a>, <a href="http://www.ddj.com/security/ddjuniversity.jhtml">Security</a>, <a href="http://www.ddj.com/web-development/ddjuniversity.jhtml">Web Development</a> and <a href="http://www.ddj.com/windows/ddjuniversity.jhtml">Windows/.NET</a>.
+
+</div> --> + + +<!----------------------SEARCH JOBS BOX-----------------> + +<div id="jobscontainer" style="overflow: hidden; height: 41px; width: 402px;"> + +<form method="post" action="http://www.TechCareers.com/js/action/searchresults.asp?affiliate=ddj&_DARGS=/template_parts/dept/right/right.jhtml" id="form1" name="form1"> + +<input name="FOrderBy" value="modifydate[d]" type="hidden"> +<div id="TechCareersBox"> +<div id="SponsorBar" style="background-color: teal;"> +<span class="miniBARTitles">DR. DOBB'S CAREER CENTER</span> + + + + <!-- http://as.cmpnet.com/html.ng/pagepos=careers1&affiliate=ddj&site=sdmg&country=switzerland&server=atg&target=/linux-open-source/184406479 --> +<a target="_parent" href="http://as.cmpnet.com/event.ng/Type=click&FlightID=75640&AdID=125688&TargetID=2878&Segments=3108,3448,4875,11102&Targets=2625,2878&Values=34,46,51,63,77,87,91,102,140,442,656,944,945,975,1311,1339,1603,1716,1765,1767,1785,1925,1970,2179,2299,2310,2326,2352,2678,2735,2761,2767,2862,2942,3052,3082,3392,3890,3904,4080,4377,4799,6193,6293,6332,6392,6393,6422&RawValues=IP,66.77.24.210,&Redirect=http://www.cmp.com"><img src="makedepend_files/iopsblank.gif" alt="" border="0" height="1" width="1"></a><img src="makedepend_files/TypecountClientType2AdID125688FlightID75640TargetID2878S_003.gif" border="0" height="1" width="1"> + + +</div> +<div style="padding: 5px 0px 5px 5px; width: 100%;"> +Ready to take that job and shove it? <span style="text-decoration: underline;" class="pointerhand" onclick="jobsopen()">open</span> | <span style="text-decoration: underline;" class="pointerhand" onclick="jobsclose()">close</span><br> +</div> +</div> +<table bgcolor="#000000" border="0" cellpadding="5" cellspacing="1" width="100%"> + + <tbody><tr bgcolor="#ffffff"> + <td colspan="2" class="noLINE" bgcolor="#cde5e8" valign="top"> <b>Search jobs on</b> <a class="thirteenBOLD" href="http://www.techcareers.com/?affiliate=ddj">Dr. Dobb's TechCareers</a></td> + </tr> + <tr bgcolor="#ffffff"> + <td valign="top" width="50%"> +<table border="0" cellpadding="0" cellspacing="0" width="100%"> + <tbody><tr> + <td valign="top"> + + <div><strong>Function:</strong><br> <select name="FCareerFocus1" size="1"> + <option value="23">Information Technology</option> + <option value="25">Engineering</option> + </select></div> + <br> + <div><strong>Keyword(s):</strong><br> + <input name="FKeywords" size="25"></div> + <br> + <div><b>State:</b> <input name="FState" size="8" maxlength="2"> <input value="Go" id="submit1" name="submit1" align="right" type="submit"></div> + </td> + + </tr> + </tbody></table> + + </td> + <td class="noLINE" valign="top" width="50%"> + <!------------------FEATURED JOB-----------------> + +<li><a href="http://www.techcareers.com/JS/Form/SignUpForm.asp?affiliate=ddj">Post Your Resume</a> +</li><li><a href="http://www.techcareers.com/MKT/Content/EMP/Default.asp?affiliate=ddj">Employers Area</a> +</li><li><a href="http://www.techcareers.com/content/news.asp?affiliate=ddj">News & Features</a> +</li><li><a href="http://www.techcareers.com/content/forum.asp?affiliate=ddj">Blogs & Forums</a> +</li><li><a href="http://www.techcareers.com/JS/CareerResources/?affiliate=ddj">Career Resources</a> +<br><br> +<b>Browse By:</b> <br> + <a href="http://www.techcareers.com/JS/JobSearch/?affiliate=ddj">Location</a> | <a href="http://www.techcareers.com/JS/JobSearch/?affiliate=ddj">Employer</a> | <a href="http://www.techcareers.com/JS/JobSearch/?affiliate=ddj">City</a><br> + + + + + <!-- http://as.cmpnet.com/html.ng/pagepos=careers2&affiliate=ddj&site=sdmg&country=switzerland&server=atg&target=/linux-open-source/184406479 --> +<a target="_parent" href="http://as.cmpnet.com/event.ng/Type=click&FlightID=75634&AdID=125682&TargetID=2625&Segments=3108,3448,4875,11103&Targets=2625,2878&Values=34,46,51,63,77,87,91,102,140,442,656,944,945,975,1311,1339,1603,1716,1765,1767,1785,1925,1970,2179,2299,2310,2326,2352,2678,2735,2761,2767,2862,2942,3052,3082,3392,3890,3904,4080,4378,4799,6193,6293,6332,6392,6393,6422&RawValues=IP,66.77.24.210,&Redirect=http://www.cmp.com"><img src="makedepend_files/iopsblank.gif" alt="" border="0" height="1" width="1"></a><img src="makedepend_files/TypecountClientType2AdID125682FlightID75634TargetID2625S_002.gif" border="0" height="1" width="1"> + + + </li></td> + </tr> + + <tr bgcolor="#ffffff"> + <td colspan="2" bgcolor="#cde5e8" valign="top"><b>Most Recent Posts:</b></td> + </tr> + <tr bgcolor="#ffffff"> + <td colspan="2" class="noLINE" valign="top"> + <!-- <script language=javascript
+src=http://www.4jobs.com/common/partnership/jobs/display.asp?max=4&col=1&par=http://www.techcareers.com&slo=N&vj=N&des=y&ran=Y&i1=23&aor=OR&loc=Y&ind=N&sal=N&comp=Y&bc=FFFFFF&bg=FFFFFF&jbc=FFFFFF&jbg=FFFFFF&pad=5&wid=&tar=n&qsn1=affiliate&qsv1=ddj&date=Y></script> --> +<!-- <script language="javascript" src="http://www.dobbsprojects.com/prox/tcrss.js"></script> --> +<script language="javascript" src="makedepend_files/tcrss.js"></script><a href="http://www.techcareers.com/JS/General/Job.asp?id=15331393&affiliate=ddj">Software Sales Specialist</a><br>Hewlett Packard seeking Software Sales Specialist in San Francisco, CA<br><br><a href="http://www.techcareers.com/JS/General/Job.asp?id=16147525&affiliate=ddj">Programmer / System Analyst</a><br>American First CU seeking Programmer / System Analyst in La Habra, CA<br><br><a href="http://www.techcareers.com/JS/General/Job.asp?id=16181149&affiliate=ddj">Senior Engineer</a><br>Mine Safety Appliances seeking Senior Engineer in Cranberry Twp, PA<br><br><a href="http://www.techcareers.com/JS/General/Job.asp?id=14466217&affiliate=ddj">Sr Staff Circuit Designer</a><br>Xilinx seeking Sr Staff Circuit Designer in San Jose, CA<br><br><a href="http://www.techcareers.com/JS/General/Job.asp?id=15501072&affiliate=ddj">Senior Embedded Applications Software Developer</a><br>SigmaTel seeking Senior Embedded Applications Software Developer in Austin, TX<br><br></td> + </tr> +</tbody></table> +</form> +</div> +<div class="Bspace15"></div> +<!----------------------END SEARCH JOBS BOX-----------------> +<br> +<!-- VIDEO BOX --> + + +<!-- END VIDEO BOX --> + +<br> + +<div align="center"> + + + + <!-- http://as.cmpnet.com/html.ng/pagepos=jumbobox&affiliate=ddj&site=sdmg&country=switzerland&server=atg&target=/linux-open-source/184406479 --> +<a target="_parent" href="http://as.cmpnet.com/event.ng/Type=click&FlightID=107968&AdID=182668&TargetID=3885&Segments=3108,3448,4569,4669,4827,4875,5385,5472,6879,6939,7714,10554,14042,14113&Targets=2625,2878,3885,4532,10200&Values=34,46,51,63,77,87,91,102,140,290,442,656,944,945,975,1311,1603,1716,1765,1767,1785,1925,1970,2179,2299,2310,2326,2352,2678,2735,2761,2767,2862,2942,3052,3082,3392,3890,3904,4080,4799,6193,6293,6332,6392,6393,6422&RawValues=IP,66.77.24.210,&Redirect=http://www.eetimes.semiconductor.com%3Fcid%3DSDW"><img src="makedepend_files/ips_ostd1_336x280_ddj.gif" alt="" border="0" height="280" width="336"></a><img src="makedepend_files/TypecountClientType2AdID182668FlightID107968TargetID3885Site.gif" border="0" height="1" width="1"> + + </div> + <br> + + + + <table> + <tbody><tr> + <td valign="top"> + + + <!-- <img src="http://i.cmpnet.com/ddj/rule.gif" width="180" height="1" hspace="0" vspace="0" border="0" alt=""> --> + <!-- INFO LINK--> + + +<table border="0" width="100"> + <tbody><tr> + <td valign="top"><!-- MICROSITES --> + <div id="ArticlerightcolBox"> + <div id="TOCBAR"><span class="BARTitles">MICROSITES</span></div> + <div class="PaddingStyle2"> + <span class="blogName">FEATURED TOPIC</span> + + + + + <!-- http://as.cmpnet.com/html.ng/pagepos=microsite1&affiliate=ddj&site=sdmg&country=switzerland&server=atg&target=/linux-open-source/184406479 --> +<a target="_parent" href="http://as.cmpnet.com/event.ng/Type=click&FlightID=110126&AdID=182335&TargetID=8824&Segments=3108,3448,4875,12454&Targets=2625,2878,8824&Values=34,46,51,63,77,87,91,102,140,442,656,944,945,975,1311,1603,1716,1765,1767,1785,1925,1970,2179,2299,2310,2326,2352,2678,2735,2761,2767,2862,2942,3052,3082,3392,3890,3904,4080,4799,5161,6193,6293,6332,6392,6393,6422&RawValues=IP,66.77.24.210,&Redirect=http://www.ddj.com/focal/msoffice-oba"><img src="makedepend_files/125x125_411_PDQ.gif" alt="" border="0" height="125" width="125"></a><img src="makedepend_files/TypecountClientType2AdID182335FlightID110126TargetID8824Site.gif" border="0" height="1" width="1"> + + + <br><br> + <span class="blogName">ADDITIONAL TOPICS</span> + + + + + <!-- http://as.cmpnet.com/html.ng/pagepos=microsite2&affiliate=ddj&site=sdmg&country=switzerland&server=atg&target=/linux-open-source/184406479 --> +<a target="_blank" href="http://as.cmpnet.com/event.ng/Type=click&FlightID=110127&AdID=182336&TargetID=8823&Segments=3108,3448,4875,12455&Targets=2625,2878,8823&Values=34,46,51,63,77,87,91,102,140,442,656,944,945,975,1311,1603,1716,1765,1767,1785,1925,1970,2179,2299,2310,2326,2352,2678,2735,2761,2767,2862,2942,3052,3082,3392,3890,3904,4080,4799,5162,6193,6293,6332,6392,6393,6422&RawValues=&Redirect=http://www.ddj.com/focal/msoffice-oba">"Get the 411 on OBA PDQ! Learn to build and deploy Microsoft Office Business Applications"</a><img src="makedepend_files/TypecountClientType2AdID182336FlightID110127TargetID8823Site.gif" border="0" height="1" width="1"> + + + + + + + <!-- http://as.cmpnet.com/html.ng/pagepos=microsite3&affiliate=ddj&site=sdmg&country=switzerland&server=atg&target=/linux-open-source/184406479 --> +<a target="_blank" href="http://as.cmpnet.com/event.ng/Type=click&FlightID=110421&AdID=182683&TargetID=8825&Segments=3108,3448,4875,12456&Targets=2625,2878,8825&Values=34,46,51,63,77,87,91,102,140,442,656,944,945,975,1311,1603,1716,1765,1767,1785,1925,1970,2179,2299,2310,2326,2352,2678,2735,2761,2767,2862,2942,3052,3082,3392,3890,3904,4080,4799,5163,6193,6293,6332,6392,6393,6422&RawValues=&Redirect=http://www.ddjresources.com/silverlight/">Are you a Windows programmer? Get the direct route to design interactive environments. Learn Silverlight here.</a><img src="makedepend_files/TypecountClientType2AdID182683FlightID110421TargetID8825Site.gif" border="0" height="1" width="1"> + + + </div> + </div> + <!-- END MICROSITES --></td> + <td valign="top"><!-- INFO LINK--> + <div id="ArticlerightcolBox"> +<span class="blogName">INFO-LINK</span> + <div class="Bspace5"> + + + + + <!-- http://as.cmpnet.com/html.ng/pagepos=infolink1&affiliate=ddj&site=sdmg&country=switzerland&server=atg&target=/linux-open-source/184406479 --> +<a target="_blank" href="http://as.cmpnet.com/event.ng/Type=click&FlightID=110048&AdID=182227&TargetID=4845&Segments=3108,3448,4875,6084,6088,6876,8025,8050,9374&Targets=2625,2878,4845,4855&Values=34,46,51,63,77,87,91,102,140,442,656,944,945,975,1311,1603,1716,1765,1767,1785,1925,1970,2179,2299,2310,2326,2352,2678,2735,2761,2767,2862,2942,3052,3082,3183,3392,3890,3904,4080,4799,6193,6293,6332,6392,6393,6422&RawValues=&Redirect=http://www.ddj.com/focal/msoffice-oba">Learn how to build OBAs using the Microsoft Office system.</a><img src="makedepend_files/TypecountClientType2AdID182227FlightID110048TargetID4845Site.gif" border="0" height="1" width="1"> + + </div> + <div class="Bspace5"> + + + + + <!-- http://as.cmpnet.com/html.ng/pagepos=infolink2&affiliate=ddj&site=sdmg&country=switzerland&server=atg&target=/linux-open-source/184406479 --> +<a target="_blank" href="http://as.cmpnet.com/event.ng/Type=click&FlightID=110414&AdID=182665&TargetID=4921&Segments=3108,3448,4875,6085,6097,8026,8051&Targets=2625,2878,7437,4921&Values=34,46,51,63,77,87,91,102,140,442,656,944,945,975,1311,1603,1716,1765,1767,1785,1925,1970,2179,2299,2310,2326,2352,2678,2735,2761,2767,2862,2942,3052,3082,3184,3392,3890,3904,4080,4799,6193,6293,6332,6392,6393,6422&RawValues=&Redirect=http://www.dobbsprojects.com/lcap/microsoft1/?method=text">Learn about the benefits of Protocol Licensing: Download this free Whitepaper from Microsoft</a><img src="makedepend_files/TypecountClientType2AdID182665FlightID110414TargetID4921Site.gif" border="0" height="1" width="1"> + + </div> + <div class="Bspace5"> + + + + + <!-- http://as.cmpnet.com/html.ng/pagepos=infolink3&affiliate=ddj&site=sdmg&country=switzerland&server=atg&target=/linux-open-source/184406479 --> +<a target="_blank" href="http://as.cmpnet.com/event.ng/Type=click&FlightID=112205&AdID=185099&TargetID=5776&Segments=3108,3448,4875,6089,8027,8052,8088&Targets=2625,2878,5776&Values=34,46,51,63,77,87,91,102,140,442,656,944,945,975,1311,1603,1716,1765,1767,1785,1925,1970,2179,2299,2310,2326,2352,2678,2735,2761,2767,2862,2942,3052,3082,3185,3392,3890,3904,4080,4799,6193,6293,6332,6392,6393,6422&RawValues=&Redirect=http://w.on24.com/r.htm?e=102904&s=1&k=EE435A18D1C8A918764935B406A34FE9&partnerref=4SD">Register Today for Microsoft's Silverlight Webinar Series</a><img src="makedepend_files/TypecountClientType2AdID185099FlightID112205TargetID5776Site.gif" border="0" height="1" width="1"> + + </div> + <div class="Bspace5"> + + + + + <!-- http://as.cmpnet.com/html.ng/pagepos=infolink4&affiliate=ddj&site=sdmg&country=switzerland&server=atg&target=/linux-open-source/184406479 --> +<a target="_blank" href="http://as.cmpnet.com/event.ng/Type=click&FlightID=112620&AdID=185513&TargetID=5775&Segments=3108,3448,4875,6090,8028,8053,8089&Targets=2625,2878,5775&Values=34,46,51,63,77,87,91,102,140,442,656,944,945,975,1311,1603,1716,1765,1767,1785,1925,1970,2179,2299,2310,2326,2352,2678,2735,2761,2767,2862,2942,3052,3082,3186,3392,3890,3904,4080,4799,6193,6293,6332,6392,6393,6422&RawValues=&Redirect=http://www.dobbsprojects.com/lcap/recursion2/?method=text">Free Whitepaper: Building a real-time C++ robotics framework using Recursion's C++ Toolkit. Download Now!</a><img src="makedepend_files/TypecountClientType2AdID185513FlightID112620TargetID5775Site.gif" border="0" height="1" width="1"> + + </div> +</div> + <br></td> + </tr> +</tbody></table> +<br> +<div align="center"> + + + + <!-- http://as.cmpnet.com/html.ng/pagepos=sec2&affiliate=ddj&site=sdmg&country=switzerland&server=atg&target=/linux-open-source/184406479 --> +<a target="_parent" href="http://as.cmpnet.com/event.ng/Type=click&FlightID=107504&AdID=179387&TargetID=10444&Segments=3108,3448,4875,14374&Targets=2625,2878,10444&Values=34,46,51,63,77,87,91,102,140,442,656,944,945,975,1311,1603,1716,1765,1767,1785,1925,1970,2179,2299,2310,2326,2352,2678,2735,2761,2767,2862,2942,3052,3082,3380,3392,3890,3904,4080,4799,6193,6293,6332,6392,6393,6422&RawValues=IP,66.77.24.210,&Redirect=http://www.ddjresources.com/silverlight"><img src="makedepend_files/silverlight_center_imu2dec.gif" alt="" border="0" height="280" width="336"></a><img src="makedepend_files/TypecountClientType2AdID179387FlightID107504TargetID10444Sit.gif" border="0" height="1" width="1"> + + </div> + <br> + + + </td> + + <td valign="top"> + <!-- MICROSITES --> + <!-- <DIV id="ArticlerightcolBox">
+ <div id="TOCBAR"><span class="BARTitles">MICROSITES</span></div>
+ <DIV class="PaddingStyle2">
+ <span class="blogName">FEATURED TOPIC</span>
+ <droplet src="/viewAd.jhtml">
+ <param name="pagepos" value="microsite1">
+ </droplet>
+ <br /><br />
+ <span class="blogName">ADDITIONAL TOPICS</span>
+ <droplet src="/viewAd.jhtml">
+ <param name="pagepos" value="microsite2">
+ </droplet>
+ <droplet src="/viewAd.jhtml">
+ <param name="pagepos" value="microsite3">
+ </droplet>
+ </div>
+ </div> --> + <!-- END MICROSITES --> + <br> + + <!--
+<div align="center"><droplet src="/viewAd.jhtml">
+ <param name="pagepos" value="jumbobox">
+ </droplet></div>
+ <br> --> + <!-- <droplet src="/viewAd.jhtml">
+ <param name="pagepos" value="testdrive">
+ </droplet> --> + </td> + </tr> + <tr> + <td colspan="2"> + <!-- Industry Brains --> + <script type="text/javascript" src="makedepend_files/jsct.js"></script><table style="border: 1px solid rgb(197, 174, 80); width: 100%; background-color: rgb(255, 255, 255); border-collapse: collapse;" xmlns:ibn="urn:industrybrains.com:linkserver" cellspacing="0"><tbody><tr><td style="padding: 4px; font-family: Arial,Helvetica,sans-serif; font-style: normal; font-variant: normal; font-weight: bold; font-size: 12px; line-height: normal; font-size-adjust: none; font-stretch: normal; color: rgb(0, 0, 0); text-align: left; background-color: rgb(249, 248, 247);" colspan="2"> MARKETPLACE (Sponsored Links) </td></tr><tr><td style="padding: 3px;"></td><td style="border: 0px solid rgb(24, 77, 49); padding: 3px;"><div><a style="font-family: Arial,Helvetica,sans-serif; font-style: normal; font-variant: normal; font-weight: bold; font-size: 12px; line-height: normal; font-size-adjust: none; font-stretch: normal; text-decoration: underline; color: rgb(64, 64, 255);" target="_new" href="http://links.industrybrains.com/click?sid=644&rqctid=134&pos=1&lid=458815&cid=101147&pr=2&tstamp=20080311173820&url=http://www.techexcel.com/products/servicesuite/servicewise.html%3futm_source%3dindustrybrains%26utm_medium%3dcpc%26utm_content%3dServiceWise%26utm_campaign%3dITILinfo">Workflow Enabled Help Desk & IT Service Management</a></div><div style="font-family: Verdana,Arial,Helvetica,sans-serif; font-style: normal; font-variant: normal; font-weight: normal; font-size: 11px; line-height: normal; font-size-adjust: none; font-stretch: normal; color: rgb(0, 0, 0);">Automate service desk activities and integrate processes across IT. Learn more here.</div></td></tr><tr><td style="padding: 3px;"></td><td style="border: 0px solid rgb(24, 77, 49); padding: 3px;"><div><a style="font-family: Arial,Helvetica,sans-serif; font-style: normal; font-variant: normal; font-weight: bold; font-size: 12px; line-height: normal; font-size-adjust: none; font-stretch: normal; text-decoration: underline; color: rgb(64, 64, 255);" target="_new" href="http://links.industrybrains.com/click?sid=644&rqctid=134&pos=2&lid=484014&cid=135265&pr=2&tstamp=20080311173820&url=http://clk.atdmt.com/MRT/go/ndstritp0450006421mrt/direct/01/">Download MICROSOFT SEARCH SERVER EXPRESS 2008 FREE</a></div><div style="font-family: Verdana,Arial,Helvetica,sans-serif; font-style: normal; font-variant: normal; font-weight: normal; font-size: 11px; line-height: normal; font-size-adjust: none; font-stretch: normal; color: rgb(0, 0, 0);">Search file shares, SharePoint sites, Exchange Public Folders, Lotus Notes repositories, and more!</div></td></tr><tr><td style="padding: 3px;"></td><td style="border: 0px solid rgb(24, 77, 49); padding: 3px;"><div><a style="font-family: Arial,Helvetica,sans-serif; font-style: normal; font-variant: normal; font-weight: bold; font-size: 12px; line-height: normal; font-size-adjust: none; font-stretch: normal; text-decoration: underline; color: rgb(64, 64, 255);" target="_new" href="http://links.industrybrains.com/click?sid=644&rqctid=134&pos=3&lid=458552&cid=108071&pr=2&tstamp=20080311173820&url=http://www.windev.com/index%3fp%3dCMPWinDev">WinDev 11 - Powerful IDE</a></div><div style="font-family: Verdana,Arial,Helvetica,sans-serif; font-style: normal; font-variant: normal; font-weight: normal; font-size: 11px; line-height: normal; font-size-adjust: none; font-stretch: normal; color: rgb(0, 0, 0);">Develop 10 times faster ! ALM, IDE, .Net, RAD, 5GL, Database, 5GL, 64-bit, etc. Free Express version</div></td></tr><tr><td style="padding: 3px;"></td><td style="border: 0px solid rgb(24, 77, 49); padding: 3px;"><div><a style="font-family: Arial,Helvetica,sans-serif; font-style: normal; font-variant: normal; font-weight: bold; font-size: 12px; line-height: normal; font-size-adjust: none; font-stretch: normal; text-decoration: underline; color: rgb(64, 64, 255);" target="_new" href="http://links.industrybrains.com/click?sid=644&rqctid=134&pos=4&lid=457884&cid=6237&pr=2&tstamp=20080311173820&url=http://www.sgvsarc.com/adv_default.asp%3fsrcid%3dAutoD">Flowcharts from C/C++ code -- Free trial download</a></div><div style="font-family: Verdana,Arial,Helvetica,sans-serif; font-style: normal; font-variant: normal; font-weight: normal; font-size: 11px; line-height: normal; font-size-adjust: none; font-stretch: normal; color: rgb(0, 0, 0);">Understand C/C++ code in less time. A new team member ? Inherited legacy code ? Get up to speed fast... </div></td></tr><tr><td style="padding: 3px;"></td><td style="border: 0px solid rgb(24, 77, 49); padding: 3px;"><div><a style="font-family: Arial,Helvetica,sans-serif; font-style: normal; font-variant: normal; font-weight: bold; font-size: 12px; line-height: normal; font-size-adjust: none; font-stretch: normal; text-decoration: underline; color: rgb(64, 64, 255);" target="_new" href="http://links.industrybrains.com/click?sid=644&rqctid=134&pos=5&lid=457902&cid=2193&pr=2&tstamp=20080311173820&url=http://www.visualbuild.com/%3fcmp">Automate Software Builds with Visual Build Pro</a></div><div style="font-family: Verdana,Arial,Helvetica,sans-serif; font-style: normal; font-variant: normal; font-weight: normal; font-size: 11px; line-height: normal; font-size-adjust: none; font-stretch: normal; color: rgb(0, 0, 0);">Easily create an automated, repeatable process for building and deploying software.</div></td></tr><tr><td style="padding: 0.2em; font-family: Arial,Helvetica,sans-serif; font-style: italic; font-variant: normal; font-weight: normal; font-size: 10px; line-height: normal; font-size-adjust: none; font-stretch: normal; color: rgb(0, 51, 102); text-align: right;" colspan="2"><a href="http://www.industrybrains.com/cmpsd" style="font-family: Verdana,Arial,Helvetica,sans-serif; font-style: italic; font-variant: normal; font-weight: normal; font-size: 10px; line-height: normal; font-size-adjust: none; font-stretch: normal; color: rgb(0, 0, 170);"> Advertise With Us </a></td></tr></tbody></table><a href="http://shlinks.industrybrains.com/sh?sid=644&a=23abf02755a5877441daafff0e0a20d0006c03f08ff1275116277e26c0ce3851"></a> + <!-- end Industry Brains --> + </td> + </tr> + + </tbody></table> + + + + </td><td rowspan="2"> +</td><td rowspan="2" valign="top" width="10"> </td> + <td valign="top" width="165"> + <!-- DEPARTMENTS --> + + +<div id="DepartmentBOX"><div id="DepartmentBar"><span class="miniBARTitles">DEPARTMENTS</span></div> +<span class="deptLinks"> + +<div class="DeptLinkPad"><a href="http://www.ddj.com/;jsessionid=4CBKBE5CTME2YQSNDLOSKH0CJUNN2JVN?cid=vnav" class="deptLinks">Home</a></div> + <div id="dividerLine"><img src="makedepend_files/blank.html" border="0" height="1" hspace="0" vspace="0" width="5"></div> + + <div class="DeptLinkPad"><a href="http://www.ddj.com/architect/;jsessionid=4CBKBE5CTME2YQSNDLOSKH0CJUNN2JVN?cid=vnav" class="deptLinks">Architecture & Design</a></div> + <div id="dividerLine"><img src="makedepend_files/blank.html" border="0" height="1" hspace="0" vspace="0" width="5"></div> + + <div class="DeptLinkPad"><a href="http://www.ddj.com/cpp/;jsessionid=4CBKBE5CTME2YQSNDLOSKH0CJUNN2JVN?cid=vnav" class="deptLinks">C/C++</a></div> + <div id="dividerLine"><img src="makedepend_files/blank.html" border="0" height="1" hspace="0" vspace="0" width="5"></div> + + + <div class="DeptLinkPad"><a href="http://www.ddj.com/database/;jsessionid=4CBKBE5CTME2YQSNDLOSKH0CJUNN2JVN?cid=vnav" class="deptLinks">Database</a></div> + <div id="dividerLine"><img src="makedepend_files/blank.html" border="0" height="1" hspace="0" vspace="0" width="5"></div> + + <div class="DeptLinkPad"><a href="http://www.ddj.com/development-tools/;jsessionid=4CBKBE5CTME2YQSNDLOSKH0CJUNN2JVN?cid=vnav" class="deptLinks">Development Tools</a></div> + <div id="dividerLine"><img src="makedepend_files/blank.html" border="0" height="1" hspace="0" vspace="0" width="5"></div> + + <div class="DeptLinkPad"><a href="http://www.ddj.com/embedded/;jsessionid=4CBKBE5CTME2YQSNDLOSKH0CJUNN2JVN?cid=vnav" class="deptLinks">Embedded Systems</a></div> + <div id="dividerLine"><img src="makedepend_files/blank.html" border="0" height="1" hspace="0" vspace="0" width="5"></div> + + <div class="DeptLinkPad"><a href="http://www.ddj.com/hpc-high-performance-computing/;jsessionid=4CBKBE5CTME2YQSNDLOSKH0CJUNN2JVN?cid=vnav" class="deptLinks">High Performance Computing</a></div> + <div id="dividerLine"><img src="makedepend_files/blank.html" border="0" height="1" hspace="0" vspace="0" width="5"></div> + + <div class="DeptLinkPad"><a href="http://www.ddj.com/java/;jsessionid=4CBKBE5CTME2YQSNDLOSKH0CJUNN2JVN?cid=vnav" class="deptLinks">Java</a></div> + <div id="dividerLine"><img src="makedepend_files/blank.html" border="0" height="1" hspace="0" vspace="0" width="5"></div> + + + <div class="DeptLinkPad"><a href="http://www.ddj.com/mobile/;jsessionid=4CBKBE5CTME2YQSNDLOSKH0CJUNN2JVN?cid=vnav" class="deptLinks">Mobility</a></div> + <div id="dividerLine"><img src="makedepend_files/blank.html" border="0" height="1" hspace="0" vspace="0" width="5"></div> + + <div class="DeptLinkPad"><a href="http://www.ddj.com/linux-open-source/;jsessionid=4CBKBE5CTME2YQSNDLOSKH0CJUNN2JVN?cid=vnav" class="deptLinks">Open Source</a></div> + <div id="dividerLine"><img src="makedepend_files/blank.html" border="0" height="1" hspace="0" vspace="0" width="5"></div> + + + <div class="DeptLinkPad"><a href="http://www.ddj.com/security/;jsessionid=4CBKBE5CTME2YQSNDLOSKH0CJUNN2JVN?cid=vnav" class="deptLinks">Security</a></div> + <div id="dividerLine"><img src="makedepend_files/blank.html" border="0" height="1" hspace="0" vspace="0" width="5"></div> + + <div class="DeptLinkPad"><a href="http://www.ddj.com/web-development/;jsessionid=4CBKBE5CTME2YQSNDLOSKH0CJUNN2JVN?cid=vnav" class="deptLinks">Web Development</a></div> + <div id="dividerLine"><img src="makedepend_files/blank.html" border="0" height="1" hspace="0" vspace="0" width="5"></div> + + <div class="DeptLinkPad"><a href="http://www.ddj.com/windows/;jsessionid=4CBKBE5CTME2YQSNDLOSKH0CJUNN2JVN?cid=vnav" class="deptLinks">Windows/.NET</a></div> + <div id="dividerLine"><img src="makedepend_files/blank.html" border="0" height="1" hspace="0" vspace="0" width="5"></div> +</span> +<!--<div id="EventBar"><span class="eventBARTitles"><a href="/events/" class="eventBARTitles">Events</a></span></div>--> +</div> + +<br> +<div id="DepartmentBOXSponsored"> +<div class="deptSponsorHeader">♦ sponsored</div></div> + <div id="DepartmentBOXSilverlight"> + <div class="DeptLinkPadSilverlight"><a href="http://www.ddjresources.com/silverlight/" class="deptLinksSilverlight">Resource Center for Microsoft® Silverlight™</a></div> + <div id="dividerLine"><img src="makedepend_files/blank.html" border="0" height="1" hspace="0" vspace="0" width="5"></div> + + <!-- <div id="EventBar"><span class="eventBARTitles"><a href="/dept/events/" class="eventBARTitles">Events</a></span></div> --> + </div> + + + <br> + <div width="161" height="601" hspace="0" vspace="0" border="0" align="center"> + + + + + <!-- http://as.cmpnet.com/html.ng/pagepos=sky&affiliate=ddj&site=sdmg&country=switzerland&server=atg&target=/linux-open-source/184406479 --> +<script type="text/javascript"><!-- +google_ad_client = "pub-7860495301058581"; +google_ad_width = 120; +google_ad_height = 600; +google_ad_format = "120x600_as"; +google_ad_type = "text"; +//2007-05-30: SDMG_Sky +google_ad_channel = "4350521531"; +google_color_border = "FFFFFF"; +google_color_bg = "FFFFFF"; +google_color_link = "003399"; +google_color_text = "000000"; +google_color_url = "003399"; +//--> +</script> +<script type="text/javascript" src="makedepend_files/show_ads.js"> +</script><iframe name="google_ads_frame" src="makedepend_files/ads.html" marginwidth="0" marginheight="0" vspace="0" hspace="0" allowtransparency="true" frameborder="0" height="600" scrolling="no" width="120"></iframe><img src="makedepend_files/TypecountClientType2AdID185678FlightID110062TargetID2781Site.gif" border="0" height="1" width="1"> + + + </div> + <br> + + + + + <!-- http://as.cmpnet.com/html.ng/pagepos=tile&affiliate=ddj&site=sdmg&country=switzerland&server=atg&target=/linux-open-source/184406479 --> +<a target="_parent" href="http://as.cmpnet.com/event.ng/Type=click&FlightID=97817&AdID=161784&TargetID=2780&Segments=3108,3250,3448,4875,5387,5471,5751&Targets=2625,2780,2878,4298&Values=34,46,51,63,77,87,91,102,140,205,442,656,944,945,975,1311,1603,1716,1765,1767,1785,1925,1970,2179,2299,2310,2326,2352,2678,2735,2761,2767,2862,2942,3052,3082,3392,3890,3904,4080,4799,6193,6293,6332,6392,6393,6422&RawValues=IP,66.77.24.210,&Redirect=http://www.ddj.com/newsletters/"><img src="makedepend_files/newsletter125x125.gif" alt="" border="0" height="125" width="125"></a><img src="makedepend_files/TypecountClientType2AdID161784FlightID97817TargetID2780SiteI.gif" border="0" height="1" width="1"> + + + </td> +</tr> +</tbody></table> +<!-- IMU AD--> + + + </td> + </tr> + </tbody></table> + +<img src="makedepend_files/rule.gif" alt="" align="left" border="0" height="1" hspace="5" vspace="10" width="900"> +<br clear="left"> +<!-- AD BANNER AT BOTTOM --> +<div id="BottomAdContainer"> + + + + <!-- http://as.cmpnet.com/html.ng/pagepos=bottom&affiliate=ddj&site=sdmg&country=switzerland&server=atg&target=/linux-open-source/184406479 --> +<a target="_parent" href="http://as.cmpnet.com/event.ng/Type=click&FlightID=107499&AdID=178983&TargetID=1251&Segments=1551,3108,3317,3448,4875,5470,5750,5766,7712,7718,11862&Targets=1251,2625,2878,4879&Values=34,46,51,63,77,87,91,102,140,204,442,656,944,945,975,1311,1603,1716,1765,1767,1785,1925,1970,2179,2299,2310,2326,2352,2678,2735,2761,2767,2862,2942,3052,3082,3392,3890,3904,4080,4799,6193,6293,6332,6392,6393,6422&RawValues=IP,66.77.24.210,&Redirect=http://www.ddjresources.com/silverlight"><img src="makedepend_files/Silverlight_Cntr_Leadr3.jpg" alt="" border="0" height="90" width="728"></a><img src="makedepend_files/TypecountClientType2AdID178983FlightID107499TargetID1251Site.gif" border="0" height="1" width="1"> + + +</div> +<!-- /AD BANNER AT BOTTOM --> +<div class="LColMargin"> + <table class="elfixo" border="0" cellpadding="0" cellspacing="0" width="900"> + <tbody><tr> + <td valign="top" width="600"> + </td> + <td valign="top" width="300"> + + <!-- put Microsoft Resource Center box on article pages --> + + + + + <table border="0" cellpadding="1"><tbody><tr><td> + <!-- http://as.cmpnet.com/html.ng/affiliate=ddj&pagepos=mslink1&site=sdmg&country=switzerland&server=atg&target=/linux-open-source/184406479 --> +<a target="_parent" href="http://as.cmpnet.com/event.ng/Type=click&FlightID=75640&AdID=125688&TargetID=2878&Segments=3108,3448,4875,7953&Targets=2625,2878&Values=34,46,51,63,77,87,91,102,140,442,656,944,945,975,1311,1603,1716,1765,1767,1785,1925,1970,2179,2299,2310,2326,2352,2678,2735,2761,2767,2862,2942,3052,3082,3392,3556,3890,3904,4080,4799,6193,6293,6332,6392,6393,6422&RawValues=IP,66.77.24.210,&Redirect=http://www.cmp.com"><img src="makedepend_files/iopsblank.gif" alt="" border="0" height="1" width="1"></a><img src="makedepend_files/TypecountClientType2AdID125688FlightID75640TargetID2878SiteI.gif" border="0" height="1" width="1"> + + </td></tr></tbody></table> + + + + + + </td> + </tr> + </tbody></table> +</div> +<img src="makedepend_files/rule.gif" alt="" align="left" border="0" height="1" hspace="5" vspace="8" width="900"> +<br clear="left"> + +<!-- COPYRIGHT AND LINKS --> +<div id="COPYRIGHTContainer"> + <span class="tenBOLD"> + <div class="Bspace8"> + <!--<a href="http://www.sdmediagroup.com/reg/faq.html" class=nineBOLD>What are the <span class="green9">green links</span>?</a> |--> + <!-- The value for parameter category is the same as deptID. It has value when the blog pages are called. --> + + + + + + + + + + <a href="http://www.ddj.com/rss/openSource.xml;jsessionid=4CBKBE5CTME2YQSNDLOSKH0CJUNN2JVN"> <img src="makedepend_files/rss.jpg" align="absbottom" border="0" hspace="3" vspace="0"></a> | + <!-- <a href="/sitemap/" class="tenBOLD">Site map</a> --> + </div> + </span> <span class="tenBOLD"> + <div class="Bspace8"> + <a href="http://www.think-services.com/" target="new" class="tenBOLD">© 2008 Think Services</a>, + <a href="http://www.unitedbusinessmedia.com/ubm/sitetools/privacy/" class="tenBOLD">Privacy Policy</a>, + <a href="http://www.cmp.com/delivery/privacy.html#california" class="tenBOLD">Your California Privacy Rights</a>, + <a href="http://www.unitedbusinessmedia.com/ubm/sitetools/disclaimer/" class="tenBOLD">Terms of Service</a>, + <a href="http://www.unitedbusinessmedia.com/" class="tenBOLD">United Business Media</a> <br> + Comments about the web site: <a href="mailto:webmaster@ddj.com" class="tenBOLD">webmaster@ddj.com</a> + </div> + Related Sites: + <a href="http://www.dotnetjunkies.com/" target="_blank" class="tenBOLD">DotNetJunkies</a>, + <a href="http://www.sdexpo.com/" target="_blank" class="tenBOLD">SD Expo</a>, + <a href="http://www.sqljunkies.com/" target="_blank" class="tenBOLD">SqlJunkies</a></span> +</div> +<!-- /COPYRIGHT AND LINKS --> + + + + + <!-- http://as.cmpnet.com/html.ng/pagepos=toptile&affiliate=ddj&site=sdmg&country=switzerland&server=atg&target=/linux-open-source/184406479 --> +<a target="_parent" href="http://as.cmpnet.com/event.ng/Type=click&FlightID=75634&AdID=125682&TargetID=2625&Segments=3108,3448,4875,7725,8024,8049,8892,8904&Targets=2625,2878,5537&Values=34,46,51,63,77,87,91,102,140,265,442,656,944,945,975,1311,1603,1716,1765,1767,1785,1925,1970,2179,2299,2310,2326,2352,2678,2735,2761,2767,2862,2942,3052,3082,3392,3890,3904,4080,4799,6193,6293,6332,6392,6393,6422&RawValues=IP,66.77.24.210,&Redirect=http://www.cmp.com"><img src="makedepend_files/iopsblank.gif" alt="" border="0" height="1" width="1"></a><img src="makedepend_files/TypecountClientType2AdID125682FlightID75634TargetID2625SiteI.gif" border="0" height="1" width="1"> + + +<!-- start Vibrant Media IntelliTXT script section --> +<script type="text/javascript" src="makedepend_files/ddj_edit.js"></script><script language="javascript" type="text/javascript" src="makedepend_files/front.js"></script> +<!-- end Vibrant Media IntelliTXT script section --> +</body></html>
\ 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 <<EOF +ARCH = $ARCH +PLATFORM = $PLATFORM +OS_MAJOR_VERSION = $OS_MAJOR_VERSION +OS_MINOR_VERSION = $OS_MINOR_VERSION +LIBS_DL = $LIBS_DL +LIBS_SSL = $LIBS_SSL +GCC_MAJOR_VERSION = $GCC_MAJOR_VERSION +GCC_MINOR_VERSION = $GCC_MINOR_VERSION +EOF + ;; +esac diff --git a/makefiles/platform.mk b/makefiles/platform.mk new file mode 100644 index 0000000..175e25f --- /dev/null +++ b/makefiles/platform.mk @@ -0,0 +1,34 @@ +# sets e. g. to LINUX, OS_MAJOR_VERSION to 2 and OS_MINOR_VERSION to 6 +# by calling the 'guess_env' shell script, where the actual probing happens +# Also sets PLATFORM_COMPILE_FLAGS to be included when compiling C/C++ code +# +# requires: +# - TOPDIR +# +# provides: +# - PLATFORM +# - OS_MAJOR_VERSION +# - OS_MINOR_VERSION +# - PLATFORM_COMPILE_FLAGS +# - EXE +# - SO +# +# author: Andreas Baumann, abaumann at yahoo dot com + +PLATFORM = $(shell $(TOPDIR)/makefiles/guess_env --platform) +OS_MAJOR_VERSION = $(shell $(TOPDIR)/makefiles/guess_env --os-major-version) +OS_MINOR_VERSION = $(shell $(TOPDIR)/makefiles/guess_env --os-minor-version) + +PLATFORM_COMPILE_FLAGS = \ + -D$(PLATFORM) \ + -DOS_MAJOR_VERSION=$(OS_MAJOR_VERSION) \ + -DOS_MINOR_VERSION=$(OS_MINOR_VERSION) + +LIBS_DL = $(shell $(TOPDIR)/makefiles/guess_env --libs-dl) +LIBS_SSP = $(shell $(TOPDIR)/makefiles/guess_env --libs-ssl) + +EXE = +SO = .so + +GCC_MAJOR_VERSION = $(shell $(TOPDIR)/makefiles/guess_env --gcc-major-version $(CC)) +GCC_MINOR_VERSION = $(shell $(TOPDIR)/makefiles/guess_env --gcc-minor-version $(CC)) diff --git a/makefiles/sub.mk b/makefiles/sub.mk new file mode 100644 index 0000000..644dc3c --- /dev/null +++ b/makefiles/sub.mk @@ -0,0 +1,20 @@ +# makefile for a sub package +# +# requires: +# - TOPDIR +# - SUBDIRS +# - INCLUDE_DIRS +# +# provides: +# - target: all targets + +-include $(TOPDIR)/makefiles/platform.mk +-include $(TOPDIR)/makefiles/compiler.mk + +.PHONY: all $(SUBDIRS) local_all +all: local_all $(OBJS) $(CPPOBJS) $(BIN_OBJS) $(BINS) $(CMODULES) $(CPPMODULES) + @test -z "$(SUBDIRS)" || ( set -e; for d in $(SUBDIRS)""; do \ + (set -e; $(MAKE) -C $$d all || exit 1); done) + +-include $(TOPDIR)/makefiles/depend.mk +-include $(TOPDIR)/makefiles/clean.mk diff --git a/makefiles/top.mk b/makefiles/top.mk new file mode 100644 index 0000000..98993f7 --- /dev/null +++ b/makefiles/top.mk @@ -0,0 +1,30 @@ +# top-level makefile for a package +# +# requires: +# - TOPDIR +# - SUBDIRS +# +# provides: +# - target 'all' +# - target 'clean' +# - target 'distclean' +# - target 'test' + +.PHONY: all +all: + @test -z "$(SUBDIRS)" || ( set -e; for d in $(SUBDIRS)""; do \ + (set -e; $(MAKE) -C $$d all || exit 1); done) + +.PHONY: clean +clean: + @test -z "$(SUBDIRS)" || ( set -e; for d in $(SUBDIRS)""; do \ + (set -e; $(MAKE) -C $$d clean || exit 1); done) + +.PHONY: distclean +distclean: clean + test -z "$(SUBDIRS)" || ( set -e; for d in $(SUBDIRS)""; do \ + (set -e; $(MAKE) -C $$d distclean || exit 1); done) + +.PHONY: test +test: all + @$(MAKE) -C tests test diff --git a/src/GNUmakefile b/src/GNUmakefile new file mode 100644 index 0000000..0ed8c1b --- /dev/null +++ b/src/GNUmakefile @@ -0,0 +1,41 @@ +TOPDIR = .. + +SUBDIRS = + +INCLUDE_DIRS = -I. + +BINS = \ + testd + +OBJS = \ + port/lockf.o \ + port/snprintf.o \ + cmdline.o \ + log.o \ + signals.o \ + pidfile.o \ + daemon.o + + +-include $(TOPDIR)/makefiles/sub.mk + +# ABa: currently a special rule for cmdline.c as gengetopt is not +# completly fixed yet +testd.d : cmdline.h +cmdline.h : daemon.ggo + gengetopt --include-getopt -i $< +cmdline.c : daemon.ggo + gengetopt --include-getopt -i $< + +cmdline.o : cmdline.c cmdline.h + $(CC) -c -o $@ $< + +local_all: cmdline.h + +local_clean: + -@rm port/*.bak 2>/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 <sys/types.h> /* for pid_t, ssize_t, off_t */ +#include <unistd.h> /* for getppid, geteuid, fork, + chdir, pipe, sysconf */ +#include <stdlib.h> /* for malloc, free */ +#include <errno.h> /* for errno */ +#include <sys/stat.h> /* for umask */ +#include <fcntl.h> /* for O_RDWR */ +#include <grp.h> /* for getgrnam, setgid */ +#include <pwd.h> /* 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 <stdarg.h> /* 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 "<unknown"; + } +} + +int log_str_to_syslog_facility( const char *facility ) { + if( strcmp( facility, "KERN" ) == 0 ) return LOG_KERN; + if( strcmp( facility, "USER" ) == 0 ) return LOG_USER; + if( strcmp( facility, "MAIL" ) == 0 ) return LOG_MAIL; + if( strcmp( facility, "DAEMON" ) == 0 ) return LOG_DAEMON; + if( strcmp( facility, "AUTH" ) == 0 ) return LOG_AUTH; + if( strcmp( facility, "SYSLOG" ) == 0 ) return LOG_SYSLOG; + if( strcmp( facility, "LPR" ) == 0 ) return LOG_LPR; + if( strcmp( facility, "NEWS" ) == 0 ) return LOG_NEWS; + if( strcmp( facility, "UUCP" ) == 0 ) return LOG_UUCP; + if( strcmp( facility, "CRON" ) == 0 ) return LOG_CRON; +#if defined LOG_AUTHPRIV + if( strcmp( facility, "AUTHPRIV" ) == 0 ) return LOG_AUTHPRIV; +#endif +#if defined LOG_FTP + if( strcmp( facility, "FTP" ) == 0 ) return LOG_FTP; +#endif + + return LOG_DAEMON; +} + +const char *log_level_to_str( int level ) { + switch( level ) { + case LOG_EMERG: return "EMERG"; + case LOG_ALERT: return "ALERT"; + case LOG_CRIT: return "CRIT"; + case LOG_ERR: return "ERR"; + case LOG_WARNING: return "WARNING"; + case LOG_NOTICE: return "NOTICE"; + case LOG_INFO: return "INFO"; + case LOG_DEBUG: return "DEBUG"; + case LOG_DEBUG1: return "DEBUG1"; + case LOG_DEBUG2: return "DEBUG2"; + case LOG_DEBUG3: return "DEBUG3"; + case LOG_DEBUG4: return "DEBUG4"; + case LOG_DEBUG5: return "DEBUG5"; + default: return "<unknown>"; + } +} + +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 <syslog.h> /* 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 <stdlib.h> /* for strtol */ +#include <errno.h> /* for errno */ +#include <sys/types.h> /* for pid_t, ssize_t, off_t */ +#include <sys/stat.h> /* for umask */ +#include <fcntl.h> /* for O_RDWR */ +#include <signal.h> /* 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 <sys/types.h> /* 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 <limits.h> + +#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 <sys/types.h> /* for off_t */ +#include <errno.h> /* 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 <fcntl.h> /* 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 <papowell@astart.com>. + * 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 <holger@jhweiss.de>. + * 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 <http://www.jhweiss.de/software/snprintf.html>. + * 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 <holger@jhweiss.de> 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 <holger@jhweiss.de> for C99-snprintf 1.0: + * + * Added a lot of new features, fixed many bugs, and incorporated various + * improvements done by Andrew Tridgell <tridge@samba.org>, Russ Allbery + * <rra@stanford.edu>, Hrvoje Niksic <hniksic@xemacs.org>, Damien Miller + * <djm@mindrot.org>, 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 <holger@jhweiss.de> 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 <me@mutt.org> 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 <roessler@does-not-exist.org> for Mutt 0.89i: + * + * The PGP code was using unsigned hexadecimal formats. Unfortunately, + * unsigned formats simply didn't work. + * + * 1997-10-22 Brandon Long <blong@fiction.net> 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 <blong@fiction.net> 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 <config.h> + * #endif + * #if HAVE_STDARG_H + * #include <stdarg.h> + * #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 + * <http://www.jhweiss.de/software/snprintf.html>. + */ + +#include "port/snprintf.h" + +#if HAVE_CONFIG_H +#include <config.h> +#endif /* HAVE_CONFIG_H */ + +#if TEST_SNPRINTF +#include <math.h> /* For pow(3), NAN, and INFINITY. */ +#include <string.h> /* 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 <stdint.h> 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 <stdio.h> /* 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 <stdarg.h> +#define VA_START(ap, last) va_start(ap, last) +#define VA_SHIFT(ap, value, type) /* No-op for ANSI C. */ +#else /* Assume <varargs.h> is available. */ +#include <varargs.h> +#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 <stdlib.h> /* 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 <errno.h> /* For ERANGE and errno. */ +#include <limits.h> /* For *_MAX. */ +#if HAVE_INTTYPES_H +#include <inttypes.h> /* For intmax_t (if not defined in <stdint.h>). */ +#endif /* HAVE_INTTYPES_H */ +#if HAVE_LOCALE_H +#include <locale.h> /* For localeconv(3). */ +#endif /* HAVE_LOCALE_H */ +#if HAVE_STDDEF_H +#include <stddef.h> /* For ptrdiff_t. */ +#endif /* HAVE_STDDEF_H */ +#if HAVE_STDINT_H +#include <stdint.h> /* 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 <stdarg.h> /* for va_list */ +#endif + +#if HAVE_SYS_TYPES_H +#include <sys/types.h> /* 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 <stdbool.h> +#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 <stdint.h> +#endif /* defined HAVE_STDINT_H */ + +/* Solaris 8 weirdness */ +#if defined HAVE_LINK_H +#include <link.h> +#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 <stdio.h> + +#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 <string.h> + +#include <strings.h> + +#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 <unistd.h> + +#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 <unistd.h> /* for getpid, pipe, write, read */ +#include <errno.h> /* for errno */ +#include <sys/types.h> /* for ssize_t */ +#include <sys/select.h> /* 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 "<unknown>"; + } +} + +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 "<unknown signal>"; + } +} + +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 <stdarg.h> /* for variable arguments */ +#include <signal.h> /* 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 <sys/types.h> /* for pid_t */ +#include <unistd.h> /* for exit, unistd, getuid, getppid */ +#include <stdlib.h> /* 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 <stdarg.h> +#include <stdio.h> + +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 ); +} |