masterdump.c 35.2 KB
Newer Older
1
/*
Brian Wellington's avatar
Brian Wellington committed
2
 * Copyright (C) 1999-2001  Internet Software Consortium.
3
 *
4 5 6
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
7
 *
8 9 10 11 12 13 14 15
 * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM
 * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
 * INTERNET SOFTWARE CONSORTIUM 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.
16 17
 */

Mark Andrews's avatar
Mark Andrews committed
18
/* $Id: masterdump.c,v 1.70 2003/10/17 03:46:43 marka Exp $ */
David Lawrence's avatar
David Lawrence committed
19

20 21
#include <config.h>

22 23
#include <stdlib.h>

24
#include <isc/event.h>
25
#include <isc/file.h>
26
#include <isc/magic.h>
27
#include <isc/mem.h>
28
#include <isc/stdio.h>
29
#include <isc/string.h>
30
#include <isc/task.h>
Bob Halley's avatar
Bob Halley committed
31
#include <isc/util.h>
32

33 34
#include <dns/db.h>
#include <dns/dbiterator.h>
35
#include <dns/events.h>
36
#include <dns/fixedname.h>
37
#include <dns/log.h>
38
#include <dns/masterdump.h>
39
#include <dns/rdata.h>
40
#include <dns/rdataclass.h>
41 42
#include <dns/rdataset.h>
#include <dns/rdatasetiter.h>
43 44
#include <dns/rdatatype.h>
#include <dns/result.h>
45 46 47
#include <dns/time.h>
#include <dns/ttl.h>

48 49 50
#define DNS_DCTX_MAGIC		ISC_MAGIC('D', 'c', 't', 'x')
#define DNS_DCTX_VALID(d)	ISC_MAGIC_VALID(d, DNS_DCTX_MAGIC)

51
#define RETERR(x) do { \
52 53 54
	isc_result_t _r = (x); \
	if (_r != ISC_R_SUCCESS) \
		return (_r); \
55 56 57 58 59 60 61 62 63 64 65 66 67 68
	} 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;
};

/*
 * The maximum length of the newline+indentation that is output
69
 * when inserting a line break in an RR.  This effectively puts an
70 71 72 73 74 75 76 77 78 79 80
 * 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 {
	dns_master_style_t	style;
	isc_boolean_t 		class_printed;
81 82 83
	char *			linebreak;
	char 			linebreak_buf[DNS_TOTEXT_LINEBREAK_MAXLEN];
	dns_name_t *		origin;
84
	dns_name_t *		neworigin;
85 86 87 88 89
	dns_fixedname_t		origin_fixname;
	isc_uint32_t 		current_ttl;
	isc_boolean_t 		current_ttl_valid;
} dns_totext_ctx_t;

Danny Mayer's avatar
Danny Mayer committed
90
LIBDNS_EXTERNAL_DATA const dns_master_style_t
91 92 93 94 95 96 97 98 99
dns_master_style_default = {
	DNS_STYLEFLAG_OMIT_OWNER |
	DNS_STYLEFLAG_OMIT_CLASS |
	DNS_STYLEFLAG_REL_OWNER |
	DNS_STYLEFLAG_REL_DATA |
	DNS_STYLEFLAG_OMIT_TTL |
	DNS_STYLEFLAG_TTL |
	DNS_STYLEFLAG_COMMENT |
	DNS_STYLEFLAG_MULTILINE,
100
	24, 24, 24, 32, 80, 8
101 102
};

Danny Mayer's avatar
Danny Mayer committed
103
LIBDNS_EXTERNAL_DATA const dns_master_style_t
104 105 106 107 108 109 110
dns_master_style_explicitttl = {
	DNS_STYLEFLAG_OMIT_OWNER |
	DNS_STYLEFLAG_OMIT_CLASS |
	DNS_STYLEFLAG_REL_OWNER |
	DNS_STYLEFLAG_REL_DATA |
	DNS_STYLEFLAG_COMMENT |
	DNS_STYLEFLAG_MULTILINE,
111
	24, 32, 32, 40, 80, 8
112 113
};

114
LIBDNS_EXTERNAL_DATA const dns_master_style_t
Brian Wellington's avatar
Brian Wellington committed
115 116 117
dns_master_style_cache = {
	DNS_STYLEFLAG_OMIT_OWNER |
	DNS_STYLEFLAG_OMIT_CLASS |
118 119 120
	DNS_STYLEFLAG_MULTILINE |
	DNS_STYLEFLAG_TRUST |
	DNS_STYLEFLAG_NCACHE,
Brian Wellington's avatar
Brian Wellington committed
121 122 123
	24, 32, 32, 40, 80, 8
};

124 125
LIBDNS_EXTERNAL_DATA const dns_master_style_t
dns_master_style_simple = {
Michael Sawyer's avatar
Michael Sawyer committed
126 127 128 129
	0,
	24, 32, 32, 40, 80, 8
};

130 131 132
/*
 * A style suitable for dns_rdataset_totext().
 */
Danny Mayer's avatar
Danny Mayer committed
133
LIBDNS_EXTERNAL_DATA const dns_master_style_t
134
dns_master_style_debug = {
Brian Wellington's avatar
Brian Wellington committed
135
	DNS_STYLEFLAG_REL_OWNER,
136 137 138 139 140
	24, 32, 40, 48, 80, 8
};


#define N_SPACES 10
141
static char spaces[N_SPACES+1] = "          ";
142 143

#define N_TABS 10
144
static char tabs[N_TABS+1] = "\t\t\t\t\t\t\t\t\t\t";
145

146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
struct dns_dumpctx {
	unsigned int		magic;
	isc_mem_t		*mctx;
	isc_mutex_t		lock;
	unsigned int		references;
	isc_boolean_t		canceled;
	isc_boolean_t		first;
	isc_boolean_t		do_date;
	isc_stdtime_t		now;
	FILE			*f;
	dns_db_t		*db;
	dns_dbversion_t		*version;
	dns_dbiterator_t	*dbiter;
	dns_totext_ctx_t	tctx;
	isc_task_t		*task;
	dns_dumpdonefunc_t	done;
	void			*done_arg;
	unsigned int		nodes;
	/* dns_master_dumpinc() */
	char			*file;
	char 			*tmpfile;
};
168

169 170
#define NXDOMAIN(x) (((x)->attributes & DNS_RDATASETATTR_NXDOMAIN) != 0) 

171
/*
172
 * Output tabs and spaces to go from column '*current' to
173 174 175
 * column 'to', and update '*current' to reflect the new
 * current column.
 */
176
static isc_result_t
Bob Halley's avatar
Bob Halley committed
177 178 179
indent(unsigned int *current, unsigned int to, int tabwidth,
       isc_buffer_t *target)
{
180 181
	isc_region_t r;
	unsigned char *p;
Bob Halley's avatar
lint  
Bob Halley committed
182 183
	unsigned int from;
	int ntabs, nspaces, t;
184 185 186 187 188 189 190

	from = *current;

	if (to < from + 1)
		to = from + 1;

	ntabs = to / tabwidth - from / tabwidth;
191
	if (ntabs < 0)
192 193 194
		ntabs = 0;

	if (ntabs > 0) {
195
		isc_buffer_availableregion(target, &r);
196
		if (r.length < (unsigned) ntabs)
197
			return (ISC_R_NOSPACE);
198
		p = r.base;
199

200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
		t = ntabs;
		while (t) {
			int n = t;
			if (n > N_TABS)
				n = N_TABS;
			memcpy(p, tabs, n);
			p += n;
			t -= n;
		}
		isc_buffer_add(target, ntabs);
		from = (to / tabwidth) * tabwidth;
	}

	nspaces = to - from;
	INSIST(nspaces >= 0);

216
	isc_buffer_availableregion(target, &r);
217
	if (r.length < (unsigned) nspaces)
218
		return (ISC_R_NOSPACE);
219 220
	p = r.base;

221
	t = nspaces;
222 223 224 225 226 227 228 229
	while (t) {
		int n = t;
		if (n > N_SPACES)
			n = N_SPACES;
		memcpy(p, spaces, n);
		p += n;
		t -= n;
	}
230
	isc_buffer_add(target, nspaces);
231 232

	*current = to;
233
	return (ISC_R_SUCCESS);
234 235
}

236
static isc_result_t
237
totext_ctx_init(const dns_master_style_t *style, dns_totext_ctx_t *ctx) {
238
	isc_result_t result;
239

240
	REQUIRE(style->tab_width != 0);
241 242 243

	ctx->style = *style;
	ctx->class_printed = ISC_FALSE;
244

245 246
	dns_fixedname_init(&ctx->origin_fixname);

247 248 249
	/*
	 * Set up the line break string if needed.
	 */
250 251 252
	if ((ctx->style.flags & DNS_STYLEFLAG_MULTILINE) != 0) {
		isc_buffer_t buf;
		isc_region_t r;
Bob Halley's avatar
Bob Halley committed
253
		unsigned int col = 0;
254

255
		isc_buffer_init(&buf, ctx->linebreak_buf,
256
				sizeof(ctx->linebreak_buf));
257

258
		isc_buffer_availableregion(&buf, &r);
259
		if (r.length < 1)
Bob Halley's avatar
Bob Halley committed
260
			return (DNS_R_TEXTTOOLONG);
261 262 263
		r.base[0] = '\n';
		isc_buffer_add(&buf, 1);

264
		result = indent(&col, ctx->style.rdata_column,
265 266
				ctx->style.tab_width, &buf);
		/*
267
		 * Do not return ISC_R_NOSPACE if the line break string
268 269
		 * buffer is too small, because that would just make
		 * dump_rdataset() retry indenfinitely with ever
270
		 * bigger target buffers.  That's a different buffer,
Bob Halley's avatar
Bob Halley committed
271
		 * so it won't help.  Use DNS_R_TEXTTOOLONG as a substitute.
272
		 */
273
		if (result == ISC_R_NOSPACE)
Bob Halley's avatar
Bob Halley committed
274
			return (DNS_R_TEXTTOOLONG);
275
		if (result != ISC_R_SUCCESS)
276
			return (result);
277

278
		isc_buffer_availableregion(&buf, &r);
279
		if (r.length < 1)
Bob Halley's avatar
Bob Halley committed
280
			return (DNS_R_TEXTTOOLONG);
281 282
		r.base[0] = '\0';
		isc_buffer_add(&buf, 1);
283
		ctx->linebreak = ctx->linebreak_buf;
284
	} else {
285
		ctx->linebreak = NULL;
286 287
	}

288
	ctx->origin = NULL;
289
	ctx->neworigin = NULL;
290 291
	ctx->current_ttl = 0;
	ctx->current_ttl_valid = ISC_FALSE;
292

293
	return (ISC_R_SUCCESS);
294 295 296 297 298 299
}

#define INDENT_TO(col) \
	do { \
		 if ((result = indent(&column, ctx->style.col, \
				      ctx->style.tab_width, target)) \
300
		     != ISC_R_SUCCESS) \
301
			    return (result); \
Brian Wellington's avatar
Brian Wellington committed
302
	} while (0)
303 304


305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320
static isc_result_t
str_totext(const char *source, isc_buffer_t *target) {
	unsigned int l;
	isc_region_t region;

	isc_buffer_availableregion(target, &region);
	l = strlen(source);

	if (l > region.length)
		return (ISC_R_NOSPACE);

	memcpy(region.base, source, l);
	isc_buffer_add(target, l);
	return (ISC_R_SUCCESS);
}

321 322 323 324 325 326 327
/*
 * 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.
 */

328
static isc_result_t
329 330 331 332 333 334
rdataset_totext(dns_rdataset_t *rdataset,
		dns_name_t *owner_name,
		dns_totext_ctx_t *ctx,
		isc_boolean_t omit_final_dot,
		isc_buffer_t *target)
{
335
	isc_result_t result;
336 337 338 339
	unsigned int column;
	isc_boolean_t first = ISC_TRUE;
	isc_uint32_t current_ttl;
	isc_boolean_t current_ttl_valid;
340
	dns_rdatatype_t type;
341

342
	REQUIRE(DNS_RDATASET_VALID(rdataset));
343

344
	result = dns_rdataset_first(rdataset);
345
	REQUIRE(result == ISC_R_SUCCESS);
346 347 348 349 350 351

	current_ttl = ctx->current_ttl;
	current_ttl_valid = ctx->current_ttl_valid;

	do {
		column = 0;
352

353 354 355
		/*
		 * Owner name.
		 */
356
		if (owner_name != NULL &&
357 358
		    ! ((ctx->style.flags & DNS_STYLEFLAG_OMIT_OWNER) != 0 &&
		       !first))
359 360 361 362 363 364 365 366
		{
			unsigned int name_start = target->used;
			RETERR(dns_name_totext(owner_name,
					       omit_final_dot,
					       target));
			column += target->used - name_start;
		}

367 368 369
		/*
		 * TTL.
		 */
370 371 372 373
		if ((ctx->style.flags & DNS_STYLEFLAG_NO_TTL) == 0 &&
		    !((ctx->style.flags & DNS_STYLEFLAG_OMIT_TTL) != 0 &&
		      current_ttl_valid &&
		      rdataset->ttl == current_ttl))
374 375 376 377 378 379
		{
			char ttlbuf[64];
			isc_region_t r;
			unsigned int length;

			INDENT_TO(ttl_column);
380 381
			length = snprintf(ttlbuf, sizeof(ttlbuf), "%u",
					  rdataset->ttl);
Andreas Gustafsson's avatar
Andreas Gustafsson committed
382
			INSIST(length <= sizeof(ttlbuf));
383
			isc_buffer_availableregion(target, &r);
384
			if (r.length < length)
385
				return (ISC_R_NOSPACE);
386 387 388 389 390
			memcpy(r.base, ttlbuf, length);
			isc_buffer_add(target, length);
			column += length;

			/*
391
			 * If the $TTL directive is not in use, the TTL we
392 393
			 * just printed becomes the default for subsequent RRs.
			 */
394
			if ((ctx->style.flags & DNS_STYLEFLAG_TTL) == 0) {
395 396 397 398 399
				current_ttl = rdataset->ttl;
				current_ttl_valid = ISC_TRUE;
			}
		}

400 401 402
		/*
		 * Class.
		 */
403 404 405
		if ((ctx->style.flags & DNS_STYLEFLAG_NO_CLASS) == 0 &&
		    ((ctx->style.flags & DNS_STYLEFLAG_OMIT_CLASS) == 0 ||
		     ctx->class_printed == ISC_FALSE))
406 407 408 409
		{
			unsigned int class_start;
			INDENT_TO(class_column);
			class_start = target->used;
410 411
			result = dns_rdataclass_totext(rdataset->rdclass,
						       target);
412
			if (result != ISC_R_SUCCESS)
413 414 415 416
				return (result);
			column += (target->used - class_start);
		}

417 418 419
		/*
		 * Type.
		 */
420 421 422 423 424 425 426

		if (rdataset->type == 0) {
			type = rdataset->covers;
		} else {
			type = rdataset->type;
		}

427 428 429 430
		{
			unsigned int type_start;
			INDENT_TO(type_column);
			type_start = target->used;
431 432 433
			if (rdataset->type == 0)
				RETERR(str_totext("\\-", target));
			result = dns_rdatatype_totext(type, target);
434
			if (result != ISC_R_SUCCESS)
435 436 437 438
				return (result);
			column += (target->used - type_start);
		}

439 440
		/*
		 * Rdata.
441
		 */
442 443
		INDENT_TO(rdata_column);
		if (rdataset->type == 0) {
444 445 446 447
			if (NXDOMAIN(rdataset))
				RETERR(str_totext(";-$NXDOMAIN\n", target));
			else
				RETERR(str_totext(";-$NXRRSET\n", target));
448
		} else {
449
			dns_rdata_t rdata = DNS_RDATA_INIT;
450 451 452 453
			isc_region_t r;

			dns_rdataset_current(rdataset, &rdata);

454 455 456 457 458 459 460
			RETERR(dns_rdata_tofmttext(&rdata,
						   ctx->origin,
						   ctx->style.flags,
						   ctx->style.line_length -
						       ctx->style.rdata_column,
						   ctx->linebreak,
						   target));
461

462
			isc_buffer_availableregion(target, &r);
463
			if (r.length < 1)
464
				return (ISC_R_NOSPACE);
465 466 467 468 469 470
			r.base[0] = '\n';
			isc_buffer_add(target, 1);
		}

		first = ISC_FALSE;
		result = dns_rdataset_next(rdataset);
471
	} while (result == ISC_R_SUCCESS);
472

473
	if (result != ISC_R_NOMORE)
474 475 476 477
		return (result);

	/*
	 * Update the ctx state to reflect what we just printed.
478 479
	 * This is done last, only when we are sure we will return
	 * success, because this function may be called multiple
480
	 * times with increasing buffer sizes until it succeeds,
481
	 * and failed attempts must not update the state prematurely.
482 483 484 485 486
	 */
	ctx->class_printed = ISC_TRUE;
	ctx->current_ttl= current_ttl;
	ctx->current_ttl_valid = current_ttl_valid;

487
	return (ISC_R_SUCCESS);
488 489 490 491 492 493 494
}

/*
 * Print the name, type, and class of an empty rdataset,
 * such as those used to represent the question section
 * of a DNS message.
 */
495
static isc_result_t
496 497 498 499 500 501 502
question_totext(dns_rdataset_t *rdataset,
		dns_name_t *owner_name,
		dns_totext_ctx_t *ctx,
		isc_boolean_t omit_final_dot,
		isc_buffer_t *target)
{
	unsigned int column;
503
	isc_result_t result;
504
	isc_region_t r;
505 506 507

	REQUIRE(DNS_RDATASET_VALID(rdataset));
	result = dns_rdataset_first(rdataset);
508
	REQUIRE(result == ISC_R_NOMORE);
509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526

	column = 0;

	/* Owner name */
	{
		unsigned int name_start = target->used;
		RETERR(dns_name_totext(owner_name,
				       omit_final_dot,
				       target));
		column += target->used - name_start;
	}

	/* Class */
	{
		unsigned int class_start;
		INDENT_TO(class_column);
		class_start = target->used;
		result = dns_rdataclass_totext(rdataset->rdclass, target);
527
		if (result != ISC_R_SUCCESS)
528 529 530 531 532 533 534 535 536 537
			return (result);
		column += (target->used - class_start);
	}

	/* Type */
	{
		unsigned int type_start;
		INDENT_TO(type_column);
		type_start = target->used;
		result = dns_rdatatype_totext(rdataset->type, target);
538
		if (result != ISC_R_SUCCESS)
539 540 541
			return (result);
		column += (target->used - type_start);
	}
542

543
	isc_buffer_availableregion(target, &r);
544
	if (r.length < 1)
545
		return (ISC_R_NOSPACE);
546 547 548
	r.base[0] = '\n';
	isc_buffer_add(target, 1);

549
	return (ISC_R_SUCCESS);
550 551
}

552
isc_result_t
553 554 555
dns_rdataset_totext(dns_rdataset_t *rdataset,
		    dns_name_t *owner_name,
		    isc_boolean_t omit_final_dot,
556
		    isc_boolean_t question,
557 558 559
		    isc_buffer_t *target)
{
	dns_totext_ctx_t ctx;
560
	isc_result_t result;
561
	result = totext_ctx_init(&dns_master_style_debug, &ctx);
562
	if (result != ISC_R_SUCCESS) {
563 564
		UNEXPECTED_ERROR(__FILE__, __LINE__,
				 "could not set master file style");
565
		return (ISC_R_UNEXPECTED);
566 567 568 569 570 571 572 573 574 575
	}

	/*
	 * 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;
576

577
	if (question)
578
		return (question_totext(rdataset, owner_name, &ctx,
579 580
					omit_final_dot, target));
	else
581
		return (rdataset_totext(rdataset, owner_name, &ctx,
582 583 584
					omit_final_dot, target));
}

585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606
isc_result_t
dns_master_rdatasettotext(dns_name_t *owner_name,
			  dns_rdataset_t *rdataset,
			  const dns_master_style_t *style,
			  isc_buffer_t *target)
{
	dns_totext_ctx_t ctx;
	isc_result_t result;
	result = totext_ctx_init(style, &ctx);
	if (result != ISC_R_SUCCESS) {
		UNEXPECTED_ERROR(__FILE__, __LINE__,
				 "could not set master file style");
		return (ISC_R_UNEXPECTED);
	}

	return (rdataset_totext(rdataset, owner_name, &ctx,
				ISC_FALSE, target));
}

isc_result_t
dns_master_questiontotext(dns_name_t *owner_name,
			  dns_rdataset_t *rdataset,
607
			  const dns_master_style_t *style,
608 609
			  isc_buffer_t *target)
{
610 611 612 613 614 615 616 617 618 619
	dns_totext_ctx_t ctx;
	isc_result_t result;
	result = totext_ctx_init(style, &ctx);
	if (result != ISC_R_SUCCESS) {
		UNEXPECTED_ERROR(__FILE__, __LINE__,
				 "could not set master file style");
		return (ISC_R_UNEXPECTED);
	}

	return (question_totext(rdataset, owner_name, &ctx,
620 621 622
				ISC_FALSE, target));
}

623 624
/*
 * Print an rdataset.  'buffer' is a scratch buffer, which must have been
625
 * dynamically allocated by the caller.  It must be large enough to
626 627 628 629
 * hold the result from dns_ttl_totext().  If more than that is needed,
 * the buffer will be grown automatically.
 */

630
static isc_result_t
631
dump_rdataset(isc_mem_t *mctx, dns_name_t *name, dns_rdataset_t *rdataset,
632
	      dns_totext_ctx_t *ctx,
633 634 635
	      isc_buffer_t *buffer, FILE *f)
{
	isc_region_t r;
636
	isc_result_t result;
637

638 639
	REQUIRE(buffer->length > 0);

640 641 642
	/*
	 * Output a $TTL directive if needed.
	 */
643

644 645 646 647 648 649 650 651 652
	if ((ctx->style.flags & DNS_STYLEFLAG_TTL) != 0) {
		if (ctx->current_ttl_valid == ISC_FALSE ||
		    ctx->current_ttl != rdataset->ttl)
		{
			if ((ctx->style.flags & DNS_STYLEFLAG_COMMENT) != 0)
			{
				isc_buffer_clear(buffer);
				result = dns_ttl_totext(rdataset->ttl,
							ISC_TRUE, buffer);
653
				INSIST(result == ISC_R_SUCCESS);
654
				isc_buffer_usedregion(buffer, &r);
655 656 657 658 659 660 661 662 663
				fprintf(f, "$TTL %u\t; %.*s\n", rdataset->ttl,
					(int) r.length, (char *) r.base);
			} else {
				fprintf(f, "$TTL %u\n", rdataset->ttl);
			}
			ctx->current_ttl = rdataset->ttl;
			ctx->current_ttl_valid = ISC_TRUE;
		}
	}
664

665 666 667 668 669
	isc_buffer_clear(buffer);

	/*
	 * Generate the text representation of the rdataset into
	 * the buffer.  If the buffer is too small, grow it.
670
	 */
671 672 673 674 675
	for (;;) {
		int newlength;
		void *newmem;
		result = rdataset_totext(rdataset, name, ctx,
					 ISC_FALSE, buffer);
676
		if (result != ISC_R_NOSPACE)
677 678 679 680 681
			break;

		newlength = buffer->length * 2;
		newmem = isc_mem_get(mctx, newlength);
		if (newmem == NULL)
682
			return (ISC_R_NOMEMORY);
683
		isc_mem_put(mctx, buffer->base, buffer->length);
684
		isc_buffer_init(buffer, newmem, newlength);
685
	}
686
	if (result != ISC_R_SUCCESS)
687 688
		return (result);

689 690 691
	/*
	 * Write the buffer contents to the master file.
	 */
692
	isc_buffer_usedregion(buffer, &r);
693
	result = isc_stdio_write(r.base, 1, (size_t)r.length, f, NULL);
694

695
	if (result != ISC_R_SUCCESS) {
696
		UNEXPECTED_ERROR(__FILE__, __LINE__,
697
				 "master file write failed: %s",
698 699
				 isc_result_totext(result));
		return (result);
700
	}
701

702
	return (ISC_R_SUCCESS);
703 704 705
}

/*
706 707 708 709 710 711 712 713 714 715 716 717
 * 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
dump_order(const dns_rdataset_t *rds) {
	int t;
	int sig;
718
	if (rds->type == dns_rdatatype_rrsig) {
719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740
		t = rds->covers;
		sig = 1;
	} else {
		t = rds->type;
		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) {
741 742
	return (dump_order(*((const dns_rdataset_t * const *) a)) -
		dump_order(*((const dns_rdataset_t * const *) b)));
743 744 745 746 747 748 749 750 751 752
}

/*
 * 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.
753
 */
754 755 756

#define MAXSORT 64

757
static const char *trustnames[] = {
758 759 760 761 762 763 764 765 766 767 768
	"none",
	"pending",
	"additional",
	"glue",
	"answer",
	"authauthority",
	"authanswer",
	"secure",
	"local" /* aka ultimate */
};

769
static isc_result_t
770
dump_rdatasets(isc_mem_t *mctx, dns_name_t *name, dns_rdatasetiter_t *rdsiter,
771 772 773
	       dns_totext_ctx_t *ctx,
	       isc_buffer_t *buffer, FILE *f)
{
774
	isc_result_t itresult, dumpresult;
775
	isc_region_t r;
776 777 778 779 780 781 782
	dns_rdataset_t rdatasets[MAXSORT];
	dns_rdataset_t *sorted[MAXSORT];
	int i, n;

	itresult = dns_rdatasetiter_first(rdsiter);
	dumpresult = ISC_R_SUCCESS;

783 784 785 786 787 788 789 790 791
	if (itresult == ISC_R_SUCCESS && ctx->neworigin != NULL) {
		isc_buffer_clear(buffer);
		itresult = dns_name_totext(ctx->neworigin, ISC_FALSE, buffer);
		RUNTIME_CHECK(itresult == ISC_R_SUCCESS);
		isc_buffer_usedregion(buffer, &r);
		fprintf(f, "$ORIGIN %.*s\n", (int) r.length, (char *) r.base);
		ctx->neworigin = NULL;
	}

792 793 794 795 796 797 798
 again:
	for (i = 0;
	     itresult == ISC_R_SUCCESS && i < MAXSORT;
	     itresult = dns_rdatasetiter_next(rdsiter), i++) {
		dns_rdataset_init(&rdatasets[i]);
		dns_rdatasetiter_current(rdsiter, &rdatasets[i]);
		sorted[i] = &rdatasets[i];
799
	}
800 801
	n = i;
	INSIST(n <= MAXSORT);
802

803
	qsort(sorted, n, sizeof(sorted[0]), dump_order_compare);
804

805
	for (i = 0; i < n; i++) {
806 807 808
		dns_rdataset_t *rds = sorted[i];
		if (ctx->style.flags & DNS_STYLEFLAG_TRUST) {
			unsigned int trust = rds->trust;
Andreas Gustafsson's avatar
Andreas Gustafsson committed
809 810
			INSIST(trust < (sizeof(trustnames) /
					sizeof(trustnames[0])));
811 812 813 814 815 816
			fprintf(f, "; %s\n", trustnames[trust]);
		}
		if (rds->type == 0 &&
		    (ctx->style.flags & DNS_STYLEFLAG_NCACHE) == 0) {
			/* Omit negative cache entries */
		} else {
817
			isc_result_t result =
818
				dump_rdataset(mctx, name, rds, ctx,
Danny Mayer's avatar
Danny Mayer committed
819
					       buffer, f);
820 821
			if (result != ISC_R_SUCCESS)
				dumpresult = result;
822 823
			if ((ctx->style.flags & DNS_STYLEFLAG_OMIT_OWNER) != 0)
				name = NULL;
824
		}
825
		dns_rdataset_disassociate(rds);
826 827
	}

828 829
	if (dumpresult != ISC_R_SUCCESS)
		return (dumpresult);
830

831 832 833 834 835 836
	/*
	 * If we got more data than could be sorted at once,
	 * go handle the rest.
	 */
	if (itresult == ISC_R_SUCCESS)
		goto again;
837

838 839 840 841
	if (itresult == ISC_R_NOMORE)
		itresult = ISC_R_SUCCESS;

	return (itresult);
842 843 844 845 846
}


/*
 * Initial size of text conversion buffer.  The buffer is used
847
 * for several purposes: converting origin names, rdatasets,
848 849 850
 * $DATE timestamps, and comment strings for $TTL directives.
 *
 * When converting rdatasets, it is dynamically resized, but
851 852
 * when converting origins, timestamps, etc it is not.  Therefore,
 * the  initial size must large enough to hold the longest possible
853 854
 * text representation of any domain name (for $ORIGIN).
 */
855
static const int initial_buffer_length = 1200;
856

857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896
static isc_result_t
dumptostreaminc(dns_dumpctx_t *dctx);

static void
dumpctx_destroy(dns_dumpctx_t *dctx) {

	dctx->magic = 0;
	DESTROYLOCK(&dctx->lock);
	if (dctx->version != NULL)
		dns_db_closeversion(dctx->db, &dctx->version, ISC_FALSE);
	dns_dbiterator_destroy(&dctx->dbiter);
	dns_db_detach(&dctx->db);
	if (dctx->task != NULL)
		isc_task_detach(&dctx->task);
	if (dctx->file != NULL)
		isc_mem_free(dctx->mctx, dctx->file);
	if (dctx->tmpfile != NULL)
		isc_mem_free(dctx->mctx, dctx->tmpfile);
	isc_mem_putanddetach(&dctx->mctx, dctx, sizeof(*dctx));
}

void
dns_dumpctx_attach(dns_dumpctx_t *source, dns_dumpctx_t **target) {

	REQUIRE(DNS_DCTX_VALID(source));
	REQUIRE(target != NULL && *target == NULL);

	LOCK(&source->lock);
	INSIST(source->references > 0);
	source->references++;
	INSIST(source->references != 0);	/* Overflow? */
	UNLOCK(&source->lock);

	*target = source;
}

void
dns_dumpctx_detach(dns_dumpctx_t **dctxp) {
	dns_dumpctx_t *dctx;
	isc_boolean_t need_destroy = ISC_FALSE;
Mark Andrews's avatar
Mark Andrews committed
897

898 899
	REQUIRE(dctxp != NULL);
	dctx = *dctxp;
Mark Andrews's avatar
Mark Andrews committed
900
	REQUIRE(DNS_DCTX_VALID(dctx));
901 902 903 904 905 906 907 908 909 910 911 912 913

	*dctxp = NULL;

	LOCK(&dctx->lock);
	INSIST(dctx->references != 0);
	dctx->references--;
	if (dctx->references == 0)
		need_destroy = ISC_TRUE;
	UNLOCK(&dctx->lock);
	if (need_destroy)
		dumpctx_destroy(dctx);
}

914 915 916 917 918 919 920 921 922 923 924 925
dns_dbversion_t *
dns_dumpctx_version(dns_dumpctx_t *dctx) {
        REQUIRE(DNS_DCTX_VALID(dctx));
	return (dctx->version);
}

dns_db_t *
dns_dumpctx_db(dns_dumpctx_t *dctx) {
        REQUIRE(DNS_DCTX_VALID(dctx));
	return (dctx->db);
}

926 927
void
dns_dumpctx_cancel(dns_dumpctx_t *dctx) {
Mark Andrews's avatar
Mark Andrews committed
928 929 930 931 932
	REQUIRE(DNS_DCTX_VALID(dctx));

	LOCK(&dctx->lock);
	dctx->canceled = ISC_TRUE;
	UNLOCK(&dctx->lock);
933 934 935 936
}

static isc_result_t
closeandrename(FILE *f, isc_result_t result, const char *temp, const char *file)
937
{
938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974
	isc_result_t tresult;
	isc_boolean_t logit = ISC_TF(result == ISC_R_SUCCESS);

	if (result == ISC_R_SUCCESS)
		result = isc_stdio_sync(f);
	if (result != ISC_R_SUCCESS && logit) {
		isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL,
			      DNS_LOGMODULE_MASTERDUMP, ISC_LOG_ERROR,
			      "dumping master file: %s: fsync: %s",
			      temp, isc_result_totext(result));
		logit = ISC_FALSE;
	}
	tresult = isc_stdio_close(f);
	if (result == ISC_R_SUCCESS)
		result = tresult;
	if (result != ISC_R_SUCCESS && logit) {
		isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL,
			      DNS_LOGMODULE_MASTERDUMP, ISC_LOG_ERROR,
			      "dumping master file: %s: fclose: %s",
			      temp, isc_result_totext(result));
		logit = ISC_FALSE;
	}
	if (result == ISC_R_SUCCESS)
		result = isc_file_rename(temp, file);
	else
		(void)isc_file_remove(temp);
	if (result != ISC_R_SUCCESS && logit) {
		isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL,
			      DNS_LOGMODULE_MASTERDUMP, ISC_LOG_ERROR,
			      "dumping master file: rename: %s: %s",
			      file, isc_result_totext(result));
	}
	return (result);
}

static void
dump_quantum(isc_task_t *task, isc_event_t *event) {
975
	isc_result_t result;
976
	dns_dumpctx_t *dctx;
977

978 979 980 981 982 983 984 985 986 987 988
	REQUIRE(event != NULL);
	dctx = event->ev_arg;
	REQUIRE(DNS_DCTX_VALID(dctx));
	if (dctx->canceled)
		result = ISC_R_CANCELED;
	else
		result = dumptostreaminc(dctx);
	if (result == DNS_R_CONTINUE) {
		event->ev_arg = dctx;
		isc_task_send(task, &event);
		return;
989 990
	}

991 992 993 994 995 996 997 998 999
	if (dctx->file != NULL)
		result = closeandrename(dctx->f, result,
					dctx->tmpfile, dctx->file);
	if (dctx->version != NULL)
		dns_db_closeversion(dctx->db, &dctx->version, ISC_FALSE);
	(dctx->done)(dctx->done_arg, result);
	isc_event_free(&event);
	dns_dumpctx_detach(&dctx);
}
1000

1001 1002 1003
static isc_result_t
task_send(dns_dumpctx_t *dctx) {
	isc_event_t *event;
1004

Mark Andrews's avatar
Mark Andrews committed
1005 1006
	event = isc_event_allocate(dctx->mctx, NULL, DNS_EVENT_MASTERQUANTUM,
				   dump_quantum, dctx, sizeof(*event));
1007
	if (event == NULL)