]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
[master] timestamp suffixes for log files
authorEvan Hunt <each@isc.org>
Thu, 9 Mar 2017 07:20:40 +0000 (23:20 -0800)
committerEvan Hunt <each@isc.org>
Thu, 9 Mar 2017 07:20:40 +0000 (23:20 -0800)
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]

22 files changed:
CHANGES
bin/named/logconf.c
bin/named/server.c
bin/tests/system/dnstap/good-size-unlimited.conf
bin/tests/system/dnstap/good-size-version.conf
bin/tests/system/logfileconfig/clean.sh
bin/tests/system/logfileconfig/ns1/named.tsconf [new file with mode: 0644]
bin/tests/system/logfileconfig/tests.sh
doc/arm/Bv9ARM-book.xml
doc/arm/notes.xml
doc/misc/options
lib/bind9/check.c
lib/dns/dnstap.c
lib/dns/include/dns/dnstap.h
lib/isc/include/isc/log.h
lib/isc/log.c
lib/isc/unix/include/isc/time.h
lib/isc/unix/time.c
lib/isc/win32/include/isc/time.h
lib/isc/win32/libisc.def.in
lib/isc/win32/time.c
lib/isccfg/namedconf.c

diff --git a/CHANGES b/CHANGES
index edc11d38f36aff689181097a3bd69c49c3a3d0bc..a6e60e0cffcc98a355c780d07bd79ce622cc8fc4 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,10 @@
+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]
index 02d4dec39fb7fac2679488483a3160c2ecfda3ee..2f52affcb81e148c5eefb4e3f83bddcaa8268885 100644 (file)
@@ -132,7 +132,10 @@ channel_fromconf(const cfg_obj_t *channel, isc_logconfig_t *logconfig) {
                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;
 
@@ -155,16 +158,21 @@ channel_fromconf(const cfg_obj_t *channel, isc_logconfig_t *logconfig) {
 
                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;
@@ -182,6 +190,7 @@ channel_fromconf(const cfg_obj_t *channel, isc_logconfig_t *logconfig) {
                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;
        }
 
index b512b5ddfb718c288645af09c02030e4f9193522..d9f779a3d1c452c3ba991f9697ff11d1ba4c5173 100644 (file)
@@ -3090,6 +3090,8 @@ configure_dnstap(const cfg_obj_t **maps, dns_view_t *view) {
                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");
@@ -3126,12 +3128,15 @@ configure_dnstap(const cfg_obj_t **maps, dns_view_t *view) {
                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();
@@ -3202,7 +3207,8 @@ configure_dnstap(const cfg_obj_t **maps, dns_view_t *view) {
                                     &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");
        }
 
@@ -13730,9 +13736,12 @@ ns_server_dnstap(ns_server_t *server, isc_lex_t *lex, isc_buffer_t **text) {
                        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);
index 7b04fbb347bb7aa92605a39fdd0585c90e161132..f520716dc8ac830a58ac6329e59a3e8c3b3240c7 100644 (file)
@@ -7,5 +7,6 @@
  */
 
 options {
-       dnstap-output file "/tmp/dnstap.log" size unlimited versions unlimited;
+       dnstap-output file "/tmp/dnstap.log"
+               size unlimited versions unlimited suffix increment;
 };
index 0a4573d9502bb712122cd613b9415c32d5ca9d73..46022b73169ca4c1812d726779b3a5b89c315b90 100644 (file)
@@ -7,5 +7,6 @@
  */
 
 options {
-       dnstap-output file "/tmp/dnstap.log" size 10k versions 3;
+       dnstap-output file "/tmp/dnstap.log"
+               size 10k versions 3 suffix timestamp;
 };
index aa743c83ad3feecb9d213e04a55c5d5e286ff4e0..98da66c030f61b1232b2d8d69199e5b09e068523 100644 (file)
@@ -23,5 +23,7 @@ rm -f ns1/rndc.out.test*
 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.*
diff --git a/bin/tests/system/logfileconfig/ns1/named.tsconf b/bin/tests/system/logfileconfig/ns1/named.tsconf
new file mode 100644 (file)
index 0000000..877de56
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * 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";
+};
index 85ad115408b6b25a76b683cb212775e9e5ed8d41..298bce1dafe516e972bd649369da1b232045ac50 100644 (file)
@@ -19,6 +19,7 @@ PLAINCONF="${THISDIR}/${CONFDIR}/named.plainconf"
 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"
@@ -28,6 +29,7 @@ DLFILE="named_deflog"
 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"
@@ -335,6 +337,32 @@ then
        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
index ca1b17e33975fd77cd9b18b75cb1f7077ebe53ea..dbf0e86f5548660f274a2aacaddb8138d7c79621 100644 (file)
@@ -3638,7 +3638,8 @@ $ORIGIN 0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa.
   [ <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>
@@ -3733,61 +3734,68 @@ $ORIGIN 0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa.
 
          <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;
 };
@@ -5004,16 +5012,20 @@ badresp:1,adberr:0,findfail:0,valfail:0]
              </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
index 84b70b195ecd2e948a94a6ece5ed8addfc7e1791..0e4109d1462a5c3118317bfa46adfece19502fd1 100644 (file)
          <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
index 68dd47ba0f791443d03ae7881ff962263093b5c7..a2107304143068c1646e36927f9f87c281d9fa1b 100644 (file)
@@ -33,8 +33,8 @@ logging {
         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>;
@@ -155,7 +155,8 @@ options {
             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
index 8b4ef5434046f6ec3ecb23c8028d0ef38ceadb3c..ab5f6ae3d66296172f58316923bcccf5bb803da1 100644 (file)
@@ -1476,6 +1476,16 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx,
                                    "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
 
index e8b4564f64fa7e26a228588e83feac662d51abdd..82148f6159a2c26b926a771de9c7e3cba3cc996d 100644 (file)
@@ -50,6 +50,7 @@
 
 #include <isc/buffer.h>
 #include <isc/file.h>
+#include <isc/log.h>
 #include <isc/mem.h>
 #include <isc/once.h>
 #include <isc/print.h>
@@ -104,8 +105,9 @@ struct dns_dtenv {
        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;
 };
 
@@ -185,7 +187,7 @@ dns_dt_create(isc_mem_t *mctx, dns_dtmode_t mode, const char *path,
 
        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,
@@ -244,7 +246,7 @@ dns_dt_create(isc_mem_t *mctx, dns_dtmode_t mode, const char *path,
        }
        env->mode = mode;
        env->max_size = 0;
-       env->rolls = ISC_LOG_ROLLNEVER;
+       env->rolls = ISC_LOG_ROLLINFINITE;
        env->fopt = *foptp;
        *foptp = NULL;
 
@@ -279,17 +281,28 @@ dns_dt_create(isc_mem_t *mctx, dns_dtmode_t mode, const char *path,
 }
 
 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);
 }
@@ -310,14 +323,16 @@ dns_dt_reopen(dns_dtenv_t *env, int roll) {
         * 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();
@@ -332,11 +347,13 @@ dns_dt_reopen(dns_dtenv_t *env, int roll) {
                                                                  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.
@@ -349,10 +366,15 @@ dns_dt_reopen(dns_dtenv_t *env, int roll) {
 
        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.
@@ -360,9 +382,10 @@ dns_dt_reopen(dns_dtenv_t *env, int roll) {
                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);
@@ -714,7 +737,8 @@ dns_dt_send(dns_view_t *view, dns_dtmsgtype_t msgtype,
        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);
                }
        }
index 81f270677572f022674ba8c0cd67842f178e9465..df3379cdf6c769aea798e403b66990f51b05d015 100644 (file)
@@ -28,6 +28,7 @@
 struct fstrm_iothr_options;
 #endif /* HAVE_DNSTAP */
 
+#include <isc/log.h>
 #include <isc/refcount.h>
 #include <isc/region.h>
 #include <isc/sockaddr.h>
@@ -153,7 +154,8 @@ dns_dt_create(isc_mem_t *mctx, dns_dtmode_t mode, const char *path,
  */
 
 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.
  *
@@ -161,6 +163,8 @@ dns_dt_setupfile(dns_dtenv_t *env, isc_uint64_t max_size, int rolls);
  *
  * '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.
index e77bafd0eff595c16dd56bb83977c51ad713cdbf..03904b5f16febcb0d267ef17ca8f2deb0a8ba266 100644 (file)
  */
 #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;
 /*@}*/
 
 /*!
@@ -115,6 +126,7 @@ typedef struct isc_logfile {
        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
index 0b68d9233b48cd96fa7c7d078a796d0f117d9933..5526c075964cb883c5d2dd657c618c966379b280 100644 (file)
@@ -241,6 +241,7 @@ isc_log_doit(isc_log_t *lctx, isc_logcategory_t *category,
 #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)
 
@@ -365,6 +366,7 @@ isc_logconfig_create(isc_log_t *lctx, isc_logconfig_t **lcfgp) {
                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,
@@ -384,6 +386,7 @@ isc_logconfig_create(isc_log_t *lctx, isc_logconfig_t **lcfgp) {
                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,
@@ -742,6 +745,7 @@ isc_log_createchannel(isc_logconfig_t *lcfg, const char *name,
                        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;
@@ -751,6 +755,7 @@ isc_log_createchannel(isc_logconfig_t *lcfg, const char *name,
                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:
@@ -1210,23 +1215,134 @@ greatest_version(isc_logfile_t *file, int versions, int *greatestp) {
        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;
 
@@ -1237,10 +1353,11 @@ isc_logfile_roll(isc_logfile_t *file) {
                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 {
                /*
@@ -1248,56 +1365,129 @@ isc_logfile_roll(isc_logfile_t *file) {
                 * 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
index 47ccd53098140c905a780e7cb1291b297c41563f..b6bfdc9b2ba39d51b78e7c5d3991f4dee0fcef1e 100644 (file)
@@ -379,6 +379,20 @@ isc_time_formatISO8601ms(const isc_time_t *t, char *buf, unsigned int len);
  *
  */
 
+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 */
index f7d40306f0bf9d7be250c797eb1e09d091bfe517..f1b7f2274d6edd60d4d65cbd2c847156533f77ed 100644 (file)
@@ -480,3 +480,21 @@ isc_time_formatISO8601ms(const isc_time_t *t, char *buf, unsigned int len) {
                         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);
+       }
+}
index 4b244dba95da425a5bd07dda9339b236533d7bcd..a9aa481272a0c635190fcbe348a54c11bb626d92 100644 (file)
@@ -343,6 +343,20 @@ isc_time_formatISO8601ms(const isc_time_t *t, char *buf, unsigned int len);
  *
  */
 
+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);
 /*%<
index efb747da8094b0bce30c7bf410747051bbfb06d1..2bb4a3c0669055b09062229436f98ea8279817ee 100644 (file)
@@ -710,6 +710,7 @@ isc_time_compare
 isc_time_formatISO8601
 isc_time_formatISO8601ms
 isc_time_formathttptimestamp
+isc_time_formatshorttimestamp
 isc_time_formattimestamp
 isc_time_isepoch
 isc_time_microdiff
index 43e9a84ec14d2c8ecb0f5965c6e1ed0b40147f55..116151ead9f21a033feabfccd11bf4a26ee9b365 100644 (file)
@@ -413,3 +413,26 @@ isc_time_formatISO8601ms(const isc_time_t *t, char *buf, unsigned int len) {
                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;
+       }
+}
index badbcf9af724e7e5660ca4a25304b43c62ed1d5b..63f5048efa9850555361a58d1a07bfd28f2480b5 100644 (file)
@@ -107,6 +107,7 @@ static cfg_type_t cfg_type_key;
 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;
@@ -1327,6 +1328,11 @@ parse_dtout(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
                        {
                                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");
@@ -1343,6 +1349,8 @@ parse_dtout(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
                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);
@@ -1364,6 +1372,10 @@ print_dtout(cfg_printer_t *pctx, const cfg_obj_t *obj) {
                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]);
+       }
 }
 
 
@@ -1375,6 +1387,8 @@ doc_dtout(cfg_printer_t *pctx, const cfg_type_t *type) {
        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 };
@@ -1388,6 +1402,7 @@ static cfg_tuplefielddef_t dtout_fields[] = {
        { "path", &cfg_type_qstring, 0 },
        { "size", &cfg_type_sizenodefault, 0 },
        { "versions", &cfg_type_logversions, 0 },
+       { "suffix", &cfg_type_logsuffix, 0 },
        { NULL, NULL, 0 }
 };
 
@@ -3393,10 +3408,17 @@ static cfg_type_t cfg_type_logversions = {
        &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 }
 };
 
@@ -3426,6 +3448,11 @@ parse_logfile(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
                                   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;
                        }
@@ -3439,6 +3466,8 @@ parse_logfile(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
                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);
@@ -3459,6 +3488,10 @@ print_logfile(cfg_printer_t *pctx, const cfg_obj_t *obj) {
                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]);
+       }
 }
 
 
@@ -3467,9 +3500,11 @@ doc_logfile(cfg_printer_t *pctx, const cfg_type_t *type) {
        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 = {