1/*	$NetBSD: masterdump.c,v 1.14 2024/02/21 22:52:07 christos Exp $	*/
2
3/*
4 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
5 *
6 * SPDX-License-Identifier: MPL-2.0
7 *
8 * This Source Code Form is subject to the terms of the Mozilla Public
9 * License, v. 2.0. If a copy of the MPL was not distributed with this
10 * file, you can obtain one at https://mozilla.org/MPL/2.0/.
11 *
12 * See the COPYRIGHT file distributed with this work for additional
13 * information regarding copyright ownership.
14 */
15
16/*! \file */
17
18#include <inttypes.h>
19#include <stdbool.h>
20#include <stdlib.h>
21
22#include <isc/atomic.h>
23#include <isc/buffer.h>
24#include <isc/event.h>
25#include <isc/file.h>
26#include <isc/magic.h>
27#include <isc/mem.h>
28#include <isc/print.h>
29#include <isc/refcount.h>
30#include <isc/result.h>
31#include <isc/stdio.h>
32#include <isc/string.h>
33#include <isc/task.h>
34#include <isc/time.h>
35#include <isc/types.h>
36#include <isc/util.h>
37
38#include <dns/db.h>
39#include <dns/dbiterator.h>
40#include <dns/events.h>
41#include <dns/fixedname.h>
42#include <dns/log.h>
43#include <dns/master.h>
44#include <dns/masterdump.h>
45#include <dns/ncache.h>
46#include <dns/rdata.h>
47#include <dns/rdataclass.h>
48#include <dns/rdataset.h>
49#include <dns/rdatasetiter.h>
50#include <dns/rdatatype.h>
51#include <dns/time.h>
52#include <dns/ttl.h>
53
54#define DNS_DCTX_MAGIC	  ISC_MAGIC('D', 'c', 't', 'x')
55#define DNS_DCTX_VALID(d) ISC_MAGIC_VALID(d, DNS_DCTX_MAGIC)
56
57#define RETERR(x)                        \
58	do {                             \
59		isc_result_t _r = (x);   \
60		if (_r != ISC_R_SUCCESS) \
61			return ((_r));   \
62	} while (0)
63
64#define CHECK(x)                          \
65	do {                              \
66		if ((x) != ISC_R_SUCCESS) \
67			goto cleanup;     \
68	} while (0)
69
70struct dns_master_style {
71	dns_masterstyle_flags_t flags; /* DNS_STYLEFLAG_* */
72	unsigned int ttl_column;
73	unsigned int class_column;
74	unsigned int type_column;
75	unsigned int rdata_column;
76	unsigned int line_length;
77	unsigned int tab_width;
78	unsigned int split_width;
79};
80
81/*%
82 * The maximum length of the newline+indentation that is output
83 * when inserting a line break in an RR.  This effectively puts an
84 * upper limits on the value of "rdata_column", because if it is
85 * very large, the tabs and spaces needed to reach it will not fit.
86 */
87#define DNS_TOTEXT_LINEBREAK_MAXLEN 100
88
89/*% Does the rdataset 'r' contain a stale answer? */
90#define STALE(r) (((r)->attributes & DNS_RDATASETATTR_STALE) != 0)
91/*% Does the rdataset 'r' contain an expired answer? */
92#define ANCIENT(r) (((r)->attributes & DNS_RDATASETATTR_ANCIENT) != 0)
93
94/*%
95 * Context structure for a masterfile dump in progress.
96 */
97typedef struct dns_totext_ctx {
98	dns_master_style_t style;
99	bool class_printed;
100	char *linebreak;
101	char linebreak_buf[DNS_TOTEXT_LINEBREAK_MAXLEN];
102	dns_name_t *origin;
103	dns_name_t *neworigin;
104	dns_fixedname_t origin_fixname;
105	uint32_t current_ttl;
106	bool current_ttl_valid;
107	dns_ttl_t serve_stale_ttl;
108	dns_indent_t indent;
109} dns_totext_ctx_t;
110
111const dns_master_style_t dns_master_style_keyzone = {
112	DNS_STYLEFLAG_OMIT_OWNER | DNS_STYLEFLAG_OMIT_CLASS |
113		DNS_STYLEFLAG_REL_OWNER | DNS_STYLEFLAG_REL_DATA |
114		DNS_STYLEFLAG_OMIT_TTL | DNS_STYLEFLAG_TTL |
115		DNS_STYLEFLAG_COMMENT | DNS_STYLEFLAG_RRCOMMENT |
116		DNS_STYLEFLAG_MULTILINE | DNS_STYLEFLAG_KEYDATA,
117	24,
118	24,
119	24,
120	32,
121	80,
122	8,
123	UINT_MAX
124};
125
126const dns_master_style_t dns_master_style_default = {
127	DNS_STYLEFLAG_OMIT_OWNER | DNS_STYLEFLAG_OMIT_CLASS |
128		DNS_STYLEFLAG_REL_OWNER | DNS_STYLEFLAG_REL_DATA |
129		DNS_STYLEFLAG_OMIT_TTL | DNS_STYLEFLAG_TTL |
130		DNS_STYLEFLAG_COMMENT | DNS_STYLEFLAG_RRCOMMENT |
131		DNS_STYLEFLAG_MULTILINE,
132	24,
133	24,
134	24,
135	32,
136	80,
137	8,
138	UINT_MAX
139};
140
141const dns_master_style_t dns_master_style_full = {
142	DNS_STYLEFLAG_COMMENT | DNS_STYLEFLAG_RESIGN,
143	46,
144	46,
145	46,
146	64,
147	120,
148	8,
149	UINT_MAX
150};
151
152const dns_master_style_t dns_master_style_explicitttl = {
153	DNS_STYLEFLAG_OMIT_OWNER | DNS_STYLEFLAG_OMIT_CLASS |
154		DNS_STYLEFLAG_REL_OWNER | DNS_STYLEFLAG_REL_DATA |
155		DNS_STYLEFLAG_COMMENT | DNS_STYLEFLAG_RRCOMMENT |
156		DNS_STYLEFLAG_MULTILINE,
157	24,
158	32,
159	32,
160	40,
161	80,
162	8,
163	UINT_MAX
164};
165
166const dns_master_style_t dns_master_style_cache = {
167	DNS_STYLEFLAG_OMIT_OWNER | DNS_STYLEFLAG_OMIT_CLASS |
168		DNS_STYLEFLAG_MULTILINE | DNS_STYLEFLAG_RRCOMMENT |
169		DNS_STYLEFLAG_TRUST | DNS_STYLEFLAG_NCACHE,
170	24,
171	32,
172	32,
173	40,
174	80,
175	8,
176	UINT_MAX
177};
178
179const dns_master_style_t dns_master_style_cache_with_expired = {
180	DNS_STYLEFLAG_OMIT_OWNER | DNS_STYLEFLAG_OMIT_CLASS |
181		DNS_STYLEFLAG_MULTILINE | DNS_STYLEFLAG_RRCOMMENT |
182		DNS_STYLEFLAG_TRUST | DNS_STYLEFLAG_NCACHE |
183		DNS_STYLEFLAG_EXPIRED,
184	24,
185	32,
186	32,
187	40,
188	80,
189	8,
190	UINT_MAX
191};
192
193const dns_master_style_t dns_master_style_simple = { 0,	 24, 32, 32,
194						     40, 80, 8,	 UINT_MAX };
195
196/*%
197 * A style suitable for dns_rdataset_totext().
198 */
199const dns_master_style_t dns_master_style_debug = {
200	DNS_STYLEFLAG_REL_OWNER, 24, 32, 40, 48, 80, 8, UINT_MAX
201};
202
203/*%
204 * Similar, but indented (i.e., prepended with indentctx.string).
205 */
206const dns_master_style_t dns_master_style_indent = {
207	DNS_STYLEFLAG_REL_OWNER | DNS_STYLEFLAG_INDENT,
208	24,
209	32,
210	40,
211	48,
212	80,
213	8,
214	UINT_MAX
215};
216
217/*%
218 * Similar, but with each line commented out.
219 */
220const dns_master_style_t dns_master_style_comment = {
221	DNS_STYLEFLAG_REL_OWNER | DNS_STYLEFLAG_MULTILINE |
222		DNS_STYLEFLAG_RRCOMMENT | DNS_STYLEFLAG_COMMENTDATA,
223	24,
224	32,
225	40,
226	48,
227	80,
228	8,
229	UINT_MAX
230};
231
232/*%
233 * YAML style
234 */
235const dns_master_style_t dns_master_style_yaml = {
236	DNS_STYLEFLAG_YAML | DNS_STYLEFLAG_REL_OWNER | DNS_STYLEFLAG_INDENT,
237	24,
238	32,
239	40,
240	48,
241	80,
242	8,
243	UINT_MAX
244};
245
246#define N_SPACES 10
247static char spaces[N_SPACES + 1] = "          ";
248
249#define N_TABS 10
250static char tabs[N_TABS + 1] = "\t\t\t\t\t\t\t\t\t\t";
251
252struct dns_dumpctx {
253	unsigned int magic;
254	isc_mem_t *mctx;
255	isc_mutex_t lock;
256	isc_refcount_t references;
257	atomic_bool canceled;
258	bool do_date;
259	isc_stdtime_t now;
260	FILE *f;
261	dns_db_t *db;
262	dns_dbversion_t *version;
263	dns_dbiterator_t *dbiter;
264	dns_totext_ctx_t tctx;
265	isc_task_t *task;
266	dns_dumpdonefunc_t done;
267	void *done_arg;
268	/* dns_master_dumpasync() */
269	isc_result_t result;
270	char *file;
271	char *tmpfile;
272	dns_masterformat_t format;
273	dns_masterrawheader_t header;
274	isc_result_t (*dumpsets)(isc_mem_t *mctx, const dns_name_t *name,
275				 dns_rdatasetiter_t *rdsiter,
276				 dns_totext_ctx_t *ctx, isc_buffer_t *buffer,
277				 FILE *f);
278};
279
280#define NXDOMAIN(x) (((x)->attributes & DNS_RDATASETATTR_NXDOMAIN) != 0)
281
282static const dns_indent_t default_indent = { "\t", 1 };
283static const dns_indent_t default_yamlindent = { "  ", 1 };
284
285/*%
286 * Output tabs and spaces to go from column '*current' to
287 * column 'to', and update '*current' to reflect the new
288 * current column.
289 */
290static isc_result_t
291indent(unsigned int *current, unsigned int to, int tabwidth,
292       isc_buffer_t *target) {
293	isc_region_t r;
294	unsigned char *p;
295	unsigned int from;
296	int ntabs, nspaces, t;
297
298	from = *current;
299
300	if (to < from + 1) {
301		to = from + 1;
302	}
303
304	ntabs = to / tabwidth - from / tabwidth;
305	if (ntabs < 0) {
306		ntabs = 0;
307	}
308
309	if (ntabs > 0) {
310		isc_buffer_availableregion(target, &r);
311		if (r.length < (unsigned)ntabs) {
312			return (ISC_R_NOSPACE);
313		}
314		p = r.base;
315
316		t = ntabs;
317		while (t) {
318			int n = t;
319			if (n > N_TABS) {
320				n = N_TABS;
321			}
322			memmove(p, tabs, n);
323			p += n;
324			t -= n;
325		}
326		isc_buffer_add(target, ntabs);
327		from = (to / tabwidth) * tabwidth;
328	}
329
330	nspaces = to - from;
331	INSIST(nspaces >= 0);
332
333	isc_buffer_availableregion(target, &r);
334	if (r.length < (unsigned)nspaces) {
335		return (ISC_R_NOSPACE);
336	}
337	p = r.base;
338
339	t = nspaces;
340	while (t) {
341		int n = t;
342		if (n > N_SPACES) {
343			n = N_SPACES;
344		}
345		memmove(p, spaces, n);
346		p += n;
347		t -= n;
348	}
349	isc_buffer_add(target, nspaces);
350
351	*current = to;
352	return (ISC_R_SUCCESS);
353}
354
355static isc_result_t
356totext_ctx_init(const dns_master_style_t *style, const dns_indent_t *indentctx,
357		dns_totext_ctx_t *ctx) {
358	isc_result_t result;
359
360	REQUIRE(style->tab_width != 0);
361
362	if (indentctx == NULL) {
363		if ((style->flags & DNS_STYLEFLAG_YAML) != 0) {
364			indentctx = &default_yamlindent;
365		} else {
366			indentctx = &default_indent;
367		}
368	}
369
370	ctx->style = *style;
371	ctx->class_printed = false;
372
373	dns_fixedname_init(&ctx->origin_fixname);
374
375	/*
376	 * Set up the line break string if needed.
377	 */
378	if ((ctx->style.flags & DNS_STYLEFLAG_MULTILINE) != 0) {
379		isc_buffer_t buf;
380		isc_region_t r;
381		unsigned int col = 0;
382
383		isc_buffer_init(&buf, ctx->linebreak_buf,
384				sizeof(ctx->linebreak_buf));
385
386		isc_buffer_availableregion(&buf, &r);
387		if (r.length < 1) {
388			return (DNS_R_TEXTTOOLONG);
389		}
390		r.base[0] = '\n';
391		isc_buffer_add(&buf, 1);
392
393		if ((ctx->style.flags & DNS_STYLEFLAG_INDENT) != 0 ||
394		    (ctx->style.flags & DNS_STYLEFLAG_YAML) != 0)
395		{
396			unsigned int i, len = strlen(indentctx->string);
397			for (i = 0; i < indentctx->count; i++) {
398				if (isc_buffer_availablelength(&buf) < len) {
399					return (DNS_R_TEXTTOOLONG);
400				}
401				isc_buffer_putstr(&buf, indentctx->string);
402			}
403		}
404
405		if ((ctx->style.flags & DNS_STYLEFLAG_COMMENTDATA) != 0) {
406			isc_buffer_availableregion(&buf, &r);
407			if (r.length < 1) {
408				return (DNS_R_TEXTTOOLONG);
409			}
410			r.base[0] = ';';
411			isc_buffer_add(&buf, 1);
412		}
413
414		result = indent(&col, ctx->style.rdata_column,
415				ctx->style.tab_width, &buf);
416		/*
417		 * Do not return ISC_R_NOSPACE if the line break string
418		 * buffer is too small, because that would just make
419		 * dump_rdataset() retry indefinitely with ever
420		 * bigger target buffers.  That's a different buffer,
421		 * so it won't help.  Use DNS_R_TEXTTOOLONG as a substitute.
422		 */
423		if (result == ISC_R_NOSPACE) {
424			return (DNS_R_TEXTTOOLONG);
425		}
426		if (result != ISC_R_SUCCESS) {
427			return (result);
428		}
429
430		isc_buffer_availableregion(&buf, &r);
431		if (r.length < 1) {
432			return (DNS_R_TEXTTOOLONG);
433		}
434		r.base[0] = '\0';
435		isc_buffer_add(&buf, 1);
436		ctx->linebreak = ctx->linebreak_buf;
437	} else {
438		ctx->linebreak = NULL;
439	}
440
441	ctx->origin = NULL;
442	ctx->neworigin = NULL;
443	ctx->current_ttl = 0;
444	ctx->current_ttl_valid = false;
445	ctx->serve_stale_ttl = 0;
446	ctx->indent = *indentctx;
447
448	return (ISC_R_SUCCESS);
449}
450
451#define INDENT_TO(col)                                                        \
452	do {                                                                  \
453		if ((ctx->style.flags & DNS_STYLEFLAG_YAML) != 0) {           \
454			if ((result = str_totext(" ", target)) !=             \
455			    ISC_R_SUCCESS)                                    \
456				return ((result));                            \
457		} else if ((result = indent(&column, ctx->style.col,          \
458					    ctx->style.tab_width, target)) != \
459			   ISC_R_SUCCESS)                                     \
460			return ((result));                                    \
461	} while (0)
462
463static isc_result_t
464str_totext(const char *source, isc_buffer_t *target) {
465	unsigned int l;
466	isc_region_t region;
467
468	isc_buffer_availableregion(target, &region);
469	l = strlen(source);
470
471	if (l > region.length) {
472		return (ISC_R_NOSPACE);
473	}
474
475	memmove(region.base, source, l);
476	isc_buffer_add(target, l);
477	return (ISC_R_SUCCESS);
478}
479
480static isc_result_t
481ncache_summary(dns_rdataset_t *rdataset, bool omit_final_dot,
482	       dns_totext_ctx_t *ctx, isc_buffer_t *target) {
483	isc_result_t result = ISC_R_SUCCESS;
484	dns_rdataset_t rds;
485	dns_name_t name;
486
487	dns_rdataset_init(&rds);
488	dns_name_init(&name, NULL);
489
490	do {
491		dns_ncache_current(rdataset, &name, &rds);
492		for (result = dns_rdataset_first(&rds); result == ISC_R_SUCCESS;
493		     result = dns_rdataset_next(&rds))
494		{
495			if ((ctx->style.flags & DNS_STYLEFLAG_INDENT) != 0 ||
496			    (ctx->style.flags & DNS_STYLEFLAG_YAML) != 0)
497			{
498				unsigned int i;
499				for (i = 0; i < ctx->indent.count; i++) {
500					CHECK(str_totext(ctx->indent.string,
501							 target));
502				}
503			}
504
505			if ((ctx->style.flags & DNS_STYLEFLAG_YAML) != 0) {
506				CHECK(str_totext("- ", target));
507			} else {
508				CHECK(str_totext("; ", target));
509			}
510
511			CHECK(dns_name_totext(&name, omit_final_dot, target));
512			CHECK(str_totext(" ", target));
513			CHECK(dns_rdatatype_totext(rds.type, target));
514			if (rds.type == dns_rdatatype_rrsig) {
515				CHECK(str_totext(" ", target));
516				CHECK(dns_rdatatype_totext(rds.covers, target));
517				CHECK(str_totext(" ...\n", target));
518			} else {
519				dns_rdata_t rdata = DNS_RDATA_INIT;
520				dns_rdataset_current(&rds, &rdata);
521				CHECK(str_totext(" ", target));
522				CHECK(dns_rdata_tofmttext(&rdata, dns_rootname,
523							  0, 0, 0, " ",
524							  target));
525				CHECK(str_totext("\n", target));
526			}
527		}
528		dns_rdataset_disassociate(&rds);
529		result = dns_rdataset_next(rdataset);
530	} while (result == ISC_R_SUCCESS);
531
532	if (result == ISC_R_NOMORE) {
533		result = ISC_R_SUCCESS;
534	}
535cleanup:
536	if (dns_rdataset_isassociated(&rds)) {
537		dns_rdataset_disassociate(&rds);
538	}
539
540	return (result);
541}
542
543/*
544 * Convert 'rdataset' to master file text format according to 'ctx',
545 * storing the result in 'target'.  If 'owner_name' is NULL, it
546 * is omitted; otherwise 'owner_name' must be valid and have at least
547 * one label.
548 */
549
550static isc_result_t
551rdataset_totext(dns_rdataset_t *rdataset, const dns_name_t *owner_name,
552		dns_totext_ctx_t *ctx, bool omit_final_dot,
553		isc_buffer_t *target) {
554	isc_result_t result;
555	unsigned int column;
556	bool first = true;
557	uint32_t current_ttl;
558	bool current_ttl_valid;
559	dns_rdatatype_t type;
560	unsigned int type_start;
561	dns_fixedname_t fixed;
562	dns_name_t *name = NULL;
563	unsigned int i;
564
565	REQUIRE(DNS_RDATASET_VALID(rdataset));
566
567	rdataset->attributes |= DNS_RDATASETATTR_LOADORDER;
568	result = dns_rdataset_first(rdataset);
569
570	current_ttl = ctx->current_ttl;
571	current_ttl_valid = ctx->current_ttl_valid;
572
573	if (owner_name != NULL) {
574		name = dns_fixedname_initname(&fixed);
575		dns_name_copy(owner_name, name);
576		dns_rdataset_getownercase(rdataset, name);
577	}
578
579	while (result == ISC_R_SUCCESS) {
580		column = 0;
581
582		/*
583		 * Indent?
584		 */
585		if ((ctx->style.flags & DNS_STYLEFLAG_INDENT) != 0 ||
586		    (ctx->style.flags & DNS_STYLEFLAG_YAML) != 0)
587		{
588			for (i = 0; i < ctx->indent.count; i++) {
589				RETERR(str_totext(ctx->indent.string, target));
590			}
591		}
592
593		/*
594		 * YAML or comment prefix?
595		 */
596		if ((ctx->style.flags & DNS_STYLEFLAG_YAML) != 0) {
597			RETERR(str_totext("- ", target));
598		} else if ((ctx->style.flags & DNS_STYLEFLAG_COMMENTDATA) != 0)
599		{
600			RETERR(str_totext(";", target));
601		}
602
603		/*
604		 * Owner name.
605		 */
606		if (name != NULL &&
607		    !((ctx->style.flags & DNS_STYLEFLAG_OMIT_OWNER) != 0 &&
608		      !first))
609		{
610			unsigned int name_start = target->used;
611			RETERR(dns_name_totext(name, omit_final_dot, target));
612			column += target->used - name_start;
613		}
614
615		/*
616		 * TTL.
617		 */
618		if ((ctx->style.flags & DNS_STYLEFLAG_NO_TTL) == 0 &&
619		    !((ctx->style.flags & DNS_STYLEFLAG_OMIT_TTL) != 0 &&
620		      current_ttl_valid && rdataset->ttl == current_ttl))
621		{
622			char ttlbuf[64];
623			isc_region_t r;
624			unsigned int length;
625
626			INDENT_TO(ttl_column);
627			if ((ctx->style.flags & DNS_STYLEFLAG_TTL_UNITS) != 0) {
628				length = target->used;
629				result = dns_ttl_totext(rdataset->ttl, false,
630							false, target);
631				if (result != ISC_R_SUCCESS) {
632					return (result);
633				}
634				column += target->used - length;
635			} else {
636				length = snprintf(ttlbuf, sizeof(ttlbuf), "%u",
637						  rdataset->ttl);
638				INSIST(length <= sizeof(ttlbuf));
639				isc_buffer_availableregion(target, &r);
640				if (r.length < length) {
641					return (ISC_R_NOSPACE);
642				}
643				memmove(r.base, ttlbuf, length);
644				isc_buffer_add(target, length);
645				column += length;
646			}
647
648			/*
649			 * If the $TTL directive is not in use, the TTL we
650			 * just printed becomes the default for subsequent RRs.
651			 */
652			if ((ctx->style.flags & DNS_STYLEFLAG_TTL) == 0) {
653				current_ttl = rdataset->ttl;
654				current_ttl_valid = true;
655			}
656		}
657
658		/*
659		 * Class.
660		 */
661		if ((ctx->style.flags & DNS_STYLEFLAG_NO_CLASS) == 0 &&
662		    ((ctx->style.flags & DNS_STYLEFLAG_OMIT_CLASS) == 0 ||
663		     !ctx->class_printed))
664		{
665			unsigned int class_start;
666			INDENT_TO(class_column);
667			class_start = target->used;
668			if ((ctx->style.flags & DNS_STYLEFLAG_UNKNOWNFORMAT) !=
669			    0)
670			{
671				result = dns_rdataclass_tounknowntext(
672					rdataset->rdclass, target);
673			} else {
674				result = dns_rdataclass_totext(
675					rdataset->rdclass, target);
676			}
677			if (result != ISC_R_SUCCESS) {
678				return (result);
679			}
680			column += (target->used - class_start);
681		}
682
683		/*
684		 * Type.
685		 */
686
687		if ((rdataset->attributes & DNS_RDATASETATTR_NEGATIVE) != 0) {
688			type = rdataset->covers;
689		} else {
690			type = rdataset->type;
691		}
692
693		INDENT_TO(type_column);
694		type_start = target->used;
695		if ((rdataset->attributes & DNS_RDATASETATTR_NEGATIVE) != 0) {
696			RETERR(str_totext("\\-", target));
697		}
698		switch (type) {
699		case dns_rdatatype_keydata:
700#define KEYDATA "KEYDATA"
701			if ((ctx->style.flags & DNS_STYLEFLAG_KEYDATA) != 0) {
702				if (isc_buffer_availablelength(target) <
703				    (sizeof(KEYDATA) - 1))
704				{
705					return (ISC_R_NOSPACE);
706				}
707				isc_buffer_putstr(target, KEYDATA);
708				break;
709			}
710			FALLTHROUGH;
711		default:
712			if ((ctx->style.flags & DNS_STYLEFLAG_UNKNOWNFORMAT) !=
713			    0)
714			{
715				result = dns_rdatatype_tounknowntext(type,
716								     target);
717			} else {
718				result = dns_rdatatype_totext(type, target);
719			}
720			if (result != ISC_R_SUCCESS) {
721				return (result);
722			}
723		}
724		column += (target->used - type_start);
725
726		/*
727		 * Rdata.
728		 */
729		INDENT_TO(rdata_column);
730		if ((rdataset->attributes & DNS_RDATASETATTR_NEGATIVE) != 0) {
731			if (NXDOMAIN(rdataset)) {
732				RETERR(str_totext(";-$NXDOMAIN\n", target));
733			} else {
734				RETERR(str_totext(";-$NXRRSET\n", target));
735			}
736			/*
737			 * Print a summary of the cached records which make
738			 * up the negative response.
739			 */
740			RETERR(ncache_summary(rdataset, omit_final_dot, ctx,
741					      target));
742			break;
743		} else {
744			dns_rdata_t rdata = DNS_RDATA_INIT;
745			isc_region_t r;
746
747			dns_rdataset_current(rdataset, &rdata);
748
749			RETERR(dns_rdata_tofmttext(
750				&rdata, ctx->origin, ctx->style.flags,
751				ctx->style.line_length -
752					ctx->style.rdata_column,
753				ctx->style.split_width, ctx->linebreak,
754				target));
755
756			isc_buffer_availableregion(target, &r);
757			if (r.length < 1) {
758				return (ISC_R_NOSPACE);
759			}
760			r.base[0] = '\n';
761			isc_buffer_add(target, 1);
762		}
763
764		first = false;
765		result = dns_rdataset_next(rdataset);
766	}
767
768	if (result != ISC_R_NOMORE) {
769		return (result);
770	}
771
772	/*
773	 * Update the ctx state to reflect what we just printed.
774	 * This is done last, only when we are sure we will return
775	 * success, because this function may be called multiple
776	 * times with increasing buffer sizes until it succeeds,
777	 * and failed attempts must not update the state prematurely.
778	 */
779	ctx->class_printed = true;
780	ctx->current_ttl = current_ttl;
781	ctx->current_ttl_valid = current_ttl_valid;
782
783	return (ISC_R_SUCCESS);
784}
785
786/*
787 * Print the name, type, and class of an empty rdataset,
788 * such as those used to represent the question section
789 * of a DNS message.
790 */
791static isc_result_t
792question_totext(dns_rdataset_t *rdataset, const dns_name_t *owner_name,
793		dns_totext_ctx_t *ctx, bool omit_final_dot,
794		isc_buffer_t *target) {
795	unsigned int column;
796	isc_result_t result;
797	isc_region_t r;
798
799	REQUIRE(DNS_RDATASET_VALID(rdataset));
800	result = dns_rdataset_first(rdataset);
801	REQUIRE(result == ISC_R_NOMORE);
802
803	column = 0;
804
805	/* Owner name */
806	{
807		unsigned int name_start = target->used;
808		RETERR(dns_name_totext(owner_name, omit_final_dot, target));
809		column += target->used - name_start;
810	}
811
812	/* Class */
813	{
814		unsigned int class_start;
815		INDENT_TO(class_column);
816		class_start = target->used;
817		if ((ctx->style.flags & DNS_STYLEFLAG_UNKNOWNFORMAT) != 0) {
818			result = dns_rdataclass_tounknowntext(rdataset->rdclass,
819							      target);
820		} else {
821			result = dns_rdataclass_totext(rdataset->rdclass,
822						       target);
823		}
824		if (result != ISC_R_SUCCESS) {
825			return (result);
826		}
827		column += (target->used - class_start);
828	}
829
830	/* Type */
831	{
832		unsigned int type_start;
833		INDENT_TO(type_column);
834		type_start = target->used;
835		if ((ctx->style.flags & DNS_STYLEFLAG_UNKNOWNFORMAT) != 0) {
836			result = dns_rdatatype_tounknowntext(rdataset->type,
837							     target);
838		} else {
839			result = dns_rdatatype_totext(rdataset->type, target);
840		}
841		if (result != ISC_R_SUCCESS) {
842			return (result);
843		}
844		column += (target->used - type_start);
845	}
846
847	isc_buffer_availableregion(target, &r);
848	if (r.length < 1) {
849		return (ISC_R_NOSPACE);
850	}
851	r.base[0] = '\n';
852	isc_buffer_add(target, 1);
853
854	return (ISC_R_SUCCESS);
855}
856
857isc_result_t
858dns_rdataset_totext(dns_rdataset_t *rdataset, const dns_name_t *owner_name,
859		    bool omit_final_dot, bool question, isc_buffer_t *target) {
860	dns_totext_ctx_t ctx;
861	isc_result_t result;
862	result = totext_ctx_init(&dns_master_style_debug, NULL, &ctx);
863	if (result != ISC_R_SUCCESS) {
864		UNEXPECTED_ERROR("could not set master file style");
865		return (ISC_R_UNEXPECTED);
866	}
867
868	/*
869	 * The caller might want to give us an empty owner
870	 * name (e.g. if they are outputting into a master
871	 * file and this rdataset has the same name as the
872	 * previous one.)
873	 */
874	if (dns_name_countlabels(owner_name) == 0) {
875		owner_name = NULL;
876	}
877
878	if (question) {
879		return (question_totext(rdataset, owner_name, &ctx,
880					omit_final_dot, target));
881	} else {
882		return (rdataset_totext(rdataset, owner_name, &ctx,
883					omit_final_dot, target));
884	}
885}
886
887isc_result_t
888dns_master_rdatasettotext(const dns_name_t *owner_name,
889			  dns_rdataset_t *rdataset,
890			  const dns_master_style_t *style, dns_indent_t *indent,
891			  isc_buffer_t *target) {
892	dns_totext_ctx_t ctx;
893	isc_result_t result;
894	result = totext_ctx_init(style, indent, &ctx);
895	if (result != ISC_R_SUCCESS) {
896		UNEXPECTED_ERROR("could not set master file style");
897		return (ISC_R_UNEXPECTED);
898	}
899
900	return (rdataset_totext(rdataset, owner_name, &ctx, false, target));
901}
902
903isc_result_t
904dns_master_questiontotext(const dns_name_t *owner_name,
905			  dns_rdataset_t *rdataset,
906			  const dns_master_style_t *style,
907			  isc_buffer_t *target) {
908	dns_totext_ctx_t ctx;
909	isc_result_t result;
910	result = totext_ctx_init(style, NULL, &ctx);
911	if (result != ISC_R_SUCCESS) {
912		UNEXPECTED_ERROR("could not set master file style");
913		return (ISC_R_UNEXPECTED);
914	}
915
916	return (question_totext(rdataset, owner_name, &ctx, false, target));
917}
918
919/*
920 * Print an rdataset.  'buffer' is a scratch buffer, which must have been
921 * dynamically allocated by the caller.  It must be large enough to
922 * hold the result from dns_ttl_totext().  If more than that is needed,
923 * the buffer will be grown automatically.
924 */
925
926static isc_result_t
927dump_rdataset(isc_mem_t *mctx, const dns_name_t *name, dns_rdataset_t *rdataset,
928	      dns_totext_ctx_t *ctx, isc_buffer_t *buffer, FILE *f) {
929	isc_region_t r;
930	isc_result_t result;
931
932	REQUIRE(buffer->length > 0);
933
934	/*
935	 * Output a $TTL directive if needed.
936	 */
937
938	if ((ctx->style.flags & DNS_STYLEFLAG_TTL) != 0) {
939		if (!ctx->current_ttl_valid ||
940		    ctx->current_ttl != rdataset->ttl)
941		{
942			if ((ctx->style.flags & DNS_STYLEFLAG_COMMENT) != 0) {
943				isc_buffer_clear(buffer);
944				result = dns_ttl_totext(rdataset->ttl, true,
945							true, buffer);
946				INSIST(result == ISC_R_SUCCESS);
947				isc_buffer_usedregion(buffer, &r);
948				fprintf(f, "$TTL %u\t; %.*s\n", rdataset->ttl,
949					(int)r.length, (char *)r.base);
950			} else {
951				fprintf(f, "$TTL %u\n", rdataset->ttl);
952			}
953			ctx->current_ttl = rdataset->ttl;
954			ctx->current_ttl_valid = true;
955		}
956	}
957
958	isc_buffer_clear(buffer);
959
960	/*
961	 * Generate the text representation of the rdataset into
962	 * the buffer.  If the buffer is too small, grow it.
963	 */
964	for (;;) {
965		int newlength;
966		void *newmem;
967		result = rdataset_totext(rdataset, name, ctx, false, buffer);
968		if (result != ISC_R_NOSPACE) {
969			break;
970		}
971
972		newlength = buffer->length * 2;
973		newmem = isc_mem_get(mctx, newlength);
974		isc_mem_put(mctx, buffer->base, buffer->length);
975		isc_buffer_init(buffer, newmem, newlength);
976	}
977	if (result != ISC_R_SUCCESS) {
978		return (result);
979	}
980
981	/*
982	 * Write the buffer contents to the master file.
983	 */
984	isc_buffer_usedregion(buffer, &r);
985	result = isc_stdio_write(r.base, 1, (size_t)r.length, f, NULL);
986
987	if (result != ISC_R_SUCCESS) {
988		UNEXPECTED_ERROR("master file write failed: %s",
989				 isc_result_totext(result));
990		return (result);
991	}
992
993	return (ISC_R_SUCCESS);
994}
995
996/*
997 * Define the order in which rdatasets should be printed in zone
998 * files.  We will print SOA and NS records before others, SIGs
999 * immediately following the things they sign, and order everything
1000 * else by RR number.  This is all just for aesthetics and
1001 * compatibility with buggy software that expects the SOA to be first;
1002 * the DNS specifications allow any order.
1003 */
1004
1005static int
1006dump_order(const dns_rdataset_t *rds) {
1007	int t;
1008	int sig;
1009	if (rds->type == dns_rdatatype_rrsig) {
1010		t = rds->covers;
1011		sig = 1;
1012	} else {
1013		t = rds->type;
1014		sig = 0;
1015	}
1016	switch (t) {
1017	case dns_rdatatype_soa:
1018		t = 0;
1019		break;
1020	case dns_rdatatype_ns:
1021		t = 1;
1022		break;
1023	default:
1024		t += 2;
1025		break;
1026	}
1027	return ((t << 1) + sig);
1028}
1029
1030static int
1031dump_order_compare(const void *a, const void *b) {
1032	return (dump_order(*((const dns_rdataset_t *const *)a)) -
1033		dump_order(*((const dns_rdataset_t *const *)b)));
1034}
1035
1036/*
1037 * Dump all the rdatasets of a domain name to a master file.  We make
1038 * a "best effort" attempt to sort the RRsets in a nice order, but if
1039 * there are more than MAXSORT RRsets, we punt and only sort them in
1040 * groups of MAXSORT.  This is not expected to ever happen in practice
1041 * since much less than 64 RR types have been registered with the
1042 * IANA, so far, and the output will be correct (though not
1043 * aesthetically pleasing) even if it does happen.
1044 */
1045
1046#define MAXSORT 64
1047
1048static isc_result_t
1049dump_rdatasets_text(isc_mem_t *mctx, const dns_name_t *name,
1050		    dns_rdatasetiter_t *rdsiter, dns_totext_ctx_t *ctx,
1051		    isc_buffer_t *buffer, FILE *f) {
1052	isc_result_t itresult, dumpresult;
1053	isc_region_t r;
1054	dns_rdataset_t rdatasets[MAXSORT];
1055	dns_rdataset_t *sorted[MAXSORT];
1056	int i, n;
1057
1058	itresult = dns_rdatasetiter_first(rdsiter);
1059	dumpresult = ISC_R_SUCCESS;
1060
1061	if (itresult == ISC_R_SUCCESS && ctx->neworigin != NULL) {
1062		isc_buffer_clear(buffer);
1063		itresult = dns_name_totext(ctx->neworigin, false, buffer);
1064		RUNTIME_CHECK(itresult == ISC_R_SUCCESS);
1065		isc_buffer_usedregion(buffer, &r);
1066		fprintf(f, "$ORIGIN %.*s\n", (int)r.length, (char *)r.base);
1067		ctx->neworigin = NULL;
1068	}
1069
1070again:
1071	for (i = 0; itresult == ISC_R_SUCCESS && i < MAXSORT;
1072	     itresult = dns_rdatasetiter_next(rdsiter), i++)
1073	{
1074		dns_rdataset_init(&rdatasets[i]);
1075		dns_rdatasetiter_current(rdsiter, &rdatasets[i]);
1076		sorted[i] = &rdatasets[i];
1077	}
1078	n = i;
1079	INSIST(n <= MAXSORT);
1080
1081	qsort(sorted, n, sizeof(sorted[0]), dump_order_compare);
1082
1083	for (i = 0; i < n; i++) {
1084		dns_rdataset_t *rds = sorted[i];
1085
1086		if (ANCIENT(rds) &&
1087		    (ctx->style.flags & DNS_STYLEFLAG_EXPIRED) == 0)
1088		{
1089			/* Omit expired entries */
1090			dns_rdataset_disassociate(rds);
1091			continue;
1092		}
1093
1094		if ((ctx->style.flags & DNS_STYLEFLAG_TRUST) != 0) {
1095			if ((ctx->style.flags & DNS_STYLEFLAG_INDENT) != 0 ||
1096			    (ctx->style.flags & DNS_STYLEFLAG_YAML) != 0)
1097			{
1098				unsigned int j;
1099				for (j = 0; j < ctx->indent.count; j++) {
1100					fprintf(f, "%s", ctx->indent.string);
1101				}
1102			}
1103			fprintf(f, "; %s\n", dns_trust_totext(rds->trust));
1104		}
1105		if (((rds->attributes & DNS_RDATASETATTR_NEGATIVE) != 0) &&
1106		    (ctx->style.flags & DNS_STYLEFLAG_NCACHE) == 0)
1107		{
1108			/* Omit negative cache entries */
1109		} else {
1110			isc_result_t result;
1111			if (STALE(rds)) {
1112				fprintf(f, "; stale\n");
1113			} else if (ANCIENT(rds)) {
1114				isc_buffer_t b;
1115				char buf[sizeof("YYYYMMDDHHMMSS")];
1116				memset(buf, 0, sizeof(buf));
1117				isc_buffer_init(&b, buf, sizeof(buf) - 1);
1118				dns_time64_totext((uint64_t)rds->ttl, &b);
1119				fprintf(f,
1120					"; expired since %s "
1121					"(awaiting cleanup)\n",
1122					buf);
1123			}
1124			result = dump_rdataset(mctx, name, rds, ctx, buffer, f);
1125			if (result != ISC_R_SUCCESS) {
1126				dumpresult = result;
1127			}
1128			if ((ctx->style.flags & DNS_STYLEFLAG_OMIT_OWNER) != 0)
1129			{
1130				name = NULL;
1131			}
1132		}
1133		if (((ctx->style.flags & DNS_STYLEFLAG_RESIGN) != 0) &&
1134		    ((rds->attributes & DNS_RDATASETATTR_RESIGN) != 0))
1135		{
1136			isc_buffer_t b;
1137			char buf[sizeof("YYYYMMDDHHMMSS")];
1138			memset(buf, 0, sizeof(buf));
1139			isc_buffer_init(&b, buf, sizeof(buf) - 1);
1140			dns_time64_totext((uint64_t)rds->resign, &b);
1141			if ((ctx->style.flags & DNS_STYLEFLAG_INDENT) != 0 ||
1142			    (ctx->style.flags & DNS_STYLEFLAG_YAML) != 0)
1143			{
1144				unsigned int j;
1145				for (j = 0; j < ctx->indent.count; j++) {
1146					fprintf(f, "%s", ctx->indent.string);
1147				}
1148			}
1149			fprintf(f, "; resign=%s\n", buf);
1150		}
1151		dns_rdataset_disassociate(rds);
1152	}
1153
1154	if (dumpresult != ISC_R_SUCCESS) {
1155		return (dumpresult);
1156	}
1157
1158	/*
1159	 * If we got more data than could be sorted at once,
1160	 * go handle the rest.
1161	 */
1162	if (itresult == ISC_R_SUCCESS) {
1163		goto again;
1164	}
1165
1166	if (itresult == ISC_R_NOMORE) {
1167		itresult = ISC_R_SUCCESS;
1168	}
1169
1170	return (itresult);
1171}
1172
1173/*
1174 * Dump given RRsets in the "raw" format.
1175 */
1176static isc_result_t
1177dump_rdataset_raw(isc_mem_t *mctx, const dns_name_t *name,
1178		  dns_rdataset_t *rdataset, isc_buffer_t *buffer, FILE *f) {
1179	isc_result_t result;
1180	uint32_t totallen;
1181	uint16_t dlen;
1182	isc_region_t r, r_hdr;
1183
1184	REQUIRE(buffer->length > 0);
1185	REQUIRE(DNS_RDATASET_VALID(rdataset));
1186
1187	rdataset->attributes |= DNS_RDATASETATTR_LOADORDER;
1188restart:
1189	totallen = 0;
1190	result = dns_rdataset_first(rdataset);
1191	REQUIRE(result == ISC_R_SUCCESS);
1192
1193	isc_buffer_clear(buffer);
1194
1195	/*
1196	 * Common header and owner name (length followed by name)
1197	 * These fields should be in a moderate length, so we assume we
1198	 * can store all of them in the initial buffer.
1199	 */
1200	isc_buffer_availableregion(buffer, &r_hdr);
1201	INSIST(r_hdr.length >= sizeof(dns_masterrawrdataset_t));
1202	isc_buffer_putuint32(buffer, totallen);		 /* XXX: leave space */
1203	isc_buffer_putuint16(buffer, rdataset->rdclass); /* 16-bit class */
1204	isc_buffer_putuint16(buffer, rdataset->type);	 /* 16-bit type */
1205	isc_buffer_putuint16(buffer, rdataset->covers);	 /* same as type */
1206	isc_buffer_putuint32(buffer, rdataset->ttl);	 /* 32-bit TTL */
1207	isc_buffer_putuint32(buffer, dns_rdataset_count(rdataset));
1208	totallen = isc_buffer_usedlength(buffer);
1209	INSIST(totallen <= sizeof(dns_masterrawrdataset_t));
1210
1211	dns_name_toregion(name, &r);
1212	INSIST(isc_buffer_availablelength(buffer) >= (sizeof(dlen) + r.length));
1213	dlen = (uint16_t)r.length;
1214	isc_buffer_putuint16(buffer, dlen);
1215	isc_buffer_copyregion(buffer, &r);
1216	totallen += sizeof(dlen) + r.length;
1217
1218	do {
1219		dns_rdata_t rdata = DNS_RDATA_INIT;
1220
1221		dns_rdataset_current(rdataset, &rdata);
1222		dns_rdata_toregion(&rdata, &r);
1223		INSIST(r.length <= 0xffffU);
1224		dlen = (uint16_t)r.length;
1225
1226		/*
1227		 * Copy the rdata into the buffer.  If the buffer is too small,
1228		 * grow it.  This should be rare, so we'll simply restart the
1229		 * entire procedure (or should we copy the old data and
1230		 * continue?).
1231		 */
1232		if (isc_buffer_availablelength(buffer) <
1233		    sizeof(dlen) + r.length)
1234		{
1235			int newlength;
1236			void *newmem;
1237
1238			newlength = buffer->length * 2;
1239			newmem = isc_mem_get(mctx, newlength);
1240			isc_mem_put(mctx, buffer->base, buffer->length);
1241			isc_buffer_init(buffer, newmem, newlength);
1242			goto restart;
1243		}
1244		isc_buffer_putuint16(buffer, dlen);
1245		isc_buffer_copyregion(buffer, &r);
1246		totallen += sizeof(dlen) + r.length;
1247
1248		result = dns_rdataset_next(rdataset);
1249	} while (result == ISC_R_SUCCESS);
1250
1251	if (result != ISC_R_NOMORE) {
1252		return (result);
1253	}
1254
1255	/*
1256	 * Fill in the total length field.
1257	 * XXX: this is a bit tricky.  Since we have already "used" the space
1258	 * for the total length in the buffer, we first remember the entire
1259	 * buffer length in the region, "rewind", and then write the value.
1260	 */
1261	isc_buffer_usedregion(buffer, &r);
1262	isc_buffer_clear(buffer);
1263	isc_buffer_putuint32(buffer, totallen);
1264	INSIST(isc_buffer_usedlength(buffer) < totallen);
1265
1266	/*
1267	 * Write the buffer contents to the raw master file.
1268	 */
1269	result = isc_stdio_write(r.base, 1, (size_t)r.length, f, NULL);
1270
1271	if (result != ISC_R_SUCCESS) {
1272		UNEXPECTED_ERROR("raw master file write failed: %s",
1273				 isc_result_totext(result));
1274		return (result);
1275	}
1276
1277	return (result);
1278}
1279
1280static isc_result_t
1281dump_rdatasets_raw(isc_mem_t *mctx, const dns_name_t *owner_name,
1282		   dns_rdatasetiter_t *rdsiter, dns_totext_ctx_t *ctx,
1283		   isc_buffer_t *buffer, FILE *f) {
1284	isc_result_t result;
1285	dns_rdataset_t rdataset;
1286	dns_fixedname_t fixed;
1287	dns_name_t *name;
1288
1289	name = dns_fixedname_initname(&fixed);
1290	dns_name_copy(owner_name, name);
1291	for (result = dns_rdatasetiter_first(rdsiter); result == ISC_R_SUCCESS;
1292	     result = dns_rdatasetiter_next(rdsiter))
1293	{
1294		dns_rdataset_init(&rdataset);
1295		dns_rdatasetiter_current(rdsiter, &rdataset);
1296
1297		dns_rdataset_getownercase(&rdataset, name);
1298
1299		if (((rdataset.attributes & DNS_RDATASETATTR_NEGATIVE) != 0) &&
1300		    (ctx->style.flags & DNS_STYLEFLAG_NCACHE) == 0)
1301		{
1302			/* Omit negative cache entries */
1303		} else {
1304			result = dump_rdataset_raw(mctx, name, &rdataset,
1305						   buffer, f);
1306		}
1307		dns_rdataset_disassociate(&rdataset);
1308		if (result != ISC_R_SUCCESS) {
1309			return (result);
1310		}
1311	}
1312
1313	if (result == ISC_R_NOMORE) {
1314		result = ISC_R_SUCCESS;
1315	}
1316
1317	return (result);
1318}
1319
1320/*
1321 * Initial size of text conversion buffer.  The buffer is used
1322 * for several purposes: converting origin names, rdatasets,
1323 * $DATE timestamps, and comment strings for $TTL directives.
1324 *
1325 * When converting rdatasets, it is dynamically resized, but
1326 * when converting origins, timestamps, etc it is not.  Therefore,
1327 * the initial size must large enough to hold the longest possible
1328 * text representation of any domain name (for $ORIGIN).
1329 */
1330static const int initial_buffer_length = 1200;
1331
1332static isc_result_t
1333dumptostream(dns_dumpctx_t *dctx);
1334
1335static void
1336dumpctx_destroy(dns_dumpctx_t *dctx) {
1337	dctx->magic = 0;
1338	isc_mutex_destroy(&dctx->lock);
1339	dns_dbiterator_destroy(&dctx->dbiter);
1340	if (dctx->version != NULL) {
1341		dns_db_closeversion(dctx->db, &dctx->version, false);
1342	}
1343	dns_db_detach(&dctx->db);
1344	if (dctx->task != NULL) {
1345		isc_task_detach(&dctx->task);
1346	}
1347	if (dctx->file != NULL) {
1348		isc_mem_free(dctx->mctx, dctx->file);
1349	}
1350	if (dctx->tmpfile != NULL) {
1351		isc_mem_free(dctx->mctx, dctx->tmpfile);
1352	}
1353	isc_mem_putanddetach(&dctx->mctx, dctx, sizeof(*dctx));
1354}
1355
1356void
1357dns_dumpctx_attach(dns_dumpctx_t *source, dns_dumpctx_t **target) {
1358	REQUIRE(DNS_DCTX_VALID(source));
1359	REQUIRE(target != NULL && *target == NULL);
1360
1361	isc_refcount_increment(&source->references);
1362
1363	*target = source;
1364}
1365
1366void
1367dns_dumpctx_detach(dns_dumpctx_t **dctxp) {
1368	dns_dumpctx_t *dctx;
1369
1370	REQUIRE(dctxp != NULL);
1371	dctx = *dctxp;
1372	*dctxp = NULL;
1373	REQUIRE(DNS_DCTX_VALID(dctx));
1374
1375	if (isc_refcount_decrement(&dctx->references) == 1) {
1376		dumpctx_destroy(dctx);
1377	}
1378}
1379
1380dns_dbversion_t *
1381dns_dumpctx_version(dns_dumpctx_t *dctx) {
1382	REQUIRE(DNS_DCTX_VALID(dctx));
1383	return (dctx->version);
1384}
1385
1386dns_db_t *
1387dns_dumpctx_db(dns_dumpctx_t *dctx) {
1388	REQUIRE(DNS_DCTX_VALID(dctx));
1389	return (dctx->db);
1390}
1391
1392void
1393dns_dumpctx_cancel(dns_dumpctx_t *dctx) {
1394	REQUIRE(DNS_DCTX_VALID(dctx));
1395
1396	atomic_store_release(&dctx->canceled, true);
1397}
1398
1399static isc_result_t
1400flushandsync(FILE *f, isc_result_t result, const char *temp) {
1401	bool logit = (result == ISC_R_SUCCESS);
1402
1403	if (result == ISC_R_SUCCESS) {
1404		result = isc_stdio_flush(f);
1405	}
1406	if (result != ISC_R_SUCCESS && logit) {
1407		if (temp != NULL) {
1408			isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL,
1409				      DNS_LOGMODULE_MASTERDUMP, ISC_LOG_ERROR,
1410				      "dumping to master file: %s: flush: %s",
1411				      temp, isc_result_totext(result));
1412		} else {
1413			isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL,
1414				      DNS_LOGMODULE_MASTERDUMP, ISC_LOG_ERROR,
1415				      "dumping to stream: flush: %s",
1416				      isc_result_totext(result));
1417		}
1418		logit = false;
1419	}
1420
1421	if (result == ISC_R_SUCCESS) {
1422		result = isc_stdio_sync(f);
1423	}
1424	if (result != ISC_R_SUCCESS && logit) {
1425		if (temp != NULL) {
1426			isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL,
1427				      DNS_LOGMODULE_MASTERDUMP, ISC_LOG_ERROR,
1428				      "dumping to master file: %s: fsync: %s",
1429				      temp, isc_result_totext(result));
1430		} else {
1431			isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL,
1432				      DNS_LOGMODULE_MASTERDUMP, ISC_LOG_ERROR,
1433				      "dumping to stream: fsync: %s",
1434				      isc_result_totext(result));
1435		}
1436	}
1437	return (result);
1438}
1439
1440static isc_result_t
1441closeandrename(FILE *f, isc_result_t result, const char *temp,
1442	       const char *file) {
1443	isc_result_t tresult;
1444	bool logit = (result == ISC_R_SUCCESS);
1445
1446	result = flushandsync(f, result, temp);
1447	if (result != ISC_R_SUCCESS) {
1448		logit = false;
1449	}
1450
1451	tresult = isc_stdio_close(f);
1452	if (result == ISC_R_SUCCESS) {
1453		result = tresult;
1454	}
1455	if (result != ISC_R_SUCCESS && logit) {
1456		isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL,
1457			      DNS_LOGMODULE_MASTERDUMP, ISC_LOG_ERROR,
1458			      "dumping master file: %s: fclose: %s", temp,
1459			      isc_result_totext(result));
1460		logit = false;
1461	}
1462	if (result == ISC_R_SUCCESS) {
1463		result = isc_file_rename(temp, file);
1464	} else {
1465		(void)isc_file_remove(temp);
1466	}
1467	if (result != ISC_R_SUCCESS && logit) {
1468		isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL,
1469			      DNS_LOGMODULE_MASTERDUMP, ISC_LOG_ERROR,
1470			      "dumping master file: rename: %s: %s", file,
1471			      isc_result_totext(result));
1472	}
1473	return (result);
1474}
1475
1476/*
1477 * This will run in a libuv threadpool thread.
1478 */
1479static void
1480master_dump_cb(void *data) {
1481	isc_result_t result = ISC_R_UNSET;
1482	dns_dumpctx_t *dctx = data;
1483	REQUIRE(DNS_DCTX_VALID(dctx));
1484
1485	if (atomic_load_acquire(&dctx->canceled)) {
1486		result = ISC_R_CANCELED;
1487	} else {
1488		result = dumptostream(dctx);
1489	}
1490
1491	if (dctx->file != NULL) {
1492		isc_result_t tresult = ISC_R_UNSET;
1493		tresult = closeandrename(dctx->f, result, dctx->tmpfile,
1494					 dctx->file);
1495		if (tresult != ISC_R_SUCCESS && result == ISC_R_SUCCESS) {
1496			result = tresult;
1497		}
1498	} else {
1499		result = flushandsync(dctx->f, result, NULL);
1500	}
1501
1502	dctx->result = result;
1503}
1504
1505/*
1506 * This will run in a network/task manager thread when the dump is complete.
1507 */
1508static void
1509master_dump_done_cb(void *data, isc_result_t result) {
1510	dns_dumpctx_t *dctx = data;
1511
1512	if (result == ISC_R_SUCCESS && dctx->result != ISC_R_SUCCESS) {
1513		result = dctx->result;
1514	}
1515
1516	(dctx->done)(dctx->done_arg, result);
1517	dns_dumpctx_detach(&dctx);
1518}
1519
1520/*
1521 * This must be run from a network/task manager thread.
1522 */
1523static void
1524setup_dump(isc_task_t *task, isc_event_t *event) {
1525	dns_dumpctx_t *dctx = NULL;
1526
1527	REQUIRE(isc_nm_tid() >= 0);
1528	REQUIRE(event != NULL);
1529
1530	dctx = event->ev_arg;
1531
1532	REQUIRE(DNS_DCTX_VALID(dctx));
1533
1534	isc_nm_work_offload(isc_task_getnetmgr(task), master_dump_cb,
1535			    master_dump_done_cb, dctx);
1536
1537	isc_event_free(&event);
1538}
1539
1540static isc_result_t
1541task_send(dns_dumpctx_t *dctx) {
1542	isc_event_t *event;
1543
1544	event = isc_event_allocate(dctx->mctx, NULL, DNS_EVENT_DUMPQUANTUM,
1545				   setup_dump, dctx, sizeof(*event));
1546	isc_task_send(dctx->task, &event);
1547	return (ISC_R_SUCCESS);
1548}
1549
1550static isc_result_t
1551dumpctx_create(isc_mem_t *mctx, dns_db_t *db, dns_dbversion_t *version,
1552	       const dns_master_style_t *style, FILE *f, dns_dumpctx_t **dctxp,
1553	       dns_masterformat_t format, dns_masterrawheader_t *header) {
1554	dns_dumpctx_t *dctx;
1555	isc_result_t result;
1556	unsigned int options;
1557
1558	dctx = isc_mem_get(mctx, sizeof(*dctx));
1559
1560	dctx->mctx = NULL;
1561	dctx->f = f;
1562	dctx->dbiter = NULL;
1563	dctx->db = NULL;
1564	dctx->version = NULL;
1565	dctx->done = NULL;
1566	dctx->done_arg = NULL;
1567	dctx->task = NULL;
1568	atomic_init(&dctx->canceled, false);
1569	dctx->file = NULL;
1570	dctx->tmpfile = NULL;
1571	dctx->format = format;
1572	if (header == NULL) {
1573		dns_master_initrawheader(&dctx->header);
1574	} else {
1575		dctx->header = *header;
1576	}
1577
1578	switch (format) {
1579	case dns_masterformat_text:
1580		dctx->dumpsets = dump_rdatasets_text;
1581		break;
1582	case dns_masterformat_raw:
1583		dctx->dumpsets = dump_rdatasets_raw;
1584		break;
1585	default:
1586		UNREACHABLE();
1587	}
1588
1589	result = totext_ctx_init(style, NULL, &dctx->tctx);
1590	if (result != ISC_R_SUCCESS) {
1591		UNEXPECTED_ERROR("could not set master file style");
1592		goto cleanup;
1593	}
1594
1595	isc_stdtime_get(&dctx->now);
1596	dns_db_attach(db, &dctx->db);
1597
1598	dctx->do_date = dns_db_iscache(dctx->db);
1599	if (dctx->do_date) {
1600		(void)dns_db_getservestalettl(dctx->db,
1601					      &dctx->tctx.serve_stale_ttl);
1602	}
1603
1604	if (dctx->format == dns_masterformat_text &&
1605	    (dctx->tctx.style.flags & DNS_STYLEFLAG_REL_OWNER) != 0)
1606	{
1607		options = DNS_DB_RELATIVENAMES;
1608	} else {
1609		options = 0;
1610	}
1611	result = dns_db_createiterator(dctx->db, options, &dctx->dbiter);
1612	if (result != ISC_R_SUCCESS) {
1613		goto cleanup;
1614	}
1615
1616	isc_mutex_init(&dctx->lock);
1617
1618	if (version != NULL) {
1619		dns_db_attachversion(dctx->db, version, &dctx->version);
1620	} else if (!dns_db_iscache(db)) {
1621		dns_db_currentversion(dctx->db, &dctx->version);
1622	}
1623	isc_mem_attach(mctx, &dctx->mctx);
1624
1625	isc_refcount_init(&dctx->references, 1);
1626	dctx->magic = DNS_DCTX_MAGIC;
1627	*dctxp = dctx;
1628	return (ISC_R_SUCCESS);
1629
1630cleanup:
1631	if (dctx->dbiter != NULL) {
1632		dns_dbiterator_destroy(&dctx->dbiter);
1633	}
1634	if (dctx->db != NULL) {
1635		dns_db_detach(&dctx->db);
1636	}
1637	isc_mem_put(mctx, dctx, sizeof(*dctx));
1638	return (result);
1639}
1640
1641static isc_result_t
1642writeheader(dns_dumpctx_t *dctx) {
1643	isc_result_t result = ISC_R_SUCCESS;
1644	isc_buffer_t buffer;
1645	char *bufmem;
1646	isc_region_t r;
1647	dns_masterrawheader_t rawheader;
1648	uint32_t rawversion, now32;
1649
1650	bufmem = isc_mem_get(dctx->mctx, initial_buffer_length);
1651
1652	isc_buffer_init(&buffer, bufmem, initial_buffer_length);
1653
1654	switch (dctx->format) {
1655	case dns_masterformat_text:
1656		/*
1657		 * If the database has cache semantics, output an
1658		 * RFC2540 $DATE directive so that the TTLs can be
1659		 * adjusted when it is reloaded.  For zones it is not
1660		 * really needed, and it would make the file
1661		 * incompatible with pre-RFC2540 software, so we omit
1662		 * it in the zone case.
1663		 */
1664		if (dctx->do_date) {
1665			fprintf(dctx->f, "; using a %u second stale ttl\n",
1666				dctx->tctx.serve_stale_ttl);
1667			result = dns_time32_totext(dctx->now, &buffer);
1668			RUNTIME_CHECK(result == ISC_R_SUCCESS);
1669			isc_buffer_usedregion(&buffer, &r);
1670			fprintf(dctx->f, "$DATE %.*s\n", (int)r.length,
1671				(char *)r.base);
1672		}
1673		break;
1674	case dns_masterformat_raw:
1675		r.base = (unsigned char *)&rawheader;
1676		r.length = sizeof(rawheader);
1677		isc_buffer_region(&buffer, &r);
1678		now32 = dctx->now;
1679		rawversion = 1;
1680		if ((dctx->header.flags & DNS_MASTERRAW_COMPAT) != 0) {
1681			rawversion = 0;
1682		}
1683
1684		isc_buffer_putuint32(&buffer, dctx->format);
1685		isc_buffer_putuint32(&buffer, rawversion);
1686		isc_buffer_putuint32(&buffer, now32);
1687
1688		if (rawversion == 1) {
1689			isc_buffer_putuint32(&buffer, dctx->header.flags);
1690			isc_buffer_putuint32(&buffer,
1691					     dctx->header.sourceserial);
1692			isc_buffer_putuint32(&buffer, dctx->header.lastxfrin);
1693		}
1694
1695		INSIST(isc_buffer_usedlength(&buffer) <= sizeof(rawheader));
1696		result = isc_stdio_write(buffer.base, 1,
1697					 isc_buffer_usedlength(&buffer),
1698					 dctx->f, NULL);
1699		if (result != ISC_R_SUCCESS) {
1700			break;
1701		}
1702
1703		break;
1704	default:
1705		UNREACHABLE();
1706	}
1707
1708	isc_mem_put(dctx->mctx, buffer.base, buffer.length);
1709	return (result);
1710}
1711
1712static isc_result_t
1713dumptostream(dns_dumpctx_t *dctx) {
1714	isc_result_t result = ISC_R_SUCCESS;
1715	isc_buffer_t buffer;
1716	char *bufmem;
1717	dns_name_t *name;
1718	dns_fixedname_t fixname;
1719	unsigned int options = DNS_DB_STALEOK;
1720
1721	if ((dctx->tctx.style.flags & DNS_STYLEFLAG_EXPIRED) != 0) {
1722		options |= DNS_DB_EXPIREDOK;
1723	}
1724
1725	bufmem = isc_mem_get(dctx->mctx, initial_buffer_length);
1726
1727	isc_buffer_init(&buffer, bufmem, initial_buffer_length);
1728
1729	name = dns_fixedname_initname(&fixname);
1730
1731	CHECK(writeheader(dctx));
1732
1733	result = dns_dbiterator_first(dctx->dbiter);
1734	if (result != ISC_R_SUCCESS && result != ISC_R_NOMORE) {
1735		goto cleanup;
1736	}
1737
1738	while (result == ISC_R_SUCCESS) {
1739		dns_rdatasetiter_t *rdsiter = NULL;
1740		dns_dbnode_t *node = NULL;
1741
1742		result = dns_dbiterator_current(dctx->dbiter, &node, name);
1743		if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) {
1744			break;
1745		}
1746		if (result == DNS_R_NEWORIGIN) {
1747			dns_name_t *origin =
1748				dns_fixedname_name(&dctx->tctx.origin_fixname);
1749			result = dns_dbiterator_origin(dctx->dbiter, origin);
1750			RUNTIME_CHECK(result == ISC_R_SUCCESS);
1751			if ((dctx->tctx.style.flags & DNS_STYLEFLAG_REL_DATA) !=
1752			    0)
1753			{
1754				dctx->tctx.origin = origin;
1755			}
1756			dctx->tctx.neworigin = origin;
1757		}
1758
1759		result = dns_dbiterator_pause(dctx->dbiter);
1760		RUNTIME_CHECK(result == ISC_R_SUCCESS);
1761
1762		result = dns_db_allrdatasets(dctx->db, node, dctx->version,
1763					     options, dctx->now, &rdsiter);
1764		if (result != ISC_R_SUCCESS) {
1765			dns_db_detachnode(dctx->db, &node);
1766			goto cleanup;
1767		}
1768		result = (dctx->dumpsets)(dctx->mctx, name, rdsiter,
1769					  &dctx->tctx, &buffer, dctx->f);
1770		dns_rdatasetiter_destroy(&rdsiter);
1771		if (result != ISC_R_SUCCESS) {
1772			dns_db_detachnode(dctx->db, &node);
1773			goto cleanup;
1774		}
1775		dns_db_detachnode(dctx->db, &node);
1776		result = dns_dbiterator_next(dctx->dbiter);
1777	}
1778
1779	if (result == ISC_R_NOMORE) {
1780		result = ISC_R_SUCCESS;
1781	}
1782cleanup:
1783	RUNTIME_CHECK(dns_dbiterator_pause(dctx->dbiter) == ISC_R_SUCCESS);
1784	isc_mem_put(dctx->mctx, buffer.base, buffer.length);
1785	return (result);
1786}
1787
1788isc_result_t
1789dns_master_dumptostreamasync(isc_mem_t *mctx, dns_db_t *db,
1790			     dns_dbversion_t *version,
1791			     const dns_master_style_t *style, FILE *f,
1792			     isc_task_t *task, dns_dumpdonefunc_t done,
1793			     void *done_arg, dns_dumpctx_t **dctxp) {
1794	dns_dumpctx_t *dctx = NULL;
1795	isc_result_t result;
1796
1797	REQUIRE(task != NULL);
1798	REQUIRE(f != NULL);
1799	REQUIRE(done != NULL);
1800
1801	result = dumpctx_create(mctx, db, version, style, f, &dctx,
1802				dns_masterformat_text, NULL);
1803	if (result != ISC_R_SUCCESS) {
1804		return (result);
1805	}
1806	isc_task_attach(task, &dctx->task);
1807	dctx->done = done;
1808	dctx->done_arg = done_arg;
1809
1810	result = task_send(dctx);
1811	if (result == ISC_R_SUCCESS) {
1812		dns_dumpctx_attach(dctx, dctxp);
1813		return (DNS_R_CONTINUE);
1814	}
1815
1816	dns_dumpctx_detach(&dctx);
1817	return (result);
1818}
1819
1820isc_result_t
1821dns_master_dumptostream(isc_mem_t *mctx, dns_db_t *db, dns_dbversion_t *version,
1822			const dns_master_style_t *style,
1823			dns_masterformat_t format,
1824			dns_masterrawheader_t *header, FILE *f) {
1825	dns_dumpctx_t *dctx = NULL;
1826	isc_result_t result;
1827
1828	result = dumpctx_create(mctx, db, version, style, f, &dctx, format,
1829				header);
1830	if (result != ISC_R_SUCCESS) {
1831		return (result);
1832	}
1833
1834	result = dumptostream(dctx);
1835	INSIST(result != DNS_R_CONTINUE);
1836	dns_dumpctx_detach(&dctx);
1837
1838	result = flushandsync(f, result, NULL);
1839	return (result);
1840}
1841
1842static isc_result_t
1843opentmp(isc_mem_t *mctx, dns_masterformat_t format, const char *file,
1844	char **tempp, FILE **fp) {
1845	FILE *f = NULL;
1846	isc_result_t result;
1847	char *tempname = NULL;
1848	int tempnamelen;
1849
1850	tempnamelen = strlen(file) + 20;
1851	tempname = isc_mem_allocate(mctx, tempnamelen);
1852
1853	result = isc_file_mktemplate(file, tempname, tempnamelen);
1854	if (result != ISC_R_SUCCESS) {
1855		goto cleanup;
1856	}
1857
1858	if (format == dns_masterformat_text) {
1859		result = isc_file_openunique(tempname, &f);
1860	} else {
1861		result = isc_file_bopenunique(tempname, &f);
1862	}
1863	if (result != ISC_R_SUCCESS) {
1864		isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL,
1865			      DNS_LOGMODULE_MASTERDUMP, ISC_LOG_ERROR,
1866			      "dumping master file: %s: open: %s", tempname,
1867			      isc_result_totext(result));
1868		goto cleanup;
1869	}
1870
1871#if defined(POSIX_FADV_DONTNEED)
1872	posix_fadvise(fileno(f), 0, 0, POSIX_FADV_DONTNEED);
1873#endif
1874
1875	*tempp = tempname;
1876	*fp = f;
1877	return (ISC_R_SUCCESS);
1878
1879cleanup:
1880	isc_mem_free(mctx, tempname);
1881	return (result);
1882}
1883
1884isc_result_t
1885dns_master_dumpasync(isc_mem_t *mctx, dns_db_t *db, dns_dbversion_t *version,
1886		     const dns_master_style_t *style, const char *filename,
1887		     isc_task_t *task, dns_dumpdonefunc_t done, void *done_arg,
1888		     dns_dumpctx_t **dctxp, dns_masterformat_t format,
1889		     dns_masterrawheader_t *header) {
1890	FILE *f = NULL;
1891	isc_result_t result;
1892	char *tempname = NULL;
1893	char *file = NULL;
1894	dns_dumpctx_t *dctx = NULL;
1895
1896	file = isc_mem_strdup(mctx, filename);
1897
1898	result = opentmp(mctx, format, filename, &tempname, &f);
1899	if (result != ISC_R_SUCCESS) {
1900		goto cleanup;
1901	}
1902
1903	result = dumpctx_create(mctx, db, version, style, f, &dctx, format,
1904				header);
1905	if (result != ISC_R_SUCCESS) {
1906		(void)isc_stdio_close(f);
1907		(void)isc_file_remove(tempname);
1908		goto cleanup;
1909	}
1910
1911	isc_task_attach(task, &dctx->task);
1912	dctx->done = done;
1913	dctx->done_arg = done_arg;
1914	dctx->file = file;
1915	file = NULL;
1916	dctx->tmpfile = tempname;
1917	tempname = NULL;
1918
1919	result = task_send(dctx);
1920	if (result == ISC_R_SUCCESS) {
1921		dns_dumpctx_attach(dctx, dctxp);
1922		return (DNS_R_CONTINUE);
1923	}
1924
1925cleanup:
1926	if (dctx != NULL) {
1927		dns_dumpctx_detach(&dctx);
1928	}
1929	if (file != NULL) {
1930		isc_mem_free(mctx, file);
1931	}
1932	if (tempname != NULL) {
1933		isc_mem_free(mctx, tempname);
1934	}
1935	return (result);
1936}
1937
1938isc_result_t
1939dns_master_dump(isc_mem_t *mctx, dns_db_t *db, dns_dbversion_t *version,
1940		const dns_master_style_t *style, const char *filename,
1941		dns_masterformat_t format, dns_masterrawheader_t *header) {
1942	FILE *f = NULL;
1943	isc_result_t result;
1944	char *tempname;
1945	dns_dumpctx_t *dctx = NULL;
1946
1947	result = opentmp(mctx, format, filename, &tempname, &f);
1948	if (result != ISC_R_SUCCESS) {
1949		return (result);
1950	}
1951
1952	result = dumpctx_create(mctx, db, version, style, f, &dctx, format,
1953				header);
1954	if (result != ISC_R_SUCCESS) {
1955		goto cleanup;
1956	}
1957
1958	result = dumptostream(dctx);
1959	INSIST(result != DNS_R_CONTINUE);
1960	dns_dumpctx_detach(&dctx);
1961
1962	result = closeandrename(f, result, tempname, filename);
1963
1964cleanup:
1965	isc_mem_free(mctx, tempname);
1966	return (result);
1967}
1968
1969/*
1970 * Dump a database node into a master file.
1971 * XXX: this function assumes the text format.
1972 */
1973isc_result_t
1974dns_master_dumpnodetostream(isc_mem_t *mctx, dns_db_t *db,
1975			    dns_dbversion_t *version, dns_dbnode_t *node,
1976			    const dns_name_t *name,
1977			    const dns_master_style_t *style, FILE *f) {
1978	isc_result_t result;
1979	isc_buffer_t buffer;
1980	char *bufmem;
1981	isc_stdtime_t now;
1982	dns_totext_ctx_t ctx;
1983	dns_rdatasetiter_t *rdsiter = NULL;
1984	unsigned int options = DNS_DB_STALEOK;
1985
1986	if ((style->flags & DNS_STYLEFLAG_EXPIRED) != 0) {
1987		options |= DNS_DB_EXPIREDOK;
1988	}
1989
1990	result = totext_ctx_init(style, NULL, &ctx);
1991	if (result != ISC_R_SUCCESS) {
1992		UNEXPECTED_ERROR("could not set master file style");
1993		return (ISC_R_UNEXPECTED);
1994	}
1995
1996	isc_stdtime_get(&now);
1997
1998	bufmem = isc_mem_get(mctx, initial_buffer_length);
1999
2000	isc_buffer_init(&buffer, bufmem, initial_buffer_length);
2001
2002	result = dns_db_allrdatasets(db, node, version, options, now, &rdsiter);
2003	if (result != ISC_R_SUCCESS) {
2004		goto failure;
2005	}
2006	result = dump_rdatasets_text(mctx, name, rdsiter, &ctx, &buffer, f);
2007	if (result != ISC_R_SUCCESS) {
2008		goto failure;
2009	}
2010	dns_rdatasetiter_destroy(&rdsiter);
2011
2012	result = ISC_R_SUCCESS;
2013
2014failure:
2015	isc_mem_put(mctx, buffer.base, buffer.length);
2016	return (result);
2017}
2018
2019isc_result_t
2020dns_master_dumpnode(isc_mem_t *mctx, dns_db_t *db, dns_dbversion_t *version,
2021		    dns_dbnode_t *node, const dns_name_t *name,
2022		    const dns_master_style_t *style, const char *filename) {
2023	FILE *f = NULL;
2024	isc_result_t result;
2025
2026	result = isc_stdio_open(filename, "w", &f);
2027	if (result != ISC_R_SUCCESS) {
2028		isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL,
2029			      DNS_LOGMODULE_MASTERDUMP, ISC_LOG_ERROR,
2030			      "dumping node to file: %s: open: %s", filename,
2031			      isc_result_totext(result));
2032		return (ISC_R_UNEXPECTED);
2033	}
2034
2035	result = dns_master_dumpnodetostream(mctx, db, version, node, name,
2036					     style, f);
2037	if (result != ISC_R_SUCCESS) {
2038		isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL,
2039			      DNS_LOGMODULE_MASTERDUMP, ISC_LOG_ERROR,
2040			      "dumping master file: %s: dump: %s", filename,
2041			      isc_result_totext(result));
2042		(void)isc_stdio_close(f);
2043		return (ISC_R_UNEXPECTED);
2044	}
2045
2046	result = isc_stdio_close(f);
2047	if (result != ISC_R_SUCCESS) {
2048		isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL,
2049			      DNS_LOGMODULE_MASTERDUMP, ISC_LOG_ERROR,
2050			      "dumping master file: %s: close: %s", filename,
2051			      isc_result_totext(result));
2052		return (ISC_R_UNEXPECTED);
2053	}
2054
2055	return (result);
2056}
2057
2058dns_masterstyle_flags_t
2059dns_master_styleflags(const dns_master_style_t *style) {
2060	REQUIRE(style != NULL);
2061	return (style->flags);
2062}
2063
2064isc_result_t
2065dns_master_stylecreate(dns_master_style_t **stylep,
2066		       dns_masterstyle_flags_t flags, unsigned int ttl_column,
2067		       unsigned int class_column, unsigned int type_column,
2068		       unsigned int rdata_column, unsigned int line_length,
2069		       unsigned int tab_width, unsigned int split_width,
2070		       isc_mem_t *mctx) {
2071	dns_master_style_t *style;
2072
2073	REQUIRE(stylep != NULL && *stylep == NULL);
2074	style = isc_mem_get(mctx, sizeof(*style));
2075
2076	style->flags = flags;
2077	style->ttl_column = ttl_column;
2078	style->class_column = class_column;
2079	style->type_column = type_column;
2080	style->rdata_column = rdata_column;
2081	style->line_length = line_length;
2082	style->tab_width = tab_width;
2083	style->split_width = split_width;
2084	*stylep = style;
2085	return (ISC_R_SUCCESS);
2086}
2087
2088void
2089dns_master_styledestroy(dns_master_style_t **stylep, isc_mem_t *mctx) {
2090	dns_master_style_t *style;
2091
2092	REQUIRE(stylep != NULL && *stylep != NULL);
2093	style = *stylep;
2094	*stylep = NULL;
2095	isc_mem_put(mctx, style, sizeof(*style));
2096}
2097