masterdump.c revision b66b333f59cf51ef87f973084a5023acd9317fb2
/*
* Copyright (C) 2004-2009, 2011-2015 Internet Systems Consortium, Inc. ("ISC")
* Copyright (C) 1999-2003 Internet Software Consortium.
*
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
/*! \file */
#include <config.h>
#include <stdlib.h>
#include <dns/dbiterator.h>
#include <dns/fixedname.h>
#include <dns/masterdump.h>
#include <dns/rdataclass.h>
#include <dns/rdataset.h>
#include <dns/rdatasetiter.h>
#include <dns/rdatatype.h>
#define RETERR(x) do { \
isc_result_t _r = (x); \
if (_r != ISC_R_SUCCESS) \
return (_r); \
} while (0)
#define CHECK(x) do { \
if ((x) != ISC_R_SUCCESS) \
goto cleanup; \
} while (0)
struct dns_master_style {
unsigned int flags; /* DNS_STYLEFLAG_* */
unsigned int ttl_column;
unsigned int class_column;
unsigned int type_column;
unsigned int rdata_column;
unsigned int line_length;
unsigned int tab_width;
unsigned int split_width;
};
/*%
* The maximum length of the newline+indentation that is output
* when inserting a line break in an RR. This effectively puts an
* upper limits on the value of "rdata_column", because if it is
* very large, the tabs and spaces needed to reach it will not fit.
*/
#define DNS_TOTEXT_LINEBREAK_MAXLEN 100
/*%
* Context structure for a masterfile dump in progress.
*/
typedef struct dns_totext_ctx {
char * linebreak;
dns_name_t * origin;
};
};
};
};
};
0,
};
/*%
* A style suitable for dns_rdataset_totext().
*/
};
/*%
* Similar, but indented (i.e., prepended with dns_master_indentstr).
*/
};
/*%
* Similar, but with each line commented out.
*/
};
/*%
* Default indent string.
*/
#define N_SPACES 10
#define N_TABS 10
struct dns_dumpctx {
unsigned int magic;
unsigned int references;
FILE *f;
void *done_arg;
unsigned int nodes;
/* dns_master_dumpinc() */
char *file;
char *tmpfile;
};
/*%
* Output tabs and spaces to go from column '*current' to
* column 'to', and update '*current' to reflect the new
* current column.
*/
static isc_result_t
{
isc_region_t r;
unsigned char *p;
unsigned int from;
if (ntabs < 0)
ntabs = 0;
if (ntabs > 0) {
return (ISC_R_NOSPACE);
p = r.base;
t = ntabs;
while (t) {
int n = t;
if (n > N_TABS)
n = N_TABS;
p += n;
t -= n;
}
}
return (ISC_R_NOSPACE);
p = r.base;
t = nspaces;
while (t) {
int n = t;
if (n > N_SPACES)
n = N_SPACES;
p += n;
t -= n;
}
return (ISC_R_SUCCESS);
}
static isc_result_t
/*
* Set up the line break string if needed.
*/
isc_region_t r;
unsigned int col = 0;
sizeof(ctx->linebreak_buf));
isc_buffer_availableregion(&buf, &r);
if (r.length < 1)
return (DNS_R_TEXTTOOLONG);
r.base[0] = '\n';
isc_buffer_availableregion(&buf, &r);
return (DNS_R_TEXTTOOLONG);
(const isc_uint8_t *) dns_master_indentstr,
ilen);
}
isc_buffer_availableregion(&buf, &r);
if (r.length < 1)
return (DNS_R_TEXTTOOLONG);
r.base[0] = ';';
}
/*
* Do not return ISC_R_NOSPACE if the line break string
* buffer is too small, because that would just make
* dump_rdataset() retry indefinitely with ever
* bigger target buffers. That's a different buffer,
* so it won't help. Use DNS_R_TEXTTOOLONG as a substitute.
*/
if (result == ISC_R_NOSPACE)
return (DNS_R_TEXTTOOLONG);
if (result != ISC_R_SUCCESS)
return (result);
isc_buffer_availableregion(&buf, &r);
if (r.length < 1)
return (DNS_R_TEXTTOOLONG);
r.base[0] = '\0';
} else {
}
ctx->current_ttl = 0;
return (ISC_R_SUCCESS);
}
do { \
!= ISC_R_SUCCESS) \
return (result); \
} while (0)
static isc_result_t
unsigned int l;
return (ISC_R_NOSPACE);
isc_buffer_add(target, l);
return (ISC_R_SUCCESS);
}
static isc_result_t
{
do {
result == ISC_R_SUCCESS;
} else {
0, 0, 0, " ", target));
}
}
} while (result == ISC_R_SUCCESS);
if (result == ISC_R_NOMORE)
if (dns_rdataset_isassociated(&rds))
return (result);
}
/*
* Convert 'rdataset' to master file text format according to 'ctx',
* storing the result in 'target'. If 'owner_name' is NULL, it
* is omitted; otherwise 'owner_name' must be valid and have at least
* one label.
*/
static isc_result_t
{
unsigned int column;
unsigned int type_start;
if (owner_name != NULL) {
}
while (result == ISC_R_SUCCESS) {
column = 0;
/*
* Indent?
*/
/*
* Comment?
*/
/*
* Owner name.
*/
!first))
{
}
/*
* TTL.
*/
{
char ttlbuf[64];
isc_region_t r;
unsigned int length;
target);
if (result != ISC_R_SUCCESS)
return (result);
} else {
return (ISC_R_NOSPACE);
}
/*
* If the $TTL directive is not in use, the TTL we
* just printed becomes the default for subsequent RRs.
*/
}
}
/*
* Class.
*/
{
unsigned int class_start;
target);
if (result != ISC_R_SUCCESS)
return (result);
}
/*
* Type.
*/
} else {
}
switch (type) {
case dns_rdatatype_keydata:
#define KEYDATA "KEYDATA"
if (isc_buffer_availablelength(target) <
(sizeof(KEYDATA) - 1))
return (ISC_R_NOSPACE);
break;
}
/* FALLTHROUGH */
default:
if (result != ISC_R_SUCCESS)
return (result);
}
/*
* Rdata.
*/
target));
else
/*
* Print a summary of the cached records which make
* up the negative response.
*/
target));
break;
} else {
isc_region_t r;
target));
if (r.length < 1)
return (ISC_R_NOSPACE);
r.base[0] = '\n';
}
}
if (result != ISC_R_NOMORE)
return (result);
/*
* Update the ctx state to reflect what we just printed.
* This is done last, only when we are sure we will return
* success, because this function may be called multiple
* times with increasing buffer sizes until it succeeds,
* and failed attempts must not update the state prematurely.
*/
return (ISC_R_SUCCESS);
}
/*
* Print the name, type, and class of an empty rdataset,
* such as those used to represent the question section
* of a DNS message.
*/
static isc_result_t
{
unsigned int column;
isc_region_t r;
column = 0;
/* Owner name */
{
target));
}
/* Class */
{
unsigned int class_start;
if (result != ISC_R_SUCCESS)
return (result);
}
/* Type */
{
unsigned int type_start;
if (result != ISC_R_SUCCESS)
return (result);
}
if (r.length < 1)
return (ISC_R_NOSPACE);
r.base[0] = '\n';
return (ISC_R_SUCCESS);
}
{
if (result != ISC_R_SUCCESS) {
"could not set master file style");
return (ISC_R_UNEXPECTED);
}
/*
* The caller might want to give us an empty owner
* name (e.g. if they are outputting into a master
* file and this rdataset has the same name as the
* previous one.)
*/
if (dns_name_countlabels(owner_name) == 0)
owner_name = NULL;
if (question)
omit_final_dot, target));
else
omit_final_dot, target));
}
const dns_master_style_t *style,
{
if (result != ISC_R_SUCCESS) {
"could not set master file style");
return (ISC_R_UNEXPECTED);
}
}
const dns_master_style_t *style,
{
if (result != ISC_R_SUCCESS) {
"could not set master file style");
return (ISC_R_UNEXPECTED);
}
}
/*
* Print an rdataset. 'buffer' is a scratch buffer, which must have been
* dynamically allocated by the caller. It must be large enough to
* hold the result from dns_ttl_totext(). If more than that is needed,
* the buffer will be grown automatically.
*/
static isc_result_t
{
isc_region_t r;
/*
* Output a $TTL directive if needed.
*/
{
{
isc_buffer_usedregion(buffer, &r);
} else {
}
}
}
/*
* Generate the text representation of the rdataset into
* the buffer. If the buffer is too small, grow it.
*/
for (;;) {
int newlength;
void *newmem;
if (result != ISC_R_NOSPACE)
break;
return (ISC_R_NOMEMORY);
}
if (result != ISC_R_SUCCESS)
return (result);
/*
* Write the buffer contents to the master file.
*/
isc_buffer_usedregion(buffer, &r);
if (result != ISC_R_SUCCESS) {
"master file write failed: %s",
return (result);
}
return (ISC_R_SUCCESS);
}
/*
* Define the order in which rdatasets should be printed in zone
* files. We will print SOA and NS records before others, SIGs
* immediately following the things they sign, and order everything
* else by RR number. This is all just for aesthetics and
* compatibility with buggy software that expects the SOA to be first;
* the DNS specifications allow any order.
*/
static int
int t;
int sig;
sig = 1;
} else {
sig = 0;
}
switch (t) {
case dns_rdatatype_soa:
t = 0;
break;
case dns_rdatatype_ns:
t = 1;
break;
default:
t += 2;
break;
}
return (t << 1) + sig;
}
static int
dump_order_compare(const void *a, const void *b) {
return (dump_order(*((const dns_rdataset_t * const *) a)) -
dump_order(*((const dns_rdataset_t * const *) b)));
}
/*
* Dump all the rdatasets of a domain name to a master file. We make
* a "best effort" attempt to sort the RRsets in a nice order, but if
* there are more than MAXSORT RRsets, we punt and only sort them in
* groups of MAXSORT. This is not expected to ever happen in practice
* since much less than 64 RR types have been registered with the
* IANA, so far, and the output will be correct (though not
* aesthetically pleasing) even if it does happen.
*/
#define MAXSORT 64
static isc_result_t
{
isc_region_t r;
int i, n;
isc_buffer_usedregion(buffer, &r);
}
for (i = 0;
dns_rdataset_init(&rdatasets[i]);
}
n = i;
for (i = 0; i < n; i++) {
fprintf(f, "%s; %s\n",
? dns_master_indentstr : "",
/* Omit negative cache entries */
} else {
buffer, f);
if (result != ISC_R_SUCCESS)
dumpresult = result;
}
isc_buffer_t b;
char buf[sizeof("YYYYMMDDHHMMSS")];
fprintf(f, "%s; resign=%s\n",
? dns_master_indentstr : "",
buf);
}
}
if (dumpresult != ISC_R_SUCCESS)
return (dumpresult);
/*
* If we got more data than could be sorted at once,
* go handle the rest.
*/
if (itresult == ISC_R_SUCCESS)
goto again;
if (itresult == ISC_R_NOMORE)
return (itresult);
}
/*
* Dump given RRsets in the "raw" format.
*/
static isc_result_t
{
isc_region_t r, r_hdr;
totallen = 0;
/*
* Common header and owner name (length followed by name)
* These fields should be in a moderate length, so we assume we
* can store all of them in the initial buffer.
*/
dns_name_toregion(name, &r);
isc_buffer_copyregion(buffer, &r);
do {
dns_rdata_toregion(&rdata, &r);
/*
* Copy the rdata into the buffer. If the buffer is too small,
* grow it. This should be rare, so we'll simply restart the
* entire procedure (or should we copy the old data and
* continue?).
*/
if (isc_buffer_availablelength(buffer) <
int newlength;
void *newmem;
return (ISC_R_NOMEMORY);
goto restart;
}
isc_buffer_copyregion(buffer, &r);
} while (result == ISC_R_SUCCESS);
if (result != ISC_R_NOMORE)
return (result);
/*
* Fill in the total length field.
* XXX: this is a bit tricky. Since we have already "used" the space
* for the total length in the buffer, we first remember the entire
* buffer length in the region, "rewind", and then write the value.
*/
isc_buffer_usedregion(buffer, &r);
/*
* Write the buffer contents to the raw master file.
*/
if (result != ISC_R_SUCCESS) {
"raw master file write failed: %s",
return (result);
}
return (result);
}
static isc_result_t
{
result == ISC_R_SUCCESS;
/* Omit negative cache entries */
} else {
buffer, f);
}
if (result != ISC_R_SUCCESS)
return (result);
}
if (result == ISC_R_NOMORE)
return (result);
}
static isc_result_t
{
UNUSED(f);
return (ISC_R_NOTIMPLEMENTED);
}
/*
* Initial size of text conversion buffer. The buffer is used
* for several purposes: converting origin names, rdatasets,
* $DATE timestamps, and comment strings for $TTL directives.
*
* When converting rdatasets, it is dynamically resized, but
* when converting origins, timestamps, etc it is not. Therefore,
* the initial size must large enough to hold the longest possible
* text representation of any domain name (for $ORIGIN).
*/
static const int initial_buffer_length = 1200;
static isc_result_t
static void
}
void
source->references++;
}
void
dctx->references--;
if (dctx->references == 0)
if (need_destroy)
}
}
dns_db_t *
}
void
}
static isc_result_t
if (result == ISC_R_SUCCESS)
result = isc_stdio_flush(f);
"dumping to master file: %s: flush: %s",
else
"dumping to stream: flush: %s",
}
if (result == ISC_R_SUCCESS)
result = isc_stdio_sync(f);
"dumping to master file: %s: fsync: %s",
else
"dumping to stream: fsync: %s",
}
return (result);
}
static isc_result_t
{
if (result != ISC_R_SUCCESS)
tresult = isc_stdio_close(f);
if (result == ISC_R_SUCCESS)
"dumping master file: %s: fclose: %s",
}
if (result == ISC_R_SUCCESS)
else
(void)isc_file_remove(temp);
"dumping master file: rename: %s: %s",
}
return (result);
}
static void
else
if (result == DNS_R_CONTINUE) {
return;
}
} else
}
static isc_result_t
return (ISC_R_NOMEMORY);
return (ISC_R_SUCCESS);
}
static isc_result_t
{
unsigned int options;
return (ISC_R_NOMEMORY);
dctx->f = f;
else
switch (format) {
case dns_masterformat_text:
break;
case dns_masterformat_raw:
break;
case dns_masterformat_map:
break;
default:
INSIST(0);
break;
}
if (result != ISC_R_SUCCESS) {
"could not set master file style");
goto cleanup;
}
} else
options = 0;
if (result != ISC_R_SUCCESS)
goto cleanup;
if (result != ISC_R_SUCCESS)
goto cleanup;
else if (!dns_db_iscache(db))
return (ISC_R_SUCCESS);
return (result);
}
static isc_result_t
char *bufmem;
isc_region_t r;
return (ISC_R_NOMEMORY);
case dns_masterformat_text:
/*
* If the database has cache semantics, output an
* RFC2540 $DATE directive so that the TTLs can be
* adjusted when it is reloaded. For zones it is not
* really needed, and it would make the file
* incompatible with pre-RFC2540 software, so we omit
* it in the zone case.
*/
isc_buffer_usedregion(&buffer, &r);
}
break;
case dns_masterformat_raw:
case dns_masterformat_map:
isc_buffer_region(&buffer, &r);
/*
* We assume isc_stdtime_t is a 32-bit integer,
* which should be the case on most platforms.
* If it turns out to be uncommon, we'll need
* to bump the version number and revise the
* header format.
*/
"dumping master file in raw "
"format: stdtime is not 32bits");
now32 = 0;
#else
#endif
rawversion = 1;
rawversion = 0;
if (rawversion == 1) {
}
if (result != ISC_R_SUCCESS)
break;
break;
default:
INSIST(0);
}
return (result);
}
static isc_result_t
char *bufmem;
unsigned int nodes;
return (ISC_R_NOMEMORY);
/*
* Fast format is not currently written incrementally,
* so we make the call to dns_db_serialize() here.
* If the database is anything other than an rbtdb,
* this should result in not implemented
*/
dctx->f);
goto cleanup;
}
goto cleanup;
} else
break;
if (result == DNS_R_NEWORIGIN) {
dns_name_t *origin =
DNS_STYLEFLAG_REL_DATA) != 0)
}
if (result != ISC_R_SUCCESS) {
goto cleanup;
}
if (result != ISC_R_SUCCESS) {
goto cleanup;
}
}
/*
* Work out how many nodes can be written in the time between
* two requests to the nameserver. Smooth the resulting number and
* use it as a estimate for the number of nodes to be written in the
* next iteration.
*/
unsigned int interval;
isc_time_now(&end);
if (pps < 100)
pps = 100;
if (interval == 0)
interval = 1;
if (usecs == 0) {
} else {
if (nodes == 0)
nodes = 1;
else if (nodes > 1000)
nodes = 1000;
/* Smooth and assign. */
ISC_LOG_DEBUG(1),
"dumptostreaminc(%p) new nodes -> %d\n",
}
} else if (result == ISC_R_NOMORE)
return (result);
}
const dns_master_style_t *style,
{
if (result != ISC_R_SUCCESS)
return (result);
if (result == ISC_R_SUCCESS) {
return (DNS_R_CONTINUE);
}
return (result);
}
/*
* Dump an entire database into a master file.
*/
const dns_master_style_t *style,
FILE *f)
{
dns_masterformat_text, NULL, f));
}
const dns_master_style_t *style,
{
}
const dns_master_style_t *style,
{
if (result != ISC_R_SUCCESS)
return (result);
return (result);
}
static isc_result_t
int tempnamelen;
return (ISC_R_NOMEMORY);
if (result != ISC_R_SUCCESS)
goto cleanup;
if (format == dns_masterformat_text)
else
if (result != ISC_R_SUCCESS) {
"dumping master file: %s: open: %s",
goto cleanup;
}
*fp = f;
return (ISC_R_SUCCESS);
return (result);
}
{
}
{
}
{
return (ISC_R_NOMEMORY);
if (result != ISC_R_SUCCESS)
goto cleanup;
if (result != ISC_R_SUCCESS) {
(void)isc_stdio_close(f);
(void)isc_file_remove(tempname);
goto cleanup;
}
if (result == ISC_R_SUCCESS) {
return (DNS_R_CONTINUE);
}
return (result);
}
{
}
{
}
{
char *tempname;
if (result != ISC_R_SUCCESS)
return (result);
if (result != ISC_R_SUCCESS)
goto cleanup;
return (result);
}
/*
* Dump a database node into a master file.
* XXX: this function assumes the text format.
*/
const dns_master_style_t *style,
FILE *f)
{
char *bufmem;
if (result != ISC_R_SUCCESS) {
"could not set master file style");
return (ISC_R_UNEXPECTED);
}
return (ISC_R_NOMEMORY);
if (result != ISC_R_SUCCESS)
goto failure;
if (result != ISC_R_SUCCESS)
goto failure;
return (result);
}
{
if (result != ISC_R_SUCCESS) {
"dumping node to file: %s: open: %s", filename,
return (ISC_R_UNEXPECTED);
}
style, f);
if (result != ISC_R_SUCCESS) {
"dumping master file: %s: dump: %s", filename,
(void)isc_stdio_close(f);
return (ISC_R_UNEXPECTED);
}
result = isc_stdio_close(f);
if (result != ISC_R_SUCCESS) {
"dumping master file: %s: close: %s", filename,
return (ISC_R_UNEXPECTED);
}
return (result);
}
unsigned int
}
unsigned int ttl_column, unsigned int class_column,
unsigned int type_column, unsigned int rdata_column,
unsigned int line_length, unsigned int tab_width,
{
}
unsigned int ttl_column, unsigned int class_column,
unsigned int type_column, unsigned int rdata_column,
unsigned int line_length, unsigned int tab_width,
{
return (ISC_R_NOMEMORY);
return (ISC_R_SUCCESS);
}
void
}