diff -ur dhcp-3.0.5-orig/common/conflex.c dhcp-3.0.5/common/conflex.c --- dhcp-3.0.5-orig/common/conflex.c 2006-02-23 09:43:27.000000000 +1100 +++ dhcp-3.0.5/common/conflex.c 2007-06-12 10:30:28.000000000 +1000 @@ -746,6 +746,8 @@ return FAILOVER; if (!strcasecmp (atom + 1, "ree")) return TOKEN_FREE; + if (!strcasecmp (atom + 1, "ile")) + return TOKEN_FILE; break; case 'g': if (!strcasecmp (atom + 1, "iaddr")) diff -ur dhcp-3.0.5-orig/includes/dhcpd.h dhcp-3.0.5/includes/dhcpd.h --- dhcp-3.0.5-orig/includes/dhcpd.h 2006-05-18 06:16:59.000000000 +1000 +++ dhcp-3.0.5/includes/dhcpd.h 2007-06-12 10:30:28.000000000 +1000 @@ -497,6 +497,8 @@ DHO_HOST_NAME } #endif +#define FNAME_BUFFER_LEN 512 + struct group_object { OMAPI_OBJECT_PREAMBLE; @@ -910,6 +912,8 @@ #define _PATH_DHCPD_DB "dhcpd.leases" #undef _PATH_DHCPD_PID #define _PATH_DHCPD_PID "dhcpd.pid" +#undef _PATH_DHCPD_START_LOCK +#define _PATH_DHCPD_START_LOCK "dhcpd-startup" #else #ifndef _PATH_DHCPD_CONF #define _PATH_DHCPD_CONF "/etc/dhcpd.conf" @@ -936,6 +940,10 @@ #define _PATH_DHCLIENT_PID "/var/run/dhclient.pid" #endif +#ifndef _PATH_DHCPD_START_LOCK +#define _PATH_DHCPD_START_LOCK "/var/lock/dhcpd-startup" +#endif + #ifndef _PATH_DHCLIENT_DB #define _PATH_DHCLIENT_DB "/etc/dhclient.leases" #endif @@ -1137,6 +1145,8 @@ extern int dhcp_max_agent_option_packet_length; +extern int use_next_lease_file; + int main PROTO ((int, char **, char **)); void postconf_initialization (int); void postdb_startup (void); @@ -1159,13 +1169,13 @@ /* confpars.c */ void parse_trace_setup (void); isc_result_t readconf PROTO ((void)); -isc_result_t read_conf_file (const char *, struct group *, int, int); +isc_result_t read_conf_file (const char *, struct group *, int, int, char *); #if defined (TRACING) void trace_conf_input (trace_type_t *, unsigned, char *); void trace_conf_stop (trace_type_t *ttype); #endif isc_result_t conf_file_subparse (struct parse *, struct group *, int); -isc_result_t lease_file_subparse (struct parse *); +isc_result_t lease_file_subparse (struct parse *, char *); int parse_statement PROTO ((struct parse *, struct group *, int, struct host_decl *, int)); #if defined (FAILOVER_PROTOCOL) diff -ur dhcp-3.0.5-orig/includes/dhctoken.h dhcp-3.0.5/includes/dhctoken.h --- dhcp-3.0.5-orig/includes/dhctoken.h 2005-09-23 02:19:57.000000000 +1000 +++ dhcp-3.0.5/includes/dhctoken.h 2007-06-12 10:32:07.000000000 +1000 @@ -309,7 +309,8 @@ DOMAIN_NAME = 613, DO_FORWARD_UPDATE = 614, KNOWN_CLIENTS = 615, - ATSFP = 616 + ATSFP = 616, + TOKEN_FILE = 617 }; #define is_identifier(x) ((x) >= FIRST_TOKEN && \ diff -ur dhcp-3.0.5-orig/server/confpars.c dhcp-3.0.5/server/confpars.c --- dhcp-3.0.5-orig/server/confpars.c 2006-07-21 02:02:52.000000000 +1000 +++ dhcp-3.0.5/server/confpars.c 2007-06-12 10:30:28.000000000 +1000 @@ -39,6 +39,8 @@ #include "dhcpd.h" +static int parse_next_file (struct parse *cfile, char *filename); + static TIME parsed_time; static unsigned char global_host_once = 1; @@ -63,34 +65,56 @@ isc_result_t readconf () { - return read_conf_file (path_dhcpd_conf, root_group, ROOT_GROUP, 0); + return read_conf_file (path_dhcpd_conf, root_group, ROOT_GROUP, 0, (char *)0); } -isc_result_t read_conf_file (const char *filename, struct group *group, - int group_type, int leasep) + +/* last_lease_filename is either a buffer of length FNAME_BUFFER_LEN + or a NULL pointer. */ + +isc_result_t read_conf_file (const char *first_filename, struct group *group, + int group_type, int leasep, + char *last_lease_filename) { int file; struct parse *cfile; isc_result_t status; + char filename [FNAME_BUFFER_LEN]; #if defined (TRACING) char *fbuf, *dbuf; off_t flen; int result; unsigned tflen, ulen; trace_type_t *ttype; - +#endif + + strncpy (filename, first_filename, FNAME_BUFFER_LEN); + if (filename [FNAME_BUFFER_LEN - 1] != '\0') + log_fatal ("read_conf_file: file path too long"); + + if (leasep && last_lease_filename) { + strncpy (last_lease_filename, first_filename, + FNAME_BUFFER_LEN); + if (last_lease_filename[ FNAME_BUFFER_LEN - 1 ] != '\0') + log_fatal ("lease file path too long"); + } + +#if defined (TRACING) if (leasep) ttype = trace_readleases_type; else ttype = trace_readconf_type; - +#endif + + do { +#if defined (TRACING) /* If we're in playback, we need to snarf the contents of the named file out of the playback file rather than trying to open and read it. */ if (trace_playback ()) { dbuf = (char *)0; tflen = 0; - status = trace_get_file (ttype, filename, &tflen, &dbuf); + status = trace_get_file (ttype, first_filename, &tflen, &dbuf); if (status != ISC_R_SUCCESS) return status; ulen = tflen; @@ -111,7 +135,7 @@ if ((file = open (filename, O_RDONLY)) < 0) { if (leasep) { log_error ("Can't open lease database %s: %m --", - path_dhcpd_db); + filename); log_error (" check for failed database %s!", "rewrite attempt"); log_error ("Please read the dhcpd.leases manual%s", @@ -164,14 +188,27 @@ #else new_parse (&cfile, file, (char *)0, 0, filename, 0); #endif - if (leasep) - status = lease_file_subparse (cfile); + if (leasep) { + *filename = '\0'; + status = lease_file_subparse (cfile, filename); + + if (strlen (filename) > 0 ) { + strncpy (last_lease_filename, filename, + FNAME_BUFFER_LEN); + if (last_lease_filename [FNAME_BUFFER_LEN - 1] + != '\0') { + log_fatal ("read_conf_file: lease file " + "path too long"); + } + } + } else status = conf_file_subparse (cfile, group, group_type); end_parse (&cfile); #if defined (TRACING) dfree (dbuf, MDL); #endif + } while (leasep && status == ISC_R_SUCCESS && *filename); return status; } @@ -196,7 +233,7 @@ trace_write_packet (ttype, len, data, MDL); new_parse (&cfile, -1, fbuf, flen, data, 0); if (ttype == trace_readleases_type) - lease_file_subparse (cfile); + lease_file_subparse (cfile, (char *)0); else conf_file_subparse (cfile, root_group, ROOT_GROUP); end_parse (&cfile); @@ -249,7 +286,7 @@ | lease-declaration | lease-declarations lease-declaration */ -isc_result_t lease_file_subparse (struct parse *cfile) +isc_result_t lease_file_subparse (struct parse *cfile, char *next_filename) { const char *val; enum dhcp_token token; @@ -276,6 +313,8 @@ parse_failover_state_declaration (cfile, (dhcp_failover_state_t *)0); #endif + } else if (token == TOKEN_NEXT) { + parse_next_file (cfile, next_filename); } else { log_error ("Corrupt lease file - possible data loss!"); skip_to_semi (cfile); @@ -351,7 +390,7 @@ parse_warn (cfile, "filename string expected."); skip_to_semi (cfile); } else { - status = read_conf_file (val, group, type, 0); + status = read_conf_file (val, group, type, 0, (char *)0); if (status != ISC_R_SUCCESS) parse_warn (cfile, "%s: bad parse.", val); parse_semi (cfile); @@ -3348,3 +3387,48 @@ return status; } +int parse_next_file (struct parse *cfile, char *filename) +{ + enum dhcp_token token; + const char *val; + + if (filename == NULL) { + log_error ("parse_next_file: got unexpected NULL pointer"); + skip_to_semi (cfile); + return 0; + } + + token = next_token (&val, (unsigned *)0, cfile); + if (token != TOKEN_FILE) { + parse_warn (cfile, "expecting \"file\""); + skip_to_semi (cfile); + return 0; + } + + token = next_token (&val, (unsigned *)0, cfile); + if (token == STRING) { + strncpy (filename, val, FNAME_BUFFER_LEN); + if (filename [FNAME_BUFFER_LEN - 1] != '\0') + log_fatal ("next file name '%s' too long", val); + } else { + parse_warn (cfile, "expecting next file name."); + skip_to_semi (cfile); + return 0; + } + + token = next_token (&val, (unsigned *)0, cfile); + if (token != SEMI) { + parse_warn (cfile, "semicolon expected."); + skip_to_semi (cfile); + return 0; + } + + token = peek_token (&val, (unsigned *)0, cfile); + if (token == END_OF_FILE) { + token = next_token (&val, (unsigned *)0, cfile); + return 1; + } else { + parse_warn (cfile, "expecting no text after next file name."); + return 0; + } +} diff -ur dhcp-3.0.5-orig/server/db.c dhcp-3.0.5/server/db.c --- dhcp-3.0.5-orig/server/db.c 2006-07-20 02:45:30.000000000 +1000 +++ dhcp-3.0.5/server/db.c 2007-06-12 10:30:28.000000000 +1000 @@ -41,7 +41,9 @@ #include #include "version.h" -FILE *db_file; +int new_lease_or_journal_lease_file (void); + +static FILE *db_file; static int counting = 0; static int count = 0; @@ -63,7 +65,7 @@ /* If the lease file is corrupt, don't try to write any more leases until we've written a good lease file. */ if (lease_file_is_corrupt) - if (!new_lease_file ()) + if (!new_lease_or_journal_lease_file ()) return 0; if (counting) @@ -326,7 +328,7 @@ /* If the lease file is corrupt, don't try to write any more leases until we've written a good lease file. */ if (lease_file_is_corrupt) - if (!new_lease_file ()) + if (!new_lease_or_journal_lease_file ()) return 0; if (!db_printable (host -> name)) @@ -472,7 +474,7 @@ /* If the lease file is corrupt, don't try to write any more leases until we've written a good lease file. */ if (lease_file_is_corrupt) - if (!new_lease_file ()) + if (!new_lease_or_journal_lease_file ()) return 0; if (!db_printable (group -> name)) @@ -537,7 +539,7 @@ int errors = 0; if (lease_file_is_corrupt) - if (!new_lease_file ()) + if (!new_lease_or_journal_lease_file ()) return 0; errno = 0; @@ -646,7 +648,7 @@ int i; if (lease_file_is_corrupt) - if (!new_lease_file ()) + if (!new_lease_or_journal_lease_file ()) return 0; if (!class -> superclass) { @@ -724,7 +726,7 @@ if (count && cur_time - write_time > 3600) { count = 0; write_time = cur_time; - new_lease_file (); + new_lease_or_journal_lease_file (); } return 1; } @@ -733,13 +735,20 @@ int testp; { isc_result_t status; + char last_lease_filename [FNAME_BUFFER_LEN]; + strncpy (last_lease_filename, path_dhcpd_db, FNAME_BUFFER_LEN); + if (last_lease_filename [FNAME_BUFFER_LEN - 1] != '\0') + log_fatal ("lease file name longer than storage available"); #if defined (TRACING) + if (use_next_lease_file && (trace_playback () || trace_record ())) + log_fatal ("use-next-file option is incompatible with tracing"); + if (!trace_playback ()) { #endif /* Read in the existing lease file... */ - status = read_conf_file (path_dhcpd_db, - (struct group *)0, 0, 1); + status = read_conf_file (path_dhcpd_db, (struct group *)0, + 0, 1, last_lease_filename); /* XXX ignore status? */ #if defined (TRACING) } @@ -754,7 +763,10 @@ } #endif if (!testp) { - db_file = fopen (path_dhcpd_db, "a"); + if (use_next_lease_file) + db_file = fopen (last_lease_filename, "a"); + else + db_file = fopen (path_dhcpd_db, "a"); if (!db_file) log_fatal ("Can't open %s for append.", path_dhcpd_db); expire_all_pools (); @@ -764,7 +776,7 @@ else #endif GET_TIME (&write_time); - new_lease_file (); + new_lease_or_journal_lease_file (); } } @@ -899,6 +911,115 @@ return 0; } +/* $newfilename = strftime "$path_dhcpd_db%FT%T" time */ +/* print $db_file q{next file "$newfilename";\n} or die; */ +/* close $db_file; */ +/* open $db_file, '>', $newfilename or die; */ +/* write preamble; */ + +/* + Always creates a file of the form shown above; never creates a file + of the name $path_dhcpd_db. + + new_journal_lease_file() should only be called if + use_next_lease_file is true. + + Expect db_file to be initialised. */ + +int new_journal_lease_file (void) +{ + TIME t; + struct tm *tm; + int db_validity; + char iso_8601_date[21]; + char newfname [512]; + + GET_TIME (&t); + + db_validity = lease_file_is_corrupt; + + tm = localtime(&t);; + if (tm == NULL) { + log_error ("new_journal_lease_file: localtime() failed: %m"); + return 0; + } + if (strftime (iso_8601_date, sizeof iso_8601_date, "%Y-%m-%dT%T", tm) == 0) { + log_error ("new_journal_lease_file: strftime() failed: %m"); + return 0; + } + if (snprintf (newfname, sizeof newfname, "%s.%s", + path_dhcpd_db, iso_8601_date) >= sizeof newfname) + log_fatal("new_lease_file: lease file path too long"); + + if (!db_file) { + log_error ("new_journal_lease_file: expected lease file to be open"); + return 0; + } + + if (fprintf (db_file, "next file \"%s\";\n", newfname) <= 0) { + log_error ("Can't write next file statement: %m"); + goto fail; + } + fclose (db_file); + + if ((db_file = fopen(newfname, "w")) == NULL) { + log_error ("Can't fopen new lease file '%s': %m", newfname); + return 0; + } + + /* Write an introduction so people don't complain about time + being off. */ + errno = 0; + fprintf (db_file, "# All times in this file are in UTC (GMT), not %s", + "your local timezone. This is\n"); + if (errno != 0) + goto fail; + fprintf (db_file, "# not a bug, so please don't ask about it. %s", + "There is no portable way to\n"); + if (errno != 0) + goto fail; + fprintf (db_file, "# store leases in the local timezone, so please %s", + "don't request this as a\n"); + if (errno != 0) + goto fail; + fprintf (db_file, "# feature. If this is inconvenient or %s", + "confusing to you, we sincerely\n"); + if (errno != 0) + goto fail; + fprintf (db_file, "# apologize. Seriously, though - don't ask.\n"); + if (errno != 0) + goto fail; + fprintf (db_file, "# The format of this file is documented in the %s", + "dhcpd.leases(5) manual page.\n"); + if (errno != 0) + goto fail; + fprintf (db_file, "# This lease file was written by isc-dhcp-%s\n\n", + DHCP_VERSION); + if (errno != 0) + goto fail; + + /* At this point we have a new lease file that, so far, could not + * be described as either corrupt nor valid. + */ + lease_file_is_corrupt = 0; + + counting = 1; + return 1; + + fail: + lease_file_is_corrupt = db_validity; + unlink (newfname); + return 0; +} + +int new_lease_or_journal_lease_file (void) +{ + if (!use_next_lease_file) + return new_lease_file (); + else + return new_journal_lease_file (); +} + int group_writer (struct group_object *group) { if (!write_group (group)) diff -ur dhcp-3.0.5-orig/server/dhcpd.8 dhcp-3.0.5/server/dhcpd.8 --- dhcp-3.0.5-orig/server/dhcpd.8 2006-07-14 03:29:34.000000000 +1000 +++ dhcp-3.0.5/server/dhcpd.8 2007-06-12 10:30:28.000000000 +1000 @@ -66,6 +66,7 @@ .I pid-file ] [ +[ .B -tf .I trace-output-file ] @@ -73,6 +74,15 @@ .B -play .I trace-playback-file ] +| +[ +.B -use-next-file +] +] +[ +.B -startup-lock-file +.I lock-file-path +] [ .I if0 [ @@ -239,6 +249,51 @@ your existing lease file with its test data. The DHCP server will refuse to operate in playback mode unless you specify an alternate lease file. +.PP +The \fB-use-next-file\fR option supports a new feature that avoids the +need for the server to stop while rewriting the leases file. This is +particularly useful with failover pairs managing a large number of +leases, which require the pair to enter the communications interrupted +state for a number of minutes, after which the servers occasionally +have not been able to resume normal communication. +.PP +The \fB-use-next-file\fR option causes the server to write a new type +of top level lease statement: +.nf +.sp 1 + next file "next_lease_file_name"; +.fi +When the server reads this statement, it will close the current leases +file and open up the file whose name is specified in the next file +statement. Note that when the server starts up, it will behave in +this way while \fIreading\fR the existing lease file(s), regardless of +whether the \fB-use-next-file\fR option is specified. +.PP +The \fB-use-next-file\fR option determines behaviour when +\fIwriting\fR lease files during normal operation after startup. +Without the option, the server stops every hour, writes its leases +from memory into a new file, then moves it over the top of the +original lease file. With this option, at regular intervals, the +server appends a next file statement to the end of the current lease +file and then opens up this new file, and appends lease information to +this file. +.PP +An external program must perform the compaction of this "chain" of +lease files. +.PP +The \fB-startup-lock-file\fR option specifies a lock file that will be +created and flock(2)ed exclusively during startup while dhcpd reads +its leases files. The file will be unlocked when startup is completed +and dhcpd is ready to daemonise and handle requests. This provides a +way for an external process (used with the \fB-use-next-file\fR +option) to know when not to change the chain of lease files. +.PP +If an external program renames or deletes files in the chain of lease +files while dhcdp is starting up, dhcpd is likely to encounter a +\Finext file\fR statement that points to a file that no longer exists. +In such a case, dhcpd would terminate. The lock file provides a +simple way for the programs to communicate about when dhcpd is in its +startup phase. .SH CONFIGURATION The syntax of the dhcpd.conf(5) file is discussed separately. This section should be used as an overview of the configuration process, diff -ur dhcp-3.0.5-orig/server/dhcpd.c dhcp-3.0.5/server/dhcpd.c --- dhcp-3.0.5-orig/server/dhcpd.c 2006-07-18 01:23:44.000000000 +1000 +++ dhcp-3.0.5/server/dhcpd.c 2007-06-12 10:30:28.000000000 +1000 @@ -46,6 +46,7 @@ #include "dhcpd.h" #include "version.h" #include +#include static void usage PROTO ((void)); @@ -150,8 +151,10 @@ const char *path_dhcpd_conf = _PATH_DHCPD_CONF; const char *path_dhcpd_db = _PATH_DHCPD_DB; const char *path_dhcpd_pid = _PATH_DHCPD_PID; +const char *path_startup_lock_file = _PATH_DHCPD_START_LOCK; int dhcp_max_agent_option_packet_length = DHCP_MTU_MAX; +int use_next_lease_file = 0; static omapi_auth_key_t *omapi_key = (omapi_auth_key_t *)0; int omapi_port; @@ -221,6 +224,8 @@ int no_dhcpd_conf = 0; int no_dhcpd_db = 0; int no_dhcpd_pid = 0; + int no_dhcpd_startup_lock = 0; + int fd; #if defined (TRACING) char *traceinfile = (char *)0; char *traceoutfile = (char *)0; @@ -334,6 +339,13 @@ traceinfile = argv [i]; trace_replay_init (); #endif /* TRACING */ + } else if (!strcmp (argv [i], "-use-next-file")) { + use_next_lease_file = 1; + } else if (!strcmp (argv [i], "-startup-lock-file")) { + if (++i == argc) + usage (); + path_startup_lock_file = argv [i]; + no_dhcpd_startup_lock = 1; } else if (argv [i][0] == '-') { usage (); } else { @@ -364,6 +376,9 @@ if (!no_dhcpd_pid && (s = getenv ("PATH_DHCPD_PID"))) { path_dhcpd_pid = s; } + if (!no_dhcpd_startup_lock && (s = getenv ("PATH_DHCPD_START_LOCK"))) { + path_startup_lock_file = s; + } if (!quiet) { log_info ("%s %s", message, DHCP_VERSION); @@ -376,6 +391,9 @@ } #if defined (TRACING) + if (use_next_lease_file && (traceinfile || traceoutfile)) + log_fatal ("use-next-file option is incompatible with tracing"); + trace_init (set_time, MDL); if (traceoutfile) { result = trace_begin (traceoutfile, MDL); @@ -499,9 +517,24 @@ group_write_hook = group_writer; - /* Start up the database... */ + /* Start up the database creating a lock file to indicate + to external processes not to modify the chain of lease files */ + fd = open (path_startup_lock_file, O_WRONLY | O_TRUNC | O_CREAT, 0644); + if (fd < 0) + log_fatal ("Can't open startup lock file '%s': %m", + path_startup_lock_file); + if (flock (fd, LOCK_EX) != 0) + log_fatal ("Can't lock startup lock file '%s': %m", + path_startup_lock_file); + db_startup (lftest); + if (flock (fd, LOCK_UN) != 0) + log_fatal ("Can't unlock startup lock file '%s': %m", + path_startup_lock_file); + + close (fd); + if (lftest) exit (0); @@ -881,14 +914,17 @@ log_info (copyright); log_info (arr); - log_fatal ("Usage: dhcpd [-p ] [-d] [-f]%s%s%s%s", + log_fatal ("Usage: dhcpd [-p ] [-d] [-f]%s%s%s%s%s%s", "\n [-cf config-file] [-lf lease-file]", #if defined (TRACING) - "\n [-tf trace-output-file]", - "\n [-play trace-input-file]", + "\n [ [-tf trace-output-file]", + "\n [-play trace-input-file] ]", + "\n | ", #else "", "", + "\n ", #endif /* TRACING */ + "[-use-next-file] [-startup-lock-file lock-file-path]", "\n [-t] [-T] [-s server] [if0 [...ifN]]"); } diff -ur dhcp-3.0.5-orig/server/dhcpd.leases.5 dhcp-3.0.5/server/dhcpd.leases.5 --- dhcp-3.0.5-orig/server/dhcpd.leases.5 2004-06-11 03:59:53.000000000 +1000 +++ dhcp-3.0.5/server/dhcpd.leases.5 2007-06-12 10:30:28.000000000 +1000 @@ -245,6 +245,25 @@ \fBpotential-conflict\fR, \fBrecover\fR, \fBrecover-done\fR, \fBshutdown\fR, \fBpaused\fR, and \fBstartup\fR. .B DBDIR/dhcpd.leases +.SH THE NEXT FILE STATEMENT +This version of dhcp supports a new top level statement +.PP +.B next file "\fInext_file_name\fR"; +.PP +This statement should appear at the end of a leases file. +.PP +The name \fInext_file_name\fR specifies an absolute path, i.e., it +begins with a "\fC/\fR". +.PP +On startup, the dhcp server will close the current file, then open the +leases file specified by "\fInext_file_name\fR" and continue parsing +that file. There can be an unlimited chain of such files. +.PP +The purpose of the \fBnext file\fR option is to allow the server to +run continuously without needing to stop and compact its leases file. +However, it depends on an external program to perform this compaction +of the leases files. A Perl program \fBdhcp-aggregator\fR is +available for this. .SH SEE ALSO dhcpd(8), dhcp-options(5), dhcp-eval(5), dhcpd.conf(5), RFC2132, RFC2131. .SH AUTHOR