"Fossies" - the Fresh Open Source Software Archive

Member "knot-2.9.2/src/knot/updates/zone-update.c" (12 Dec 2019, 23081 Bytes) of package /linux/misc/dns/knot-2.9.2.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "zone-update.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 2.9.1_vs_2.9.2.

    1 /*  Copyright (C) 2019 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
    2 
    3     This program is free software: you can redistribute it and/or modify
    4     it under the terms of the GNU General Public License as published by
    5     the Free Software Foundation, either version 3 of the License, or
    6     (at your option) any later version.
    7 
    8     This program is distributed in the hope that it will be useful,
    9     but WITHOUT ANY WARRANTY; without even the implied warranty of
   10     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   11     GNU General Public License for more details.
   12 
   13     You should have received a copy of the GNU General Public License
   14     along with this program.  If not, see <https://www.gnu.org/licenses/>.
   15  */
   16 
   17 #include "knot/common/log.h"
   18 #include "knot/dnssec/zone-events.h"
   19 #include "knot/updates/zone-update.h"
   20 #include "knot/zone/adjust.h"
   21 #include "knot/zone/serial.h"
   22 #include "knot/zone/zone-diff.h"
   23 #include "contrib/trim.h"
   24 #include "contrib/ucw/lists.h"
   25 
   26 #include <urcu.h>
   27 
   28 // Call mem_trim() whenever accumuled size of updated zones reaches this size.
   29 #define UPDATE_MEMTRIM_AT (10 * 1024 * 1024)
   30 
   31 static int init_incremental(zone_update_t *update, zone_t *zone, zone_contents_t *old_contents)
   32 {
   33     if (old_contents == NULL) {
   34         return KNOT_EINVAL;
   35     }
   36 
   37     int ret = changeset_init(&update->change, zone->name);
   38     if (ret != KNOT_EOK) {
   39         return ret;
   40     }
   41 
   42     if (update->flags & UPDATE_HYBRID) {
   43         update->new_cont = old_contents;
   44     } else {
   45         ret = apply_prepare_zone_copy(old_contents, &update->new_cont);
   46         if (ret != KNOT_EOK) {
   47             changeset_clear(&update->change);
   48             return ret;
   49         }
   50     }
   51 
   52     uint32_t apply_flags = (update->flags & UPDATE_STRICT) ? APPLY_STRICT : 0;
   53     apply_flags |= (update->flags & UPDATE_HYBRID) ? APPLY_UNIFY_FULL : 0;
   54     ret = apply_init_ctx(update->a_ctx, update->new_cont, apply_flags);
   55     if (ret != KNOT_EOK) {
   56         changeset_clear(&update->change);
   57         return ret;
   58     }
   59 
   60     /* Copy base SOA RR. */
   61     update->change.soa_from =
   62         node_create_rrset(old_contents->apex, KNOT_RRTYPE_SOA);
   63     if (update->change.soa_from == NULL) {
   64         zone_contents_free(update->new_cont);
   65         changeset_clear(&update->change);
   66         return KNOT_ENOMEM;
   67     }
   68 
   69     return KNOT_EOK;
   70 }
   71 
   72 static int init_full(zone_update_t *update, zone_t *zone)
   73 {
   74     update->new_cont = zone_contents_new(zone->name, true);
   75     if (update->new_cont == NULL) {
   76         return KNOT_ENOMEM;
   77     }
   78 
   79     int ret = apply_init_ctx(update->a_ctx, update->new_cont, APPLY_UNIFY_FULL);
   80     if (ret != KNOT_EOK) {
   81         zone_contents_free(update->new_cont);
   82         return ret;
   83     }
   84 
   85     return KNOT_EOK;
   86 }
   87 
   88 static int replace_soa(zone_contents_t *contents, const knot_rrset_t *rr)
   89 {
   90     /* SOA possible only within apex. */
   91     if (!knot_dname_is_equal(rr->owner, contents->apex->owner)) {
   92         return KNOT_EDENIED;
   93     }
   94 
   95     knot_rrset_t old_soa = node_rrset(contents->apex, KNOT_RRTYPE_SOA);
   96     zone_node_t *n = contents->apex;
   97     int ret = zone_contents_remove_rr(contents, &old_soa, &n);
   98     if (ret != KNOT_EOK && ret != KNOT_EINVAL) {
   99         return ret;
  100     }
  101 
  102     ret = zone_contents_add_rr(contents, rr, &n);
  103     if (ret == KNOT_ETTL) {
  104         return KNOT_EOK;
  105     }
  106 
  107     return ret;
  108 }
  109 
  110 static int init_base(zone_update_t *update, zone_t *zone, zone_contents_t *old_contents,
  111                      zone_update_flags_t flags)
  112 {
  113     if (update == NULL || zone == NULL) {
  114         return KNOT_EINVAL;
  115     }
  116 
  117     memset(update, 0, sizeof(*update));
  118     update->zone = zone;
  119     update->flags = flags;
  120 
  121     update->a_ctx = calloc(1, sizeof(*update->a_ctx));
  122     if (update->a_ctx == NULL) {
  123         return KNOT_ENOMEM;
  124     }
  125 
  126     if (zone->control_update != NULL && zone->control_update != update) {
  127         log_zone_warning(zone->name, "blocked zone update due to open control transaction");
  128     }
  129 
  130     knot_sem_wait(&zone->cow_lock);
  131     update->a_ctx->cow_mutex = &zone->cow_lock;
  132 
  133     if (old_contents == NULL) {
  134         old_contents = zone->contents; // don't obtain this pointer before any other zone_update ceased to exist!
  135     }
  136 
  137     int ret = KNOT_EINVAL;
  138     if (flags & (UPDATE_INCREMENTAL | UPDATE_HYBRID)) {
  139         ret = init_incremental(update, zone, old_contents);
  140     } else if (flags & UPDATE_FULL) {
  141         ret = init_full(update, zone);
  142     }
  143     if (ret != KNOT_EOK) {
  144         knot_sem_post(&zone->cow_lock);
  145         free(update->a_ctx);
  146     }
  147 
  148     return ret;
  149 }
  150 
  151 /* ------------------------------- API -------------------------------------- */
  152 
  153 int zone_update_init(zone_update_t *update, zone_t *zone, zone_update_flags_t flags)
  154 {
  155     return init_base(update, zone, NULL, flags);
  156 }
  157 
  158 int zone_update_from_differences(zone_update_t *update, zone_t *zone, zone_contents_t *old_cont,
  159                  zone_contents_t *new_cont, zone_update_flags_t flags, bool ignore_dnssec)
  160 {
  161     if (update == NULL || zone == NULL || new_cont == NULL ||
  162         !(flags & (UPDATE_INCREMENTAL | UPDATE_HYBRID)) || (flags & UPDATE_FULL)) {
  163         return KNOT_EINVAL;
  164     }
  165 
  166     changeset_t diff;
  167     int ret = changeset_init(&diff, zone->name);
  168     if (ret != KNOT_EOK) {
  169         return ret;
  170     }
  171 
  172     ret = init_base(update, zone, old_cont, flags);
  173     if (ret != KNOT_EOK) {
  174         changeset_clear(&diff);
  175         return ret;
  176     }
  177 
  178     if (old_cont == NULL) {
  179         old_cont = zone->contents;
  180     }
  181 
  182     ret = zone_contents_diff(old_cont, new_cont, &diff, ignore_dnssec);
  183     if (ret != KNOT_EOK && ret != KNOT_ENODIFF && ret != KNOT_ESEMCHECK) {
  184         changeset_clear(&diff);
  185         zone_update_clear(update);
  186         return ret;
  187     }
  188 
  189     // True if nonempty changes were made but the serial
  190     // remained the same and has to be incremented.
  191     bool diff_semcheck = (ret == KNOT_ESEMCHECK);
  192 
  193     ret = zone_update_apply_changeset(update, &diff);
  194     changeset_clear(&diff);
  195     if (ret != KNOT_EOK) {
  196         zone_update_clear(update);
  197         return ret;
  198     }
  199 
  200     if (diff_semcheck) {
  201         ret = zone_update_increment_soa(update, conf());
  202         if (ret != KNOT_EOK) {
  203             zone_update_clear(update);
  204             return ret;
  205         }
  206         log_zone_info(zone->name, "automatic SOA serial increment");
  207     }
  208 
  209     update->init_cont = new_cont;
  210     return KNOT_EOK;
  211 }
  212 
  213 int zone_update_from_contents(zone_update_t *update, zone_t *zone_without_contents,
  214                               zone_contents_t *new_cont, zone_update_flags_t flags)
  215 {
  216     if (update == NULL || zone_without_contents == NULL || new_cont == NULL) {
  217         return KNOT_EINVAL;
  218     }
  219 
  220     memset(update, 0, sizeof(*update));
  221     update->zone = zone_without_contents;
  222     update->flags = flags;
  223     update->new_cont = new_cont;
  224 
  225     update->a_ctx = calloc(1, sizeof(*update->a_ctx));
  226     if (update->a_ctx == NULL) {
  227         return KNOT_ENOMEM;
  228     }
  229 
  230     if (zone_without_contents->control_update != NULL) {
  231         log_zone_warning(zone_without_contents->name,
  232                          "blocked zone update due to open control transaction");
  233     }
  234 
  235     knot_sem_wait(&update->zone->cow_lock);
  236     update->a_ctx->cow_mutex = &update->zone->cow_lock;
  237 
  238     if (flags & (UPDATE_INCREMENTAL | UPDATE_HYBRID)) {
  239         int ret = changeset_init(&update->change, zone_without_contents->name);
  240         if (ret != KNOT_EOK) {
  241             free(update->a_ctx);
  242             knot_sem_post(&update->zone->cow_lock);
  243             return ret;
  244         }
  245 
  246         update->change.soa_from = node_create_rrset(new_cont->apex, KNOT_RRTYPE_SOA);
  247         if (update->change.soa_from == NULL) {
  248             changeset_clear(&update->change);
  249             free(update->a_ctx);
  250             knot_sem_post(&update->zone->cow_lock);
  251             return KNOT_ENOMEM;
  252         }
  253     }
  254 
  255     uint32_t apply_flags = (update->flags & UPDATE_STRICT) ? APPLY_STRICT : 0;
  256     int ret = apply_init_ctx(update->a_ctx, update->new_cont, apply_flags | APPLY_UNIFY_FULL);
  257     if (ret != KNOT_EOK) {
  258         changeset_clear(&update->change);
  259         free(update->a_ctx);
  260         knot_sem_post(&update->zone->cow_lock);
  261         return ret;
  262     }
  263 
  264     return KNOT_EOK;
  265 }
  266 
  267 int zone_update_start_extra(zone_update_t *update)
  268 {
  269     assert((update->flags & (UPDATE_INCREMENTAL | UPDATE_HYBRID)));
  270 
  271     int ret = changeset_init(&update->extra_ch, update->new_cont->apex->owner);
  272     if (ret != KNOT_EOK) {
  273         return ret;
  274     }
  275 
  276     if (update->init_cont != NULL) {
  277         ret = zone_update_increment_soa(update, conf());
  278         if (ret != KNOT_EOK) {
  279             return ret;
  280         }
  281 
  282         ret = zone_contents_diff(update->init_cont, update->new_cont, &update->extra_ch, false);
  283         if (ret != KNOT_EOK) {
  284             return ret;
  285         }
  286     } else {
  287         update->extra_ch.soa_from = node_create_rrset(update->new_cont->apex, KNOT_RRTYPE_SOA);
  288         if (update->extra_ch.soa_from == NULL) {
  289             return KNOT_ENOMEM;
  290         }
  291 
  292         ret = zone_update_increment_soa(update, conf());
  293         if (ret != KNOT_EOK) {
  294             return ret;
  295         }
  296 
  297         update->extra_ch.soa_to = node_create_rrset(update->new_cont->apex, KNOT_RRTYPE_SOA);
  298         if (update->extra_ch.soa_to == NULL) {
  299             return KNOT_ENOMEM;
  300         }
  301     }
  302 
  303     update->flags |= UPDATE_EXTRA_CHSET;
  304     return KNOT_EOK;
  305 }
  306 
  307 const zone_node_t *zone_update_get_node(zone_update_t *update, const knot_dname_t *dname)
  308 {
  309     if (update == NULL || dname == NULL) {
  310         return NULL;
  311     }
  312 
  313     return zone_contents_node_or_nsec3(update->new_cont, dname);
  314 }
  315 
  316 uint32_t zone_update_current_serial(zone_update_t *update)
  317 {
  318     const zone_node_t *apex = update->new_cont->apex;
  319     if (apex != NULL) {
  320         return knot_soa_serial(node_rdataset(apex, KNOT_RRTYPE_SOA)->rdata);
  321     } else {
  322         return 0;
  323     }
  324 }
  325 
  326 bool zone_update_changed_nsec3param(const zone_update_t *update)
  327 {
  328     if (update->zone->contents == NULL) {
  329         return true;
  330     }
  331 
  332     dnssec_nsec3_params_t *orig = &update->zone->contents->nsec3_params;
  333     dnssec_nsec3_params_t *upd = &update->new_cont->nsec3_params;
  334     return !dnssec_nsec3_params_match(orig, upd);
  335 }
  336 
  337 const knot_rdataset_t *zone_update_from(zone_update_t *update)
  338 {
  339     if (update == NULL) {
  340         return NULL;
  341     }
  342 
  343     if (update->flags & (UPDATE_INCREMENTAL | UPDATE_HYBRID)) {
  344         const zone_node_t *apex = update->zone->contents->apex;
  345         return node_rdataset(apex, KNOT_RRTYPE_SOA);
  346     }
  347 
  348     return NULL;
  349 }
  350 
  351 const knot_rdataset_t *zone_update_to(zone_update_t *update)
  352 {
  353     if (update == NULL) {
  354         return NULL;
  355     }
  356 
  357     if (update->flags & UPDATE_FULL) {
  358         const zone_node_t *apex = update->new_cont->apex;
  359         return node_rdataset(apex, KNOT_RRTYPE_SOA);
  360     } else {
  361         if (update->change.soa_to == NULL) {
  362             return NULL;
  363         }
  364         return &update->change.soa_to->rrs;
  365     }
  366 
  367     return NULL;
  368 }
  369 
  370 void zone_update_clear(zone_update_t *update)
  371 {
  372     if (update == NULL || update->zone == NULL) {
  373         return;
  374     }
  375 
  376     if (update->flags & (UPDATE_INCREMENTAL | UPDATE_HYBRID)) {
  377         changeset_clear(&update->change);
  378         changeset_clear(&update->extra_ch);
  379     }
  380 
  381     zone_contents_deep_free(update->init_cont);
  382 
  383     if (update->flags & (UPDATE_FULL | UPDATE_HYBRID)) {
  384         apply_cleanup(update->a_ctx);
  385         zone_contents_deep_free(update->new_cont);
  386     } else {
  387         apply_rollback(update->a_ctx);
  388     }
  389 
  390     free(update->a_ctx);
  391     memset(update, 0, sizeof(*update));
  392 }
  393 
  394 static int solve_add_different_ttl(zone_update_t *update, const knot_rrset_t *add)
  395 {
  396     if (add->type == KNOT_RRTYPE_RRSIG || add->type == KNOT_RRTYPE_SOA) {
  397         return KNOT_EOK;
  398     }
  399 
  400     const zone_node_t *exist_node = zone_contents_find_node(update->new_cont, add->owner);
  401     const knot_rrset_t exist_rr = node_rrset(exist_node, add->type);
  402     if (knot_rrset_empty(&exist_rr) || exist_rr.ttl == add->ttl) {
  403         return KNOT_EOK;
  404     }
  405 
  406     knot_dname_txt_storage_t buff;
  407     char *owner = knot_dname_to_str(buff, add->owner, sizeof(buff));
  408     if (owner == NULL) {
  409         owner = "";
  410     }
  411     char type[16] = "";
  412     knot_rrtype_to_string(add->type, type, sizeof(type));
  413     log_zone_notice(update->zone->name, "TTL mismatch, owner %s, type %s, "
  414                     "TTL set to %u", owner, type, add->ttl);
  415 
  416     knot_rrset_t *exist_copy = knot_rrset_copy(&exist_rr, NULL);
  417     if (exist_copy == NULL) {
  418         return KNOT_ENOMEM;
  419     }
  420     int ret = zone_update_remove(update, exist_copy);
  421     if (ret == KNOT_EOK) {
  422         exist_copy->ttl = add->ttl;
  423         ret = zone_update_add(update, exist_copy);
  424     }
  425     knot_rrset_free(exist_copy, NULL);
  426     return ret;
  427 }
  428 
  429 int zone_update_add(zone_update_t *update, const knot_rrset_t *rrset)
  430 {
  431     if (update == NULL || rrset == NULL) {
  432         return KNOT_EINVAL;
  433     }
  434     if (knot_rrset_empty(rrset)) {
  435         return KNOT_EOK;
  436     }
  437 
  438     if (update->flags & (UPDATE_INCREMENTAL | UPDATE_HYBRID)) {
  439         int ret = solve_add_different_ttl(update, rrset);
  440         if (ret == KNOT_EOK) {
  441             ret = changeset_add_addition(&update->change, rrset, CHANGESET_CHECK);
  442         }
  443         if (ret == KNOT_EOK && (update->flags & UPDATE_EXTRA_CHSET)) {
  444             ret = changeset_add_addition(&update->extra_ch, rrset, CHANGESET_CHECK);
  445         }
  446         if (ret != KNOT_EOK) {
  447             return ret;
  448         }
  449     }
  450 
  451     if (update->flags & UPDATE_INCREMENTAL) {
  452         if (rrset->type == KNOT_RRTYPE_SOA) {
  453             // replace previous SOA
  454             int ret = apply_replace_soa(update->a_ctx, rrset);
  455             if (ret != KNOT_EOK) {
  456                 changeset_remove_addition(&update->change, rrset);
  457             }
  458             return ret;
  459         }
  460 
  461         int ret = apply_add_rr(update->a_ctx, rrset);
  462         if (ret != KNOT_EOK) {
  463             changeset_remove_addition(&update->change, rrset);
  464             return ret;
  465         }
  466 
  467         return KNOT_EOK;
  468     } else if (update->flags & (UPDATE_FULL | UPDATE_HYBRID)) {
  469         if (rrset->type == KNOT_RRTYPE_SOA) {
  470             /* replace previous SOA */
  471             return replace_soa(update->new_cont, rrset);
  472         }
  473 
  474         zone_node_t *n = NULL;
  475         int ret = zone_contents_add_rr(update->new_cont, rrset, &n);
  476         if (ret == KNOT_ETTL) {
  477             knot_dname_txt_storage_t buff;
  478             char *owner = knot_dname_to_str(buff, rrset->owner, sizeof(buff));
  479             if (owner == NULL) {
  480                 owner = "";
  481             }
  482             char type[16] = "";
  483             knot_rrtype_to_string(rrset->type, type, sizeof(type));
  484             log_zone_notice(update->new_cont->apex->owner,
  485                             "TTL mismatch, owner %s, type %s, "
  486                             "TTL set to %u", owner, type, rrset->ttl);
  487             return KNOT_EOK;
  488         }
  489 
  490         return ret;
  491     } else {
  492         return KNOT_EINVAL;
  493     }
  494 }
  495 
  496 int zone_update_remove(zone_update_t *update, const knot_rrset_t *rrset)
  497 {
  498     if (update == NULL || rrset == NULL) {
  499         return KNOT_EINVAL;
  500     }
  501 
  502     if ((update->flags & (UPDATE_INCREMENTAL | UPDATE_HYBRID)) && rrset->type != KNOT_RRTYPE_SOA) {
  503         int ret = changeset_add_removal(&update->change, rrset, CHANGESET_CHECK);
  504         if (ret == KNOT_EOK && (update->flags & UPDATE_EXTRA_CHSET)) {
  505             ret = changeset_add_removal(&update->extra_ch, rrset, CHANGESET_CHECK);
  506         }
  507         if (ret != KNOT_EOK) {
  508             return ret;
  509         }
  510     }
  511 
  512     if (update->flags & UPDATE_INCREMENTAL) {
  513         if (rrset->type == KNOT_RRTYPE_SOA) {
  514             /* SOA is replaced with addition */
  515             return KNOT_EOK;
  516         }
  517 
  518         int ret = apply_remove_rr(update->a_ctx, rrset);
  519         if (ret != KNOT_EOK) {
  520             changeset_remove_removal(&update->change, rrset);
  521             return ret;
  522         }
  523 
  524         return KNOT_EOK;
  525     } else if (update->flags & (UPDATE_FULL | UPDATE_HYBRID)) {
  526         zone_node_t *n = NULL;
  527         return zone_contents_remove_rr(update->new_cont, rrset, &n);
  528     } else {
  529         return KNOT_EINVAL;
  530     }
  531 }
  532 
  533 int zone_update_remove_rrset(zone_update_t *update, knot_dname_t *owner, uint16_t type)
  534 {
  535     if (update == NULL || owner == NULL) {
  536         return KNOT_EINVAL;
  537     }
  538 
  539     const zone_node_t *node = zone_contents_node_or_nsec3(update->new_cont, owner);
  540     if (node == NULL) {
  541         return KNOT_ENONODE;
  542     }
  543 
  544     knot_rrset_t rrset = node_rrset(node, type);
  545     if (rrset.owner == NULL) {
  546         return KNOT_ENOENT;
  547     }
  548 
  549     return zone_update_remove(update, &rrset);
  550 }
  551 
  552 int zone_update_remove_node(zone_update_t *update, const knot_dname_t *owner)
  553 {
  554     if (update == NULL || owner == NULL) {
  555         return KNOT_EINVAL;
  556     }
  557 
  558     const zone_node_t *node = zone_contents_node_or_nsec3(update->new_cont, owner);
  559     if (node == NULL) {
  560         return KNOT_ENONODE;
  561     }
  562 
  563     size_t rrset_count = node->rrset_count;
  564     for (int i = 0; i < rrset_count; ++i) {
  565         knot_rrset_t rrset = node_rrset_at(node, rrset_count - 1 - i);
  566         int ret = zone_update_remove(update, &rrset);
  567         if (ret != KNOT_EOK) {
  568             return ret;
  569         }
  570     }
  571 
  572     return KNOT_EOK;
  573 }
  574 
  575 static int update_chset_step(const knot_rrset_t *rrset, bool addition, void *ctx)
  576 {
  577     zone_update_t *update = ctx;
  578     if (addition) {
  579         return zone_update_add(update, rrset);
  580     } else {
  581         return zone_update_remove(update, rrset);
  582     }
  583 }
  584 
  585 int zone_update_apply_changeset(zone_update_t *update, const changeset_t *changes)
  586 {
  587     return changeset_walk(changes, update_chset_step, update);
  588 }
  589 
  590 int zone_update_apply_changeset_reverse(zone_update_t *update, const changeset_t *changes)
  591 {
  592     changeset_t reverse;
  593     reverse.remove = changes->add;
  594     reverse.add = changes->remove;
  595     reverse.soa_from = changes->soa_to;
  596     reverse.soa_to = changes->soa_from;
  597     return zone_update_apply_changeset(update, &reverse);
  598 }
  599 
  600 static int set_new_soa(zone_update_t *update, unsigned serial_policy)
  601 {
  602     assert(update);
  603 
  604     knot_rrset_t *soa_cpy = node_create_rrset(update->new_cont->apex,
  605                                               KNOT_RRTYPE_SOA);
  606     if (soa_cpy == NULL) {
  607         return KNOT_ENOMEM;
  608     }
  609 
  610     int ret = zone_update_remove(update, soa_cpy);
  611     if (ret != KNOT_EOK) {
  612         knot_rrset_free(soa_cpy, NULL);
  613         return ret;
  614     }
  615 
  616     uint32_t old_serial = knot_soa_serial(soa_cpy->rrs.rdata);
  617     uint32_t new_serial = serial_next(old_serial, serial_policy);
  618     if (serial_compare(old_serial, new_serial) != SERIAL_LOWER) {
  619         log_zone_warning(update->zone->name, "updated SOA serial is lower "
  620                          "than current, serial %u -> %u",
  621                          old_serial, new_serial);
  622         ret = KNOT_ESOAINVAL;
  623     } else {
  624         knot_soa_serial_set(soa_cpy->rrs.rdata, new_serial);
  625 
  626         ret = zone_update_add(update, soa_cpy);
  627     }
  628     knot_rrset_free(soa_cpy, NULL);
  629 
  630     return ret;
  631 }
  632 
  633 int zone_update_increment_soa(zone_update_t *update, conf_t *conf)
  634 {
  635     if (update == NULL || conf == NULL) {
  636         return KNOT_EINVAL;
  637     }
  638 
  639     conf_val_t val = conf_zone_get(conf, C_SERIAL_POLICY, update->zone->name);
  640     return set_new_soa(update, conf_opt(&val));
  641 }
  642 
  643 static int commit_incremental(conf_t *conf, zone_update_t *update)
  644 {
  645     assert(update);
  646 
  647     int ret = KNOT_EOK;
  648     if (zone_update_to(update) == NULL && !changeset_empty(&update->change)) {
  649         /* No SOA in the update, create one according to the current policy */
  650         ret = zone_update_increment_soa(update, conf);
  651         if (ret != KNOT_EOK) {
  652             return ret;
  653         }
  654     }
  655 
  656     /* Write changes to journal if all went well. */
  657     conf_val_t val = conf_zone_get(conf, C_JOURNAL_CONTENT, update->zone->name);
  658     if (conf_opt(&val) != JOURNAL_CONTENT_NONE && !changeset_empty(&update->change)) {
  659         ret = zone_change_store(conf, update->zone, &update->change,
  660                                 (update->flags & UPDATE_EXTRA_CHSET) ? &update->extra_ch : NULL);
  661         if (ret != KNOT_EOK) {
  662             return ret;
  663         }
  664     }
  665 
  666     return KNOT_EOK;
  667 }
  668 
  669 static int commit_full(conf_t *conf, zone_update_t *update)
  670 {
  671     assert(update);
  672 
  673     /* Check if we have SOA. We might consider adding full semantic check here.
  674      * But if we wanted full sem-check I'd consider being it controlled by a flag
  675      * - to enable/disable it on demand. */
  676     if (!node_rrtype_exists(update->new_cont->apex, KNOT_RRTYPE_SOA)) {
  677         return KNOT_ESEMCHECK;
  678     }
  679 
  680     /* Store new zone contents in journal. */
  681     conf_val_t val = conf_zone_get(conf, C_JOURNAL_CONTENT, update->zone->name);
  682     unsigned content = conf_opt(&val);
  683     if (content == JOURNAL_CONTENT_ALL) {
  684         return zone_in_journal_store(conf, update->zone, update->new_cont);
  685     } else if (content != JOURNAL_CONTENT_NONE) { // zone_in_journal_store does this automatically
  686         return zone_changes_clear(conf, update->zone);
  687     }
  688 
  689     return KNOT_EOK;
  690 }
  691 
  692 typedef struct {
  693     pthread_mutex_t lock;
  694     size_t counter;
  695 } counter_reach_t;
  696 
  697 static bool counter_reach(counter_reach_t *counter, size_t increment, size_t limit)
  698 {
  699     bool reach = false;
  700     pthread_mutex_lock(&counter->lock);
  701     counter->counter += increment;
  702     if (counter->counter >= limit) {
  703         counter->counter = 0;
  704         reach = true;
  705     }
  706     pthread_mutex_unlock(&counter->lock);
  707     return reach;
  708 }
  709 
  710 /*! \brief Struct for what needs to be cleared after RCU.
  711  *
  712  * This can't be zone_update_t structure as this might be already freed at that time.
  713  */
  714 typedef struct {
  715     struct rcu_head rcuhead;
  716 
  717     zone_contents_t *free_contents;
  718     void (*free_method)(zone_contents_t *);
  719 
  720     apply_ctx_t *cleanup_apply;
  721 
  722     size_t new_cont_size;
  723 } update_clear_ctx_t;
  724 
  725 static void update_clear(struct rcu_head *param)
  726 {
  727     static counter_reach_t counter = { PTHREAD_MUTEX_INITIALIZER, 0 };
  728 
  729     update_clear_ctx_t *ctx = (update_clear_ctx_t *)param;
  730 
  731     ctx->free_method(ctx->free_contents);
  732     apply_cleanup(ctx->cleanup_apply);
  733     free(ctx->cleanup_apply);
  734 
  735     if (counter_reach(&counter, ctx->new_cont_size, UPDATE_MEMTRIM_AT)) {
  736         mem_trim();
  737     }
  738 
  739     free(ctx);
  740 }
  741 
  742 int zone_update_commit(conf_t *conf, zone_update_t *update)
  743 {
  744     if (conf == NULL || update == NULL) {
  745         return KNOT_EINVAL;
  746     }
  747 
  748     int ret = KNOT_EOK;
  749 
  750     if (update->flags & UPDATE_INCREMENTAL) {
  751         if (changeset_empty(&update->change) &&
  752             update->zone->contents != NULL) {
  753             changeset_clear(&update->change);
  754             changeset_clear(&update->extra_ch);
  755             zone_update_clear(update);
  756             return KNOT_EOK;
  757         }
  758         ret = commit_incremental(conf, update);
  759     } else if ((update->flags & UPDATE_HYBRID)) {
  760         ret = commit_incremental(conf, update);
  761     } else {
  762         ret = commit_full(conf, update);
  763     }
  764     if (ret != KNOT_EOK) {
  765         return ret;
  766     }
  767 
  768     if ((update->flags & (UPDATE_HYBRID | UPDATE_FULL))) {
  769         ret = zone_adjust_full(update->new_cont);
  770     } else {
  771         ret = zone_adjust_incremental_update(update);
  772     }
  773     if (ret != KNOT_EOK) {
  774         return ret;
  775     }
  776 
  777     /* Check the zone size. */
  778     conf_val_t val = conf_zone_get(conf, C_ZONE_MAX_SIZE, update->zone->name);
  779     if (val.code != KNOT_EOK) {
  780         val = conf_zone_get(conf, C_MAX_ZONE_SIZE, update->zone->name);
  781     }
  782     size_t size_limit = conf_int(&val);
  783 
  784     if (update->new_cont->size > size_limit) {
  785         /* Recoverable error. */
  786         return KNOT_EZONESIZE;
  787     }
  788 
  789     /* Check if the zone was re-signed upon zone load to ensure proper flush
  790      * even if the SOA serial wasn't incremented by re-signing. */
  791     val = conf_zone_get(conf, C_DNSSEC_SIGNING, update->zone->name);
  792     bool dnssec = conf_bool(&val);
  793 
  794     if (dnssec) {
  795         update->zone->zonefile.resigned = true;
  796 
  797         if (zone_is_slave(conf, update->zone)) {
  798             ret = zone_set_lastsigned_serial(update->zone,
  799                                              zone_contents_serial(update->new_cont));
  800             if (ret != KNOT_EOK) {
  801                 log_zone_warning(update->zone->name,
  802                                  "unable to save lastsigned serial, "
  803                                  "future transfers might be broken");
  804             }
  805         }
  806     }
  807 
  808     /* Switch zone contents. */
  809     zone_contents_t *old_contents;
  810     old_contents = zone_switch_contents(update->zone, update->new_cont);
  811 
  812     if (update->flags & (UPDATE_INCREMENTAL | UPDATE_HYBRID)) {
  813         changeset_clear(&update->change);
  814         changeset_clear(&update->extra_ch);
  815     }
  816     zone_contents_deep_free(update->init_cont);
  817 
  818     update_clear_ctx_t *clear_ctx = calloc(1, sizeof(*clear_ctx));
  819     if (clear_ctx != NULL) {
  820         clear_ctx->free_contents = old_contents;
  821         clear_ctx->free_method = (
  822             (update->flags & (UPDATE_FULL | UPDATE_HYBRID)) ?
  823             zone_contents_deep_free : update_free_zone
  824         );
  825         clear_ctx->cleanup_apply = update->a_ctx;
  826         clear_ctx->new_cont_size = update->new_cont->size;
  827 
  828         call_rcu((struct rcu_head *)clear_ctx, update_clear);
  829     } else {
  830         log_zone_error(update->zone->name, "failed to deallocate unused memory");
  831     }
  832 
  833     /* Sync zonefile immediately if configured. */
  834     val = conf_zone_get(conf, C_ZONEFILE_SYNC, update->zone->name);
  835     if (conf_int(&val) == 0) {
  836         zone_events_schedule_now(update->zone, ZONE_EVENT_FLUSH);
  837     }
  838 
  839     memset(update, 0, sizeof(*update));
  840 
  841     return KNOT_EOK;
  842 }
  843 
  844 bool zone_update_no_change(zone_update_t *update)
  845 {
  846     if (update == NULL) {
  847         return true;
  848     }
  849 
  850     if (update->flags & (UPDATE_INCREMENTAL | UPDATE_HYBRID)) {
  851         return changeset_empty(&update->change);
  852     } else {
  853         /* This branch does not make much sense and FULL update will most likely
  854          * be a change every time anyway, just return false. */
  855         return false;
  856     }
  857 }