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