+4579. [func] Logging channels and dnstap output files can now
+ be configured with a "suffix" option, set to
+ either "increment" or "timestamp", indicating
+ whether to use incrementing numbers or timestamps
+ as the file suffix when rolling over a log file.
+ [RT #42838]
+
4578. [security] Some chaining (CNAME or DNAME) responses to upstream
queries could trigger assertion failures.
(CVE-2017-3137) [RT #44734]
const cfg_obj_t *sizeobj = cfg_tuple_get(fileobj, "size");
const cfg_obj_t *versionsobj =
cfg_tuple_get(fileobj, "versions");
+ const cfg_obj_t *suffixobj =
+ cfg_tuple_get(fileobj, "suffix");
isc_int32_t versions = ISC_LOG_ROLLNEVER;
+ isc_log_rollsuffix_t suffix = isc_log_rollsuffix_increment;
isc_offset_t size = 0;
isc_uint64_t maxoffset;
if (versionsobj != NULL && cfg_obj_isuint32(versionsobj))
versions = cfg_obj_asuint32(versionsobj);
- if (versionsobj != NULL && cfg_obj_isstring(versionsobj) &&
+ else if (versionsobj != NULL && cfg_obj_isstring(versionsobj) &&
strcasecmp(cfg_obj_asstring(versionsobj), "unlimited") == 0)
versions = ISC_LOG_ROLLINFINITE;
if (sizeobj != NULL &&
cfg_obj_isuint64(sizeobj) &&
cfg_obj_asuint64(sizeobj) < maxoffset)
size = (isc_offset_t)cfg_obj_asuint64(sizeobj);
+ if (suffixobj != NULL && cfg_obj_isstring(suffixobj) &&
+ strcasecmp(cfg_obj_asstring(suffixobj), "timestamp") == 0)
+ suffix = isc_log_rollsuffix_timestamp;
+
dest.file.stream = NULL;
dest.file.name = cfg_obj_asstring(pathobj);
dest.file.versions = versions;
+ dest.file.suffix = suffix;
dest.file.maximum_size = size;
} else if (syslogobj != NULL) {
int facility = LOG_DAEMON;
dest.file.stream = stderr;
dest.file.name = NULL;
dest.file.versions = ISC_LOG_ROLLNEVER;
+ dest.file.suffix = isc_log_rollsuffix_increment;
dest.file.maximum_size = 0;
}
dns_dtmode_t dmode;
isc_uint64_t max_size = 0;
isc_uint32_t rolls = 0;
+ isc_log_rollsuffix_t suffix = isc_log_rollsuffix_increment;
+
obj = NULL;
CHECKM(ns_config_get(maps, "dnstap-output", &obj),
"'dnstap-output' must be set if 'dnstap' is set");
obj2 = cfg_tuple_get(obj, "versions");
if (obj2 != NULL && cfg_obj_isuint32(obj2)) {
rolls = cfg_obj_asuint32(obj2);
- } else if (obj2 != NULL && cfg_obj_isstring(obj2) &&
- strcasecmp(cfg_obj_asstring(obj2), "unlimited") == 0)
- {
- rolls = ISC_LOG_ROLLINFINITE;
} else {
- rolls = ISC_LOG_ROLLNEVER;
+ rolls = ISC_LOG_ROLLINFINITE;
+ }
+
+ obj2 = cfg_tuple_get(obj, "suffix");
+ if (obj2 != NULL && cfg_obj_isstring(obj2) &&
+ strcasecmp(cfg_obj_asstring(obj2), "timestamp") == 0)
+ {
+ suffix = isc_log_rollsuffix_timestamp;
}
fopt = fstrm_iothr_options_init();
&fopt, &ns_g_server->dtenv),
"unable to create dnstap environment");
- CHECKM(dns_dt_setupfile(ns_g_server->dtenv, max_size, rolls),
+ CHECKM(dns_dt_setupfile(ns_g_server->dtenv,
+ max_size, rolls, suffix),
"unable to set up dnstap logfile");
}
n = sscanf(ptr, "%u", &backups);
if (n != 1U)
return (ISC_R_BADNUMBER);
+ } else {
+ backups = ISC_LOG_ROLLINFINITE;
}
- } else
+ } else {
return (DNS_R_SYNTAX);
+ }
result = isc_task_beginexclusive(server->task);
RUNTIME_CHECK(result == ISC_R_SUCCESS);
*/
options {
- dnstap-output file "/tmp/dnstap.log" size unlimited versions unlimited;
+ dnstap-output file "/tmp/dnstap.log"
+ size unlimited versions unlimited suffix increment;
};
*/
options {
- dnstap-output file "/tmp/dnstap.log" size 10k versions 3;
+ dnstap-output file "/tmp/dnstap.log"
+ size 10k versions 3 suffix timestamp;
};
rm -f ns1/dig.out.test*
rm -f ns1/named_vers
rm -f ns1/named_vers.*
+rm -f ns1/named_ts
+rm -f ns1/named_ts.*
rm -f ns1/named_unlimited
rm -f ns1/named_unlimited.*
--- /dev/null
+/*
+ * Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+options {
+ query-source address 10.53.0.1;
+ notify-source 10.53.0.1;
+ transfer-source 10.53.0.1;
+ port 5300;
+ pid-file "named.pid";
+ listen-on port 5300 {
+ 10.53.0.1;
+ };
+ listen-on-v6 { none; };
+ recursion no;
+ notify yes;
+};
+
+logging {
+ channel default_log {
+ buffered no;
+ file "named_ts" versions 10 size 1000 suffix timestamp; # small size
+ severity debug 100;
+ print-time yes;
+ };
+ category default { default_log; default_debug; };
+ category lame-servers { null; };
+
+ channel query_log {
+ file "query_log";
+ print-time yes;
+ buffered yes;
+ };
+ category queries { query_log; };
+};
+
+controls {
+ inet 127.0.0.1 port 9593 allow {
+ 127.0.0.1/32; ::1/128; }
+ keys { "rndc-key"; };
+};
+
+key "rndc-key" {
+ algorithm hmac-sha256;
+ secret "Am9vCg==";
+};
+
+
+zone "." {
+ type master;
+ file "root.db";
+};
ISOCONF="${THISDIR}/${CONFDIR}/named.iso8601"
ISOCONFUTC="${THISDIR}/${CONFDIR}/named.iso8601-utc"
VERSCONF="${THISDIR}/${CONFDIR}/named.versconf"
+TSCONF="${THISDIR}/${CONFDIR}/named.tsconf"
UNLIMITEDCONF="${THISDIR}/${CONFDIR}/named.unlimited"
PLAINFILE="named_log"
DIRFILE="named_dir"
ISOFILE="named_iso8601"
ISOUTCFILE="named_iso8601_utc"
VERSFILE="named_vers"
+TSFILE="named_ts"
UNLIMITEDFILE="named_unlimited"
PIDFILE="${THISDIR}/${CONFDIR}/named.pid"
myRNDC="$RNDC -c ${THISDIR}/${CONFDIR}/rndc.conf"
status=`expr $status + 1`
fi
+n=`expr $n + 1`
+echo "I: testing timestamped versions ($n)"
+cp $TSCONF named.conf
+# a seconds since epoch version number
+touch $TSFILE.2015010112000012
+t1=`$PERL -e 'print time()."\n";'`
+$myRNDC reconfig > rndc.out.test$n 2>&1
+$DIG version.bind txt ch @10.53.0.1 -p 5300 > dig.out.test$n
+t2=`$PERL -e 'print time()."\n";'`
+t=`expr ${t2:-0} - ${t1:-0}`
+if test ${t:-1000} -gt 5
+then
+ echo "I: testing timestamped versions failed cleanup of old entries took too long ($t secs)"
+ status=`expr $status + 1`
+fi
+if ! grep "status: NOERROR" dig.out.test$n > /dev/null
+then
+ echo "I: testing timestamped versions failed DiG lookup failed"
+ status=`expr $status + 1`
+fi
+if test -f $TSFILE.1480039317
+then
+ echo "I: testing timestamped versions failed $TSFILE.1480039317 not removed"
+ status=`expr $status + 1`
+fi
+
n=`expr $n + 1`
echo "I: testing unlimited versions ($n)"
cp $UNLIMITEDCONF named.conf
[ <command>channel</command> <replaceable>channel_name</replaceable> <command>{</command>
( ( <command>file</command> <replaceable>path_name</replaceable>
[ <command>versions</command> ( <replaceable>number</replaceable> | <option>unlimited</option> ) ]
- [ <command>size</command> <replaceable>size_spec</replaceable> ] )
+ [ <command>size</command> <replaceable>size_spec</replaceable> ]
+ [ <command>suffix</command> ( <option>increment</option> | <option>timestamp</option> ) )
| <command>syslog</command> <replaceable>syslog_facility</replaceable>
| <command>stderr</command>
| <command>null</command> ) <command>;</command>
<para>
The <command>file</command> destination clause directs
- the channel
- to a disk file. It can include limitations
- both on how large the file is allowed to become, and how many
- versions
- of the file will be saved each time the file is opened.
- </para>
-
- <para>
- If you use the <command>versions</command> log file
- option, then
- <command>named</command> will retain that many backup
- versions of the file by
- renaming them when opening. For example, if you choose to keep
- three old versions
- of the file <filename>lamers.log</filename>, then just
- before it is opened
- <filename>lamers.log.1</filename> is renamed to
- <filename>lamers.log.2</filename>, <filename>lamers.log.0</filename> is renamed
- to <filename>lamers.log.1</filename>, and <filename>lamers.log</filename> is
- renamed to <filename>lamers.log.0</filename>.
- You can say <command>versions unlimited</command> to
- not limit
- the number of versions.
- If a <command>size</command> option is associated with
- the log file,
- then renaming is only done when the file being opened exceeds the
- indicated size. No backup versions are kept by default; any
- existing
- log file is simply appended.
- </para>
-
- <para>
- The <command>size</command> option for files is used
- to limit log
- growth. If the file ever exceeds the size, then <command>named</command> will
- stop writing to the file unless it has a <command>versions</command> option
- associated with it. If backup versions are kept, the files are
- rolled as
- described above and a new one begun. If there is no
+ the channel to a disk file. It can include additional
+ arguments to specify how large the file is allowed to
+ become before it is rolled to a backup file
+ (<command>size</command>), how many backup versions of
+ the file will be saved each time this happens
+ (<command>versions</command>), and the format to use
+ for naming backup versions (<command>suffix</command>).
+ </para>
+
+ <para>
+ The <command>size</command> option is used to limit
+ log file growth. If the file ever exceeds the specified
+ size, then <command>named</command> will stop writing to the
+ file unless it has a <command>versions</command> option
+ associated with it. If backup versions are kept, the files
+ are rolled as described below. If there is no
<command>versions</command> option, no more data will
- be written to the log
- until some out-of-band mechanism removes or truncates the log to
- less than the
- maximum size. The default behavior is not to limit the size of
- the
- file.
- </para>
-
- <para>
- Example usage of the <command>size</command> and
- <command>versions</command> options:
+ be written to the log until some out-of-band mechanism
+ removes or truncates the log to less than the maximum size.
+ The default behavior is not to limit the size of the file.
+ </para>
+ <para>
+ File rolling only occurs when the file exceeds the size
+ specified with the <command>size</command> option. No
+ backup versions are kept by default; any existing
+ log file is simply appended. The
+ <command>versions</command> option specifies
+ how many backup versions of the file should be kept.
+ If set to <literal>unlimited</literal>, there is no limit.
+ </para>
+ <para>
+ The <command>suffix</command> option can be set to
+ either <literal>increment</literal> or
+ <literal>timestamp</literal>. If set to
+ <literal>timestamp</literal>, then when a log file is
+ rolled, it is saved with the current timestamp as a
+ file suffix. If set to <literal>increment</literal>,
+ then backup files are saved with incrementing numbers
+ as suffixes; older files are renamed when rolling.
+ For example, if <command>versions</command>
+ is set to 3 and <command>suffix</command> to
+ <literal>increment</literal>, then when
+ <filename>filename.log</filename> reaches the size
+ specified by <command>size</command>,
+ <filename>filename.log.1</filename> is renamed to
+ <filename>filename.log.2</filename>,
+ <filename>filename.log.0</filename> is renamed
+ to <filename>filename.log.1</filename>,
+ and <filename>filename.log</filename> is
+ renamed to <filename>filename.log.0</filename>,
+ whereupon a new <filename>filename.log</filename> is
+ opened.
+ </para>
+
+ <para>
+ Example usage of the <command>size</command>,
+ <command>versions</command>, and <command>suffix</command>
+ options:
</para>
<programlisting>channel an_example_channel {
- file "example.log" versions 3 size 20m;
+ file "example.log" versions 3 size 20m suffix increment;
print-time yes;
print-category yes;
};
</para>
<para>
If the first argument is <literal>file</literal>, then
- two additional options can be added:
+ up to three additional options can be added:
<command>size</command> indicates the size to which a
<command>dnstap</command> log file can grow before being
- rolled to a new file, and <command>versions</command>
- specifies the number of rolled log files to retain. These
- are similar to the <command>size</command> and
- <command>versions</command> options in a
- <command>logging</command> channel. The default
- is to allow <command>dnstap</command> log files to grow to
- any size without rolling.
+ rolled to a new file; <command>versions</command>
+ specifies the number of rolled log files to retain; and
+ <command>suffix</command> indicates whether to retain
+ rolled log files with an incrementing counter as the
+ suffix (<literal>increment</literal>) or with the
+ current timestamp (<literal>timestamp</literal>).
+ These are similar to the <command>size</command>,
+ <command>versions</command>, and <command>suffix</command>
+ options in a <command>logging</comamnd> channel.
+ The default is to allow <command>dnstap</command> log
+ files to grow to any size without rolling.
</para>
<para>
<command>dnstap-output</command> can only be set globally
<command>dnstap</command> logfiles can now be configured to
automatically roll when they reach a specified size. If
<command>dnstap-output</command> is configured with mode
- <literal>file</literal>, then it can take optional
+ <literal>file</command>, then it can take optional
<command>size</command> and <command>versions</command>
key-value arguments to set the logfile rolling parameters.
(These have the same semantics as the corresponding
[RT #44502]
</para>
</listitem>
+ <listitem>
+ <para>
+ Logging channels and <command>dnstap-output</command> files can
+ now be configured with a <command>suffix</command> option,
+ set to either <literal>increment</literal> or
+ <literal>timestamp</literal>, indicating whether log files
+ should be given incrementing suffixes when they roll
+ over (e.g., <filename>logfile.0</filename>,
+ <filename>.1</filename>, <filename>.2</filename>, etc)
+ or suffixes indicating the time of the roll. The default
+ is <literal>increment</literal>. [RT #42838]
+ </para>
+ </listitem>
<listitem>
<para>
<command>dig +ednsopt</command> now accepts the names
category <string> { <string>; ... }; // may occur multiple times
channel <string> {
buffered <boolean>;
- file <quoted_string> [ versions ( "unlimited" | <integer> )
- ] [ size <size> ];
+ file <quoted_string> [ versions ( unlimited | <integer> ) ]
+ [ size <size> ] [ suffix ( increment | timestamp ) ];
null;
print-category <boolean>;
print-severity <boolean>;
hostname ); // not configured
dnstap-output ( file | unix ) <quoted_string> [
size ( unlimited | <size> ) ] [ versions (
- unlimited | <integer> ) ]; // not configured
+ unlimited | <integer> ) ] [ suffix ( increment
+ | timestamp ) ]; // not configured
dnstap-version ( <quoted_string> | none ); // not configured
dscp <integer>;
dual-stack-servers [ port <integer> ] { ( <quoted_string> [ port
"cannot be set with mode unix");
return (ISC_R_FAILURE);
}
+
+ obj2 = cfg_tuple_get(obj, "suffix");
+ if (obj2 != NULL && !cfg_obj_isvoid(obj2) &&
+ dmode == dns_dtmode_unix)
+ {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "dnstap-output suffix "
+ "cannot be set with mode unix");
+ return (ISC_R_FAILURE);
+ }
}
#endif
#include <isc/buffer.h>
#include <isc/file.h>
+#include <isc/log.h>
#include <isc/mem.h>
#include <isc/once.h>
#include <isc/print.h>
isc_region_t version;
char *path;
dns_dtmode_t mode;
- isc_uint64_t max_size;
- isc_uint32_t rolls;
+ isc_offset_t max_size;
+ int rolls;
+ isc_log_rollsuffix_t suffix;
isc_stats_t *stats;
};
REQUIRE(path != NULL);
REQUIRE(envp != NULL && *envp == NULL);
- REQUIRE(foptp!= NULL && *foptp != NULL);
+ REQUIRE(foptp != NULL && *foptp != NULL);
isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSTAP,
DNS_LOGMODULE_DNSTAP, ISC_LOG_INFO,
}
env->mode = mode;
env->max_size = 0;
- env->rolls = ISC_LOG_ROLLNEVER;
+ env->rolls = ISC_LOG_ROLLINFINITE;
env->fopt = *foptp;
*foptp = NULL;
}
isc_result_t
-dns_dt_setupfile(dns_dtenv_t *env, isc_uint64_t max_size, int rolls) {
+dns_dt_setupfile(dns_dtenv_t *env, isc_uint64_t max_size, int rolls,
+ isc_log_rollsuffix_t suffix)
+{
REQUIRE(VALID_DTENV(env));
- if (max_size != 0 && rolls != ISC_LOG_ROLLNEVER &&
- env->mode != dns_dtmode_file)
- {
- return (ISC_R_INVALIDFILE);
+ /*
+ * If we're using unix domain socket mode, then any
+ * change from the default values is invalid.
+ */
+ if (env->mode == dns_dtmode_unix) {
+ if (max_size == 0 && rolls == ISC_LOG_ROLLINFINITE &&
+ suffix == isc_log_rollsuffix_increment)
+ {
+ return (ISC_R_SUCCESS);
+ } else {
+ return (ISC_R_INVALIDFILE);
+ }
}
env->max_size = max_size;
env->rolls = rolls;
+ env->suffix = suffix;
return (ISC_R_SUCCESS);
}
* Check that we can create a new fw object.
*/
fwopt = fstrm_writer_options_init();
- if (fwopt == NULL)
+ if (fwopt == NULL) {
return (ISC_R_NOMEMORY);
+ }
res = fstrm_writer_options_add_content_type(fwopt,
DNSTAP_CONTENT_TYPE,
sizeof(DNSTAP_CONTENT_TYPE) - 1);
- if (res != fstrm_res_success)
+ if (res != fstrm_res_success) {
CHECK(ISC_R_FAILURE);
+ }
if (env->mode == dns_dtmode_file) {
ffwopt = fstrm_file_options_init();
env->path);
fw = fstrm_unix_writer_init(fuwopt, fwopt);
}
- } else
+ } else {
CHECK(ISC_R_NOTIMPLEMENTED);
+ }
- if (fw == NULL)
+ if (fw == NULL) {
CHECK(ISC_R_FAILURE);
+ }
/*
* We are committed here.
generation++;
- if (env->iothr != NULL)
+ if (env->iothr != NULL) {
fstrm_iothr_destroy(&env->iothr);
+ }
+
+ if (roll != 0) {
+ roll = env->rolls;
+ }
- if (env->mode == dns_dtmode_file && roll >= 0) {
+ if (env->mode == dns_dtmode_file && roll != 0) {
/*
* Create a temporary isc_logfile_t structure so we can
* take advantage of the logfile rolling facility.
char *filename = isc_mem_strdup(env->mctx, env->path);
file.name = filename;
file.stream = NULL;
- file.versions = roll != 0 ? roll : ISC_LOG_ROLLINFINITE;
+ file.versions = roll != 0 ? roll : env->rolls;
file.maximum_size = 0;
file.maximum_reached = ISC_FALSE;
+ file.suffix = env->suffix;
result = isc_logfile_roll(&file);
isc_mem_free(env->mctx, filename);
CHECK(result);
if (view->dtenv->max_size != 0) {
struct stat statbuf;
if (stat(view->dtenv->path, &statbuf) >= 0 &&
- (size_t) statbuf.st_size > view->dtenv->max_size) {
+ statbuf.st_size > view->dtenv->max_size)
+ {
dns_dt_reopen(view->dtenv, view->dtenv->rolls);
}
}
struct fstrm_iothr_options;
#endif /* HAVE_DNSTAP */
+#include <isc/log.h>
#include <isc/refcount.h>
#include <isc/region.h>
#include <isc/sockaddr.h>
*/
isc_result_t
-dns_dt_setupfile(dns_dtenv_t *env, isc_uint64_t max_size, int rolls);
+dns_dt_setupfile(dns_dtenv_t *env, isc_uint64_t max_size, int rolls,
+ isc_log_rollsuffix_t suffix);
/*%<
* Sets up the dnstap logfile limits.
*
*
* 'rolls' is the number of rolled files to retain.
*
+ * 'suffix' is the logfile suffix setting, increment or timestamp.
+ *
* Requires:
*
*\li 'env' is a valid dnstap environment.
*/
#define ISC_LOG_ROLLINFINITE (-1)
#define ISC_LOG_ROLLNEVER (-2)
+#define ISC_LOG_MAX_VERSIONS 256
+/*@}*/
+
+/*@{*/
+/*!
+ * \brief Type of suffix used on rolled log files.
+ */
+typedef enum {
+ isc_log_rollsuffix_increment,
+ isc_log_rollsuffix_timestamp
+} isc_log_rollsuffix_t;
/*@}*/
/*!
FILE *stream; /*%< Initialized to NULL for #ISC_LOG_TOFILE. */
const char *name; /*%< NULL for #ISC_LOG_TOFILEDESC. */
int versions; /* >= 0, #ISC_LOG_ROLLNEVER, #ISC_LOG_ROLLINFINITE. */
+ isc_log_rollsuffix_t suffix;
/*%
* stdio's ftell is standardized to return a long, which may well not
* be big enough for the largest file supportable by the operating
#define FILE_NAME(channel) (channel->destination.file.name)
#define FILE_STREAM(channel) (channel->destination.file.stream)
#define FILE_VERSIONS(channel) (channel->destination.file.versions)
+#define FILE_SUFFIX(channel) (channel->destination.file.suffix)
#define FILE_MAXSIZE(channel) (channel->destination.file.maximum_size)
#define FILE_MAXREACHED(channel) (channel->destination.file.maximum_reached)
destination.file.stream = stderr;
destination.file.name = NULL;
destination.file.versions = ISC_LOG_ROLLNEVER;
+ destination.file.suffix = isc_log_rollsuffix_increment;
destination.file.maximum_size = 0;
result = isc_log_createchannel(lcfg, "default_stderr",
ISC_LOG_TOFILEDESC,
destination.file.stream = stderr;
destination.file.name = NULL;
destination.file.versions = ISC_LOG_ROLLNEVER;
+ destination.file.suffix = isc_log_rollsuffix_increment;
destination.file.maximum_size = 0;
result = isc_log_createchannel(lcfg, "default_debug",
ISC_LOG_TOFILEDESC,
isc_mem_strdup(mctx, destination->file.name);
FILE_STREAM(channel) = NULL;
FILE_VERSIONS(channel) = destination->file.versions;
+ FILE_SUFFIX(channel) = destination->file.suffix;
FILE_MAXSIZE(channel) = destination->file.maximum_size;
FILE_MAXREACHED(channel) = ISC_FALSE;
break;
FILE_STREAM(channel) = destination->file.stream;
FILE_MAXSIZE(channel) = 0;
FILE_VERSIONS(channel) = ISC_LOG_ROLLNEVER;
+ FILE_SUFFIX(channel) = isc_log_rollsuffix_increment;
break;
case ISC_LOG_TONULL:
return (ISC_R_SUCCESS);
}
-isc_result_t
-isc_logfile_roll(isc_logfile_t *file) {
- int i, n, greatest;
- char current[PATH_MAX + 1];
- char new[PATH_MAX + 1];
- const char *path;
+static isc_result_t
+remove_old_tsversions(isc_logfile_t *file, int versions) {
isc_result_t result;
+ char *bname, *digit_end;
+ const char *dirname;
+ isc_int64_t version, last = ISC_INT64_MAX;
+ isc_int64_t to_keep[ISC_LOG_MAX_VERSIONS];
+ size_t bnamelen;
+ isc_dir_t dir;
+ char sep = '/';
+#ifdef _WIN32
+ char *bname2;
+#endif
+ /*
+ * It is safe to DE_CONST the file.name because it was copied
+ * with isc_mem_strdup().
+ */
+ bname = strrchr(file->name, sep);
+#ifdef _WIN32
+ bname2 = strrchr(file->name, '\\');
+ if ((bname != NULL && bname2 != NULL && bname2 > bname) ||
+ (bname == NULL && bname2 != NULL)) {
+ bname = bname2;
+ sep = '\\';
+ }
+#endif
+ if (bname != NULL) {
+ *bname++ = '\0';
+ dirname = file->name;
+ } else {
+ DE_CONST(file->name, bname);
+ dirname = ".";
+ }
+ bnamelen = strlen(bname);
- REQUIRE(file != NULL);
+ isc_dir_init(&dir);
+ result = isc_dir_open(&dir, dirname);
/*
- * Do nothing (not even excess version trimming) if ISC_LOG_ROLLNEVER
- * is specified. Apparently complete external control over the log
- * files is desired.
+ * Replace the file separator if it was taken out.
*/
- if (file->versions == ISC_LOG_ROLLNEVER)
- return (ISC_R_SUCCESS);
+ if (bname != file->name) {
+ *(bname - 1) = sep;
+ }
+
+ /*
+ * Return if the directory open failed.
+ */
+ if (result != ISC_R_SUCCESS)
+ return (result);
+
+ if (versions > 0) {
+ /*
+ * First we fill 'to_keep' structure using insertion sort
+ */
+ memset(to_keep, 0, versions * sizeof(long long));
+ while (isc_dir_read(&dir) == ISC_R_SUCCESS) {
+ if (dir.entry.length > bnamelen &&
+ strncmp(dir.entry.name, bname, bnamelen) == 0 &&
+ dir.entry.name[bnamelen] == '.')
+ {
+ version = strtoll(&dir.entry.name[bnamelen + 1],
+ &digit_end, 10);
+ if (*digit_end == '\0') {
+ int i = 0;
+ while (version < to_keep[i] &&
+ i < versions)
+ {
+ i++;
+ }
+ if (i < versions) {
+ memmove(&to_keep[i + 1],
+ &to_keep[i],
+ sizeof(long long) *
+ versions - i - 1);
+ to_keep[i] = version;
+ }
+ }
+ }
+ }
+
+ /*
+ * to_keep[versions - 1] is the last one we want to keep
+ */
+ last = to_keep[versions - 1];
+ isc_dir_reset(&dir);
+ }
+
+ /*
+ * Then we remove all files that we don't want to_keep
+ */
+ while (isc_dir_read(&dir) == ISC_R_SUCCESS) {
+ if (dir.entry.length > bnamelen &&
+ strncmp(dir.entry.name, bname, bnamelen) == 0 &&
+ dir.entry.name[bnamelen] == '.')
+ {
+ version = strtoll(&dir.entry.name[bnamelen + 1],
+ &digit_end, 10);
+ /*
+ * Remove any backup files that exceed versions.
+ */
+ if (*digit_end == '\0' && version < last) {
+ result = isc_file_remove(dir.entry.name);
+ if (result != ISC_R_SUCCESS &&
+ result != ISC_R_FILENOTFOUND)
+ syslog(LOG_ERR, "unable to remove "
+ "log file '%s': %s",
+ dir.entry.name,
+ isc_result_totext(result));
+ }
+ }
+ }
+
+ isc_dir_close(&dir);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+roll_increment(isc_logfile_t *file) {
+ int i, n, greatest;
+ char current[PATH_MAX + 1];
+ char newpath[PATH_MAX + 1];
+ const char *path;
+ isc_result_t result = ISC_R_SUCCESS;
+
+ REQUIRE(file != NULL);
+ REQUIRE(file->versions != 0);
path = file->name;
for (greatest = 0; greatest < INT_MAX; greatest++) {
n = snprintf(current, sizeof(current),
"%s.%u", path, greatest) ;
- if (n >= (int)sizeof(current) || n < 0)
- break;
- if (!isc_file_exists(current))
+ if (n >= (int)sizeof(current) || n < 0 ||
+ !isc_file_exists(current))
+ {
break;
+ }
}
} else {
/*
* version greater than the permitted version.
*/
result = greatest_version(file, file->versions, &greatest);
- if (result != ISC_R_SUCCESS)
+ if (result != ISC_R_SUCCESS) {
return (result);
+ }
/*
* Increment if greatest is not the actual maximum value.
*/
- if (greatest < file->versions - 1)
+ if (greatest < file->versions - 1) {
greatest++;
+ }
}
for (i = greatest; i > 0; i--) {
result = ISC_R_SUCCESS;
n = snprintf(current, sizeof(current), "%s.%u", path, i - 1);
- if (n >= (int)sizeof(current) || n < 0)
+ if (n >= (int)sizeof(current) || n < 0) {
result = ISC_R_NOSPACE;
+ }
if (result == ISC_R_SUCCESS) {
- n = snprintf(new, sizeof(new), "%s.%u", path, i);
- if (n >= (int)sizeof(new) || n < 0)
+ n = snprintf(newpath, sizeof(newpath), "%s.%u",
+ path, i);
+ if (n >= (int)sizeof(newpath) || n < 0) {
result = ISC_R_NOSPACE;
+ }
}
- if (result == ISC_R_SUCCESS)
- result = isc_file_rename(current, new);
- if (result != ISC_R_SUCCESS &&
- result != ISC_R_FILENOTFOUND)
+ if (result == ISC_R_SUCCESS) {
+ result = isc_file_rename(current, newpath);
+ }
+ if (result != ISC_R_SUCCESS && result != ISC_R_FILENOTFOUND) {
syslog(LOG_ERR,
"unable to rename log file '%s.%u' to "
"'%s.%u': %s", path, i - 1, path, i,
isc_result_totext(result));
+ }
}
- if (file->versions != 0) {
- n = snprintf(new, sizeof(new), "%s.0", path);
- if (n >= (int)sizeof(new) || n < 0)
- result = ISC_R_NOSPACE;
- else
- result = isc_file_rename(path, new);
- if (result != ISC_R_SUCCESS &&
- result != ISC_R_FILENOTFOUND)
- syslog(LOG_ERR,
- "unable to rename log file '%s' to '%s.0': %s",
- path, path, isc_result_totext(result));
+ n = snprintf(newpath, sizeof(newpath), "%s.0", path);
+ if (n >= (int)sizeof(newpath) || n < 0) {
+ result = ISC_R_NOSPACE;
+ } else {
+ result = isc_file_rename(path, newpath);
+ }
+ if (result != ISC_R_SUCCESS && result != ISC_R_FILENOTFOUND) {
+ syslog(LOG_ERR,
+ "unable to rename log file '%s' to '%s.0': %s",
+ path, path, isc_result_totext(result));
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+roll_timestamp(isc_logfile_t *file) {
+ int n;
+ char newts[PATH_MAX + 1];
+ char newpath[PATH_MAX + 1];
+ const char *path;
+ isc_time_t now;
+ isc_result_t result = ISC_R_SUCCESS;
+
+ REQUIRE(file != NULL);
+ REQUIRE(file->versions != 0);
+
+ path = file->name;
+
+ /*
+ * First find all the logfiles and remove the oldest ones
+ * Save one fewer than file->versions because we'll be renaming
+ * the existing file to a timestamped version after this.
+ */
+ if (file->versions != ISC_LOG_ROLLINFINITE) {
+ remove_old_tsversions(file, file->versions - 1);
+ }
+
+ /* Then just rename the current logfile */
+ isc_time_now(&now);
+ isc_time_formatshorttimestamp(&now, newts, PATH_MAX + 1);
+ n = snprintf(newpath, sizeof(newpath), "%s.%s", path, newts);
+ if (n >= (int)sizeof(newpath) || n < 0) {
+ result = ISC_R_NOSPACE;
} else {
- result = isc_file_remove(path);
+ result = isc_file_rename(path, newpath);
+ }
+ if (result != ISC_R_SUCCESS && result != ISC_R_FILENOTFOUND) {
+ syslog(LOG_ERR,
+ "unable to rename log file '%s' to '%s.0': %s",
+ path, path, isc_result_totext(result));
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+
+isc_result_t
+isc_logfile_roll(isc_logfile_t *file) {
+ isc_result_t result;
+
+ REQUIRE(file != NULL);
+
+ /*
+ * Do nothing (not even excess version trimming) if ISC_LOG_ROLLNEVER
+ * is specified. Apparently complete external control over the log
+ * files is desired.
+ */
+ if (file->versions == ISC_LOG_ROLLNEVER) {
+ return (ISC_R_SUCCESS);
+ } else if (file->versions == 0) {
+ result = isc_file_remove(file->name);
if (result != ISC_R_SUCCESS &&
result != ISC_R_FILENOTFOUND)
syslog(LOG_ERR, "unable to remove log file '%s': %s",
- path, isc_result_totext(result));
+ file->name, isc_result_totext(result));
+ return (ISC_R_SUCCESS);
}
- return (ISC_R_SUCCESS);
+ switch (file->suffix) {
+ case isc_log_rollsuffix_increment:
+ return (roll_increment(file));
+ case isc_log_rollsuffix_timestamp:
+ return (roll_timestamp(file));
+ default:
+ return (ISC_R_UNEXPECTED);
+ }
}
static isc_result_t
*
*/
+void
+isc_time_formatshorttimestamp(const isc_time_t *t, char *buf, unsigned int len);
+/*%<
+ * Format the time 't' into the buffer 'buf' of length 'len',
+ * using the format "yyyymmddhhmmsssss" userful for file timestamping.
+ * If the text does not fit in the buffer, the result is indeterminate,
+ * but is always guaranteed to be null terminated.
+ *
+ * Requires:
+ *\li 'len' > 0
+ *\li 'buf' points to an array of at least len chars
+ *
+ */
+
ISC_LANG_ENDDECLS
#endif /* ISC_TIME_H */
t->nanoseconds / NS_PER_MS);
}
}
+
+void
+isc_time_formatshorttimestamp(const isc_time_t *t, char *buf, unsigned int len)
+{
+ time_t now;
+ unsigned int flen;
+
+ REQUIRE(len > 0);
+
+ now = (time_t)t->seconds;
+ flen = strftime(buf, len, "%Y%m%d%H%M%S", gmtime(&now));
+ INSIST(flen < len);
+ if (flen > 0U && len - flen >= 5) {
+ flen -= 1; /* rewind one character (Z) */
+ snprintf(buf + flen, len - flen, "%03u",
+ t->nanoseconds / NS_PER_MS);
+ }
+}
*
*/
+void
+isc_time_formatshorttimestamp(const isc_time_t *t, char *buf, unsigned int len);
+/*%<
+ * Format the time 't' into the buffer 'buf' of length 'len',
+ * using the format "yyyymmddhhmmsssss" userful for file timestamping.
+ * If the text does not fit in the buffer, the result is indeterminate,
+ * but is always guaranteed to be null terminated.
+ *
+ * Requires:
+ *\li 'len' > 0
+ *\li 'buf' points to an array of at least len chars
+ *
+ */
+
isc_uint32_t
isc_time_seconds(const isc_time_t *t);
/*%<
isc_time_formatISO8601
isc_time_formatISO8601ms
isc_time_formathttptimestamp
+isc_time_formatshorttimestamp
isc_time_formattimestamp
isc_time_isepoch
isc_time_microdiff
buf[0] = 0;
}
}
+
+void
+isc_time_formatshorttimestamp(const isc_time_t *t, char *buf, unsigned int len)
+{
+ SYSTEMTIME st;
+ char DateBuf[50];
+ char TimeBuf[50];
+
+ /* strtime() format: "%Y%m%d%H%M%SSSS" */
+
+ REQUIRE(len > 0);
+ if (FileTimeToSystemTime(&t->absolute, &st)) {
+ GetDateFormat(LOCALE_NEUTRAL, 0, &st, "yyyyMMdd",
+ DateBuf, 50);
+ GetTimeFormat(LOCALE_NEUTRAL,
+ TIME_NOTIMEMARKER | TIME_FORCE24HOURFORMAT,
+ &st, "hhmmss", TimeBuf, 50);
+ snprintf(buf, len, "%s%s%03u", DateBuf, TimeBuf,
+ st.wMilliseconds);
+ } else {
+ buf[0] = 0;
+ }
+}
static cfg_type_t cfg_type_logfile;
static cfg_type_t cfg_type_logging;
static cfg_type_t cfg_type_logseverity;
+static cfg_type_t cfg_type_logsuffix;
static cfg_type_t cfg_type_logversions;
static cfg_type_t cfg_type_lwres;
static cfg_type_t cfg_type_masterselement;
{
CHECK(cfg_parse_obj(pctx, fields[3].type,
&obj->value.tuple[3]));
+ } else if (strcasecmp(TOKEN_STRING(pctx),
+ "suffix") == 0 &&
+ obj->value.tuple[4] == NULL) {
+ CHECK(cfg_parse_obj(pctx, fields[4].type,
+ &obj->value.tuple[4]));
} else {
cfg_parser_error(pctx, CFG_LOG_NEAR,
"unexpected token");
CHECK(cfg_parse_void(pctx, NULL, &obj->value.tuple[2]));
if (obj->value.tuple[3] == NULL)
CHECK(cfg_parse_void(pctx, NULL, &obj->value.tuple[3]));
+ if (obj->value.tuple[4] == NULL)
+ CHECK(cfg_parse_void(pctx, NULL, &obj->value.tuple[4]));
*ret = obj;
return (ISC_R_SUCCESS);
cfg_print_cstr(pctx, " versions ");
cfg_print_obj(pctx, obj->value.tuple[3]);
}
+ if (obj->value.tuple[4]->type->print != cfg_print_void) {
+ cfg_print_cstr(pctx, " suffix ");
+ cfg_print_obj(pctx, obj->value.tuple[4]);
+ }
}
cfg_print_cstr(pctx, "[ size ( unlimited | <size> ) ]");
cfg_print_cstr(pctx, " ");
cfg_print_cstr(pctx, "[ versions ( unlimited | <integer> ) ]");
+ cfg_print_cstr(pctx, " ");
+ cfg_print_cstr(pctx, "[ suffix ( increment | timestamp ) ]");
}
static const char *dtoutmode_enums[] = { "file", "unix", NULL };
{ "path", &cfg_type_qstring, 0 },
{ "size", &cfg_type_sizenodefault, 0 },
{ "versions", &cfg_type_logversions, 0 },
+ { "suffix", &cfg_type_logsuffix, 0 },
{ NULL, NULL, 0 }
};
&cfg_rep_string, logversions_enums
};
+static const char *logsuffix_enums[] = { "increment", "timestamp", NULL };
+static cfg_type_t cfg_type_logsuffix = {
+ "logsuffix", cfg_parse_enum, cfg_print_ustring, cfg_doc_enum,
+ &cfg_rep_string, &logsuffix_enums
+};
+
static cfg_tuplefielddef_t logfile_fields[] = {
{ "file", &cfg_type_qstring, 0 },
{ "versions", &cfg_type_logversions, 0 },
{ "size", &cfg_type_size, 0 },
+ { "suffix", &cfg_type_logsuffix, 0 },
{ NULL, NULL, 0 }
};
obj->value.tuple[2] == NULL) {
CHECK(cfg_parse_obj(pctx, fields[2].type,
&obj->value.tuple[2]));
+ } else if (strcasecmp(TOKEN_STRING(pctx),
+ "suffix") == 0 &&
+ obj->value.tuple[3] == NULL) {
+ CHECK(cfg_parse_obj(pctx, fields[3].type,
+ &obj->value.tuple[3]));
} else {
break;
}
CHECK(cfg_parse_void(pctx, NULL, &obj->value.tuple[1]));
if (obj->value.tuple[2] == NULL)
CHECK(cfg_parse_void(pctx, NULL, &obj->value.tuple[2]));
+ if (obj->value.tuple[3] == NULL)
+ CHECK(cfg_parse_void(pctx, NULL, &obj->value.tuple[3]));
*ret = obj;
return (ISC_R_SUCCESS);
cfg_print_cstr(pctx, " size ");
cfg_print_obj(pctx, obj->value.tuple[2]);
}
+ if (obj->value.tuple[3]->type->print != cfg_print_void) {
+ cfg_print_cstr(pctx, " suffix ");
+ cfg_print_obj(pctx, obj->value.tuple[3]);
+ }
}
UNUSED(type);
cfg_print_cstr(pctx, "<quoted_string>");
cfg_print_cstr(pctx, " ");
- cfg_print_cstr(pctx, "[ versions ( \"unlimited\" | <integer> ) ]");
+ cfg_print_cstr(pctx, "[ versions ( unlimited | <integer> ) ]");
cfg_print_cstr(pctx, " ");
cfg_print_cstr(pctx, "[ size <size> ]");
+ cfg_print_cstr(pctx, " ");
+ cfg_print_cstr(pctx, "[ suffix ( increment | timestamp ) ]");
}
static cfg_type_t cfg_type_logfile = {