diff --git a/lib/dns/include/dns/rdataslab.h b/lib/dns/include/dns/rdataslab.h index 5938702927..36f99c74f0 100644 --- a/lib/dns/include/dns/rdataslab.h +++ b/lib/dns/include/dns/rdataslab.h @@ -64,17 +64,20 @@ struct dns_slabheader_proof { dns_rdatatype_t type; }; -#define DNS_SLABTOP_FOREACH(elt, first) \ - for (dns_slabtop_t *elt = first, \ - *elt##_next = (elt != NULL) ? elt->next : NULL; \ - elt != NULL; \ - elt = elt##_next, elt##_next = (elt != NULL) ? elt->next : NULL) +#define DNS_SLABTOP_FOREACH(pos, head) \ + dns_slabtop_t *pos = NULL, *pos##_next = NULL; \ + cds_list_for_each_entry_safe(pos, pos##_next, head, types_link) + +#define DNS_SLABTOP_FOREACH_FROM(pos, head, first) \ + dns_slabtop_t *pos = first, *pos##_next = NULL; \ + cds_list_for_each_entry_safe_from(pos, pos##_next, head, types_link) typedef struct dns_slabtop dns_slabtop_t; struct dns_slabtop { - dns_slabtop_t *next; - dns_slabheader_t *header; - dns_typepair_t typepair; + struct cds_list_head types_link; + struct cds_list_head headers; + + dns_typepair_t typepair; /*% Used for SIEVE-LRU (cache) and changed_list (zone) */ ISC_LINK(struct dns_slabtop) link; @@ -114,10 +117,9 @@ struct dns_slabheader { dns_slabtop_t *top; /*% - * Points to the header for the next older version of - * this rdataset. + * Link to the other versions of this rdataset. */ - struct dns_slabheader *down; + struct cds_list_head headers_link; /*% * The database node objects containing this rdataset, if any. diff --git a/lib/dns/qpcache.c b/lib/dns/qpcache.c index efc987c12c..d64ba9c880 100644 --- a/lib/dns/qpcache.c +++ b/lib/dns/qpcache.c @@ -157,7 +157,8 @@ struct qpcnode { isc_refcount_t references; isc_refcount_t erefs; - dns_slabtop_t *data; + struct cds_list_head types_list; + struct cds_list_head *data; /*% * NOTE: The 'dirty' flag is protected by the node lock, so @@ -466,7 +467,17 @@ rdataset_size(dns_slabheader_t *header) { static dns_slabheader_t * first_header(dns_slabtop_t *top) { - return top->header; + dns_slabheader_t *header = NULL; + cds_list_for_each_entry(header, &top->headers, headers_link) { + return header; + } + return NULL; +} + +static dns_slabheader_t * +next_header(dns_slabheader_t *header) { + return cds_list_entry((header)->headers_link.next, dns_slabheader_t, + headers_link); } static dns_slabheader_t * @@ -547,22 +558,21 @@ qpcache_hit(qpcache_t *qpdb ISC_ATTR_UNUSED, dns_slabheader_t *header) { static void clean_cache_headers(dns_slabtop_t *top) { - if (top->header == NULL) { + dns_slabheader_t *parent = first_header(top); + if (parent == NULL) { return; } - dns_slabheader_t *header = top->header, *header_down = NULL; - for (header = header->down; header != NULL; header = header_down) { - header_down = header->down; + dns_slabheader_t *header = next_header(parent), *header_next = NULL; + cds_list_for_each_entry_safe_from(header, header_next, &top->headers, + headers_link) { + cds_list_del(&header->headers_link); dns_slabheader_destroy(&header); } - top->header->down = NULL; } static void clean_cache_node(qpcache_t *qpdb, qpcnode_t *node) { - dns_slabtop_t *top_prev = NULL; - /* * Caller must be holding the node lock. */ @@ -574,21 +584,23 @@ clean_cache_node(qpcache_t *qpdb, qpcnode_t *node) { * If current top header is nonexistent, ancient, or stale * and we are not keeping stale, we can clean it up too. */ - if (!EXISTS(top->header) || ANCIENT(top->header) || - (STALE(top->header) && !KEEPSTALE(qpdb))) + dns_slabheader_t *header = first_header(top); + if (header == NULL) { + continue; + } + + if (!EXISTS(header) || ANCIENT(header) || + (STALE(header) && !KEEPSTALE(qpdb))) { - dns_slabheader_destroy(&top->header); + cds_list_del(&header->headers_link); + dns_slabheader_destroy(&header); } /* * If current slabtop is empty, we can clean it up. */ - if (top->header == NULL) { - if (top_prev != NULL) { - top_prev->next = top->next; - } else { - node->data = top->next; - } + if (header == NULL) { + cds_list_del(&top->types_link); if (ISC_LINK_LINKED(top, link)) { ISC_SIEVE_UNLINK( @@ -596,12 +608,10 @@ clean_cache_node(qpcache_t *qpdb, qpcnode_t *node) { link); } dns_slabtop_destroy(((dns_db_t *)qpdb)->mctx, &top); - } else { - top_prev = top; } } - node->dirty = 0; + node->dirty = false; } /* @@ -750,7 +760,7 @@ qpcnode_release(qpcache_t *qpdb, qpcnode_t *node, isc_rwlocktype_t *nlocktypep, } /* Handle easy and typical case first. */ - if (!node->dirty && node->data != NULL) { + if (!node->dirty && !cds_list_empty(node->data)) { goto unref; } @@ -781,7 +791,7 @@ qpcnode_release(qpcache_t *qpdb, qpcnode_t *node, isc_rwlocktype_t *nlocktypep, clean_cache_node(qpdb, node); } - if (node->data != NULL) { + if (!cds_list_empty(node->data)) { goto unref; } @@ -2383,6 +2393,8 @@ static qpcnode_t * new_qpcnode(qpcache_t *qpdb, const dns_name_t *name, dns_namespace_t nspace) { qpcnode_t *newdata = isc_mem_get(qpdb->common.mctx, sizeof(*newdata)); *newdata = (qpcnode_t){ + .types_list = CDS_LIST_HEAD_INIT(newdata->types_list), + .data = &newdata->types_list, .methods = &qpcnode_methods, .qpdb = qpdb, .name = DNS_NAME_INITEMPTY, @@ -2705,12 +2717,12 @@ add(qpcache_t *qpdb, qpcnode_t *qpnode, dns_slabheader_t *newheader, if (top->typepair == newheader->typepair) { INSIST(oldheader == NULL); - oldheader = top->header; + oldheader = first_header(top); } if (sigpair != dns_rdatatype_none && top->typepair == sigpair) { INSIST(oldsigheader == NULL); - oldsigheader = top->header; + oldsigheader = first_header(top); } } @@ -2854,9 +2866,9 @@ add(qpcache_t *qpdb, qpcnode_t *qpnode, dns_slabheader_t *newheader, return DNS_R_UNCHANGED; } - oldheader->top->header = newheader; newheader->top = oldheader->top; - newheader->down = oldheader; + cds_list_add(&newheader->headers_link, + &oldheader->top->headers); ISC_SIEVE_UNLINK(qpdb->buckets[qpnode->locknum].sieve, oldheader->top, link); @@ -2876,28 +2888,24 @@ add(qpcache_t *qpdb, qpcnode_t *qpnode, dns_slabheader_t *newheader, return DNS_R_UNCHANGED; } else { /* No rdatasets of the given type exist at the node. */ - INSIST(newheader->down == NULL); - dns_slabtop_t *newtop = dns_slabtop_new( ((dns_db_t *)qpdb)->mctx, newheader->typepair); - newtop->header = newheader; newheader->top = newtop; + cds_list_add(&newheader->headers_link, &newtop->headers); + qpcache_miss(qpdb, newheader, &nlocktype, &tlocktype DNS__DB_FLARG_PASS); if (prio_header(newtop)) { /* This is a priority type, prepend it */ - newtop->next = qpnode->data; - qpnode->data = newtop; + cds_list_add(&newtop->types_link, qpnode->data); } else if (priotop != NULL) { /* Append after the priority headers */ - newtop->next = priotop->next; - priotop->next = newtop; + cds_list_add(&newtop->types_link, &priotop->types_link); } else { /* There were no priority headers */ - newtop->next = qpnode->data; - qpnode->data = newtop; + cds_list_add(&newtop->types_link, qpnode->data); } if (overmaxtype(qpdb, ntypes)) { @@ -3387,24 +3395,29 @@ rdatasetiter_next(dns_rdatasetiter_t *it DNS__DB_FLARG) { qpcnode_t *qpnode = (qpcnode_t *)iterator->common.node; isc_rwlocktype_t nlocktype = isc_rwlocktype_none; isc_rwlock_t *nlock = &qpdb->buckets[qpnode->locknum].lock; - dns_slabtop_t *next = NULL; + dns_slabtop_t *from = NULL; if (iterator->current == NULL) { return ISC_R_NOMORE; } - next = iterator->current->next; - iterator->current = NULL; NODE_RDLOCK(nlock, &nlocktype); - DNS_SLABTOP_FOREACH(top, next) { - dns_slabheader_t *header = first_existing_header(top); + from = cds_list_entry(iterator->current->types_link.next, dns_slabtop_t, + types_link); + iterator->current = NULL; - if (EXPIREDOK(iterator) || - (header != NULL && iterator_active(qpdb, iterator, header))) - { - iterator->current = top; - break; + if (from != NULL) { + DNS_SLABTOP_FOREACH_FROM(top, qpnode->data, from) { + dns_slabheader_t *header = first_existing_header(top); + + if (EXPIREDOK(iterator) || + (header != NULL && + iterator_active(qpdb, iterator, header))) + { + iterator->current = top; + break; + } } } @@ -3831,12 +3844,13 @@ qpcnode_destroy(qpcnode_t *qpnode) { qpcache_t *qpdb = qpnode->qpdb; DNS_SLABTOP_FOREACH(top, qpnode->data) { - dns_slabheader_t *down = NULL, *down_next = NULL; - for (down = top->header; down != NULL; down = down_next) { - down_next = down->down; - dns_slabheader_destroy(&down); + dns_slabheader_t *header = NULL, *header_next = NULL; + cds_list_for_each_entry_safe(header, header_next, &top->headers, + headers_link) + { + cds_list_del(&header->headers_link); + dns_slabheader_destroy(&header); } - top->header = NULL; if (ISC_LINK_LINKED(top, link)) { ISC_SIEVE_UNLINK(qpdb->buckets[qpnode->locknum].sieve, diff --git a/lib/dns/qpzone.c b/lib/dns/qpzone.c index 9eaf4f28e7..f6d0e30ee1 100644 --- a/lib/dns/qpzone.c +++ b/lib/dns/qpzone.c @@ -198,7 +198,9 @@ struct qpznode { atomic_bool wild; atomic_bool delegating; atomic_bool dirty; - dns_slabtop_t *data; + + struct cds_list_head types_list; + struct cds_list_head *data; }; struct qpzonedb { @@ -631,6 +633,8 @@ static qpznode_t * new_qpznode(qpzonedb_t *qpdb, const dns_name_t *name, dns_namespace_t nspace) { qpznode_t *newdata = isc_mem_get(qpdb->common.mctx, sizeof(*newdata)); *newdata = (qpznode_t){ + .types_list = CDS_LIST_HEAD_INIT(newdata->types_list), + .data = &newdata->types_list, .methods = &qpznode_methods, .name = DNS_NAME_INITEMPTY, .nspace = nspace, @@ -801,16 +805,48 @@ qpznode_acquire(qpznode_t *node DNS__DB_FLARG) { qpznode_erefs_increment(node DNS__DB_FLARG_PASS); } +static dns_slabheader_t * +first_header(dns_slabtop_t *top) { + dns_slabheader_t *header = NULL; + cds_list_for_each_entry(header, &top->headers, headers_link) { + return header; + } + return NULL; +} + +static dns_slabheader_t * +next_header(dns_slabheader_t *header) { + return cds_list_entry((header)->headers_link.next, dns_slabheader_t, + headers_link); +} + +static dns_slabheader_t * +first_existing_header(dns_slabtop_t *top, uint32_t serial) { + dns_slabheader_t *header = NULL; + cds_list_for_each_entry(header, &top->headers, headers_link) { + if (header->serial <= serial && !IGNORE(header)) { + if (EXISTS(header)) { + return header; + } + break; + } + } + return NULL; +} + static void clean_multiple_headers(dns_slabtop_t *top) { - dns_slabheader_t *parent = top->header; - dns_slabheader_t *header = NULL, *header_down = NULL; + dns_slabheader_t *parent = first_header(top); + if (parent == NULL) { + return; + } - for (header = parent->down; header != NULL; header = header_down) { - header_down = header->down; + dns_slabheader_t *header = next_header(parent), *header_next = NULL; + cds_list_for_each_entry_safe_from(header, header_next, &top->headers, + headers_link) { INSIST(header->serial <= parent->serial); if (header->serial == parent->serial || IGNORE(header)) { - parent->down = header->down; + cds_list_del(&header->headers_link); dns_slabheader_destroy(&header); } else { parent = header; @@ -820,23 +856,26 @@ clean_multiple_headers(dns_slabtop_t *top) { static void check_top_header(dns_slabtop_t *top) { - dns_slabheader_t *header = top->header; - if (IGNORE(header)) { - top->header = header->down; + dns_slabheader_t *header = first_header(top); + if (header != NULL && IGNORE(header)) { + cds_list_del(&header->headers_link); dns_slabheader_destroy(&header); } } static bool clean_multiple_versions(dns_slabtop_t *top, uint32_t least_serial) { - dns_slabheader_t *parent = top->header; - dns_slabheader_t *header = NULL, *header_down = NULL; - bool multiple = false; + dns_slabheader_t *parent = first_header(top); + if (parent == NULL) { + return false; + } - for (header = parent->down; header != NULL; header = header_down) { - header_down = header->down; + bool multiple = false; + dns_slabheader_t *header = next_header(parent), *header_next = NULL; + cds_list_for_each_entry_safe_from(header, header_next, &top->headers, + headers_link) { if (header->serial < least_serial) { - parent->down = header->down; + cds_list_del(&header->headers_link); dns_slabheader_destroy(&header); } else { multiple = true; @@ -848,7 +887,6 @@ clean_multiple_versions(dns_slabtop_t *top, uint32_t least_serial) { static void clean_zone_node(qpznode_t *node, uint32_t least_serial) { - dns_slabtop_t *top_prev = NULL; bool still_dirty = false; /* @@ -857,8 +895,6 @@ clean_zone_node(qpznode_t *node, uint32_t least_serial) { REQUIRE(least_serial != 0); DNS_SLABTOP_FOREACH(top, node->data) { - INSIST(top->header != NULL); - /* * First, we clean up any instances of multiple rdatasets * with the same serial number, or that have the IGNORE @@ -872,12 +908,8 @@ clean_zone_node(qpznode_t *node, uint32_t least_serial) { */ check_top_header(top); - if (top->header == NULL) { - if (top_prev != NULL) { - top_prev->next = top->next; - } else { - node->data = top->next; - } + if (first_header(top) == NULL) { + cds_list_del(&top->types_link); dns_slabtop_destroy(node->mctx, &top); } else { /* @@ -891,8 +923,6 @@ clean_zone_node(qpznode_t *node, uint32_t least_serial) { */ still_dirty = clean_multiple_versions(top, least_serial); - - top_prev = top; } } if (!still_dirty) { @@ -942,7 +972,7 @@ qpznode_release(qpznode_t *node, uint32_t least_serial, } /* Handle easy and typical case first. */ - if (!node->dirty && node->data != NULL) { + if (!node->dirty && !cds_list_empty(node->data)) { goto unref; } @@ -1016,28 +1046,6 @@ bindrdataset(qpzonedb_t *qpdb, qpznode_t *node, dns_slabheader_t *header, } } -static dns_slabheader_t * -first_header(dns_slabtop_t *top, uint32_t serial) { - for (dns_slabheader_t *header = top->header; header != NULL; - header = header->down) - { - if (header->serial <= serial && !IGNORE(header)) { - return header; - } - } - - return NULL; -} - -static dns_slabheader_t * -first_existing_header(dns_slabtop_t *top, uint32_t serial) { - dns_slabheader_t *header = first_header(top, serial); - if (header != NULL && EXISTS(header)) { - return header; - } - return NULL; -} - static void setnsec3parameters(dns_db_t *db, qpz_version_t *version) { qpznode_t *node = NULL; @@ -1296,17 +1304,8 @@ rollback_node(qpznode_t *node, uint32_t serial) { * will be cleaned up; until that time, they will be ignored. */ DNS_SLABTOP_FOREACH(top, node->data) { - dns_slabheader_t *header = top->header; - - if (header->serial == serial) { - DNS_SLABHEADER_SETATTR(header, - DNS_SLABHEADERATTR_IGNORE); - make_dirty = true; - } - - for (header = header->down; header != NULL; - header = header->down) - { + dns_slabheader_t *header = NULL; + cds_list_for_each_entry(header, &top->headers, headers_link) { if (header->serial == serial) { DNS_SLABHEADER_SETATTR( header, DNS_SLABHEADERATTR_IGNORE); @@ -1825,12 +1824,14 @@ add(qpzonedb_t *qpdb, qpznode_t *node, const dns_name_t *nodename, * IGNORE rdatasets between the top of the chain and the first real * data. We skip over them. */ - dns_slabheader_t *header = NULL, *header_prev = NULL; + dns_slabheader_t *header = NULL; if (foundtop != NULL) { - header = foundtop->header; - while (header != NULL && IGNORE(header)) { - header_prev = header; - header = header->down; + dns_slabheader_t *tmp = NULL; + cds_list_for_each_entry(tmp, &top->headers, headers_link) { + if (!IGNORE(tmp)) { + header = tmp; + break; + } } } @@ -1898,11 +1899,10 @@ add(qpzonedb_t *qpdb, qpznode_t *node, const dns_name_t *nodename, } } - INSIST(version->serial >= foundtop->header->serial); + INSIST(version->serial >= header->serial); INSIST(foundtop->typepair == newheader->typepair); if (loading) { - newheader->down = NULL; if (RESIGN(newheader)) { resigninsert(newheader); /* resigndelete not needed here */ @@ -1915,7 +1915,9 @@ add(qpzonedb_t *qpdb, qpznode_t *node, const dns_name_t *nodename, * loading, we MUST clean up 'header' now. */ newheader->top = foundtop; - foundtop->header = newheader; + cds_list_del(&header->headers_link); + cds_list_add(&newheader->headers_link, + &foundtop->headers); maybe_update_recordsandsize(false, version, header, nodename->length); @@ -1927,14 +1929,9 @@ add(qpzonedb_t *qpdb, qpznode_t *node, const dns_name_t *nodename, header DNS__DB_FLARG_PASS); } - if (header_prev != NULL) { - header_prev->down = newheader; - } else { - foundtop->header = newheader; - } - newheader->top = foundtop; - newheader->down = header; + cds_list_add(&newheader->headers_link, + &foundtop->headers); node->dirty = true; if (changed != NULL) { @@ -1970,10 +1967,11 @@ add(qpzonedb_t *qpdb, qpznode_t *node, const dns_name_t *nodename, * we INSIST on it. */ INSIST(!loading); - INSIST(version->serial >= foundtop->header->serial); + newheader->top = foundtop; - newheader->down = foundtop->header; - foundtop->header = newheader; + cds_list_add(&newheader->headers_link, + &foundtop->headers); + if (changed != NULL) { changed->dirty = true; } @@ -1994,20 +1992,19 @@ add(qpzonedb_t *qpdb, qpznode_t *node, const dns_name_t *nodename, node->mctx, newheader->typepair); newheader->top = newtop; - newtop->header = newheader; + cds_list_add(&newheader->headers_link, + &newtop->headers); if (prio_type(newheader->typepair)) { /* This is a priority type, prepend it */ - newtop->next = node->data; - node->data = newtop; + cds_list_add(&newtop->types_link, node->data); } else if (priotop != NULL) { /* Append after the priority headers */ - newtop->next = priotop->next; - priotop->next = newtop; + cds_list_add(&newtop->types_link, + &priotop->types_link); } else { /* There were no priority headers */ - newtop->next = node->data; - node->data = newtop; + cds_list_add(&newtop->types_link, node->data); } } } @@ -2135,7 +2132,6 @@ loading_addrdataset(void *arg, const dns_name_t *name, newheader = (dns_slabheader_t *)region.base; dns_slabheader_reset(newheader, (dns_dbnode_t *)node); - newheader->ttl = rdataset->ttl; atomic_store(&newheader->trust, rdataset->trust); newheader->serial = 1; @@ -3964,25 +3960,30 @@ rdatasetiter_next(dns_rdatasetiter_t *iterator DNS__DB_FLARG) { qpz_version_t *version = (qpz_version_t *)qrditer->common.version; isc_rwlocktype_t nlocktype = isc_rwlocktype_none; isc_rwlock_t *nlock = qpzone_get_lock(node); - dns_slabtop_t *next = NULL; + dns_slabtop_t *from = NULL; if (qrditer->currenttop == NULL) { return ISC_R_NOMORE; } - next = qrditer->currenttop->next; - qrditer->currenttop = NULL; - qrditer->current = NULL; NODE_RDLOCK(nlock, &nlocktype); + from = cds_list_entry(qrditer->currenttop->types_link.next, + dns_slabtop_t, types_link); + qrditer->currenttop = NULL; + qrditer->current = NULL; + /* * Find the start of the header chain for the next type. */ - DNS_SLABTOP_FOREACH(top, next) { - qrditer->current = first_existing_header(top, version->serial); - if (qrditer->current != NULL) { - qrditer->currenttop = top; - break; + if (from != NULL) { + DNS_SLABTOP_FOREACH_FROM(top, node->data, from) { + qrditer->current = + first_existing_header(top, version->serial); + if (qrditer->current != NULL) { + qrditer->currenttop = top; + break; + } } } @@ -4766,9 +4767,12 @@ qpzone_subtractrdataset(dns_db_t *db, dns_dbnode_t *dbnode, */ dns_slabheader_t *header = NULL; if (foundtop != NULL) { - header = foundtop->header; - while (header != NULL && IGNORE(header)) { - header = header->down; + dns_slabheader_t *tmp = NULL; + cds_list_for_each_entry(tmp, &foundtop->headers, headers_link) { + if (!IGNORE(tmp)) { + header = tmp; + break; + } } } if (header != NULL && EXISTS(header)) { @@ -4834,13 +4838,11 @@ qpzone_subtractrdataset(dns_db_t *db, dns_dbnode_t *dbnode, /* * If we're here, we want to link newheader at the top. */ - INSIST(version->serial >= foundtop->header->serial); maybe_update_recordsandsize(false, version, header, nodename->length); newheader->top = foundtop; - newheader->down = foundtop->header; - foundtop->header = newheader; + cds_list_add(&newheader->headers_link, &foundtop->headers); node->dirty = true; changed->dirty = true; @@ -5291,12 +5293,13 @@ static dns_dbnode_methods_t qpznode_methods = (dns_dbnode_methods_t){ static void destroy_qpznode(qpznode_t *node) { DNS_SLABTOP_FOREACH(top, node->data) { - dns_slabheader_t *down = NULL, *down_next = NULL; - for (down = top->header; down != NULL; down = down_next) { - down_next = down->down; - dns_slabheader_destroy(&down); + dns_slabheader_t *header = NULL, *header_next = NULL; + cds_list_for_each_entry_safe(header, header_next, &top->headers, + headers_link) + { + cds_list_del(&header->headers_link); + dns_slabheader_destroy(&header); } - top->header = NULL; dns_slabtop_destroy(node->mctx, &top); } diff --git a/lib/dns/rdataslab.c b/lib/dns/rdataslab.c index 3616b3aead..703c2f6b1e 100644 --- a/lib/dns/rdataslab.c +++ b/lib/dns/rdataslab.c @@ -323,6 +323,7 @@ dns_rdataslab_fromrdataset(dns_rdataset_t *rdataset, isc_mem_t *mctx, } *new = (dns_slabheader_t){ + .headers_link = CDS_LIST_HEAD_INIT(new->headers_link), .typepair = typepair, .trust = rdataset->trust, .ttl = rdataset->ttl, @@ -1179,6 +1180,8 @@ dns_slabtop_t * dns_slabtop_new(isc_mem_t *mctx, dns_typepair_t typepair) { dns_slabtop_t *top = isc_mem_get(mctx, sizeof(*top)); *top = (dns_slabtop_t){ + .types_link = CDS_LIST_HEAD_INIT(top->types_link), + .headers = CDS_LIST_HEAD_INIT(top->headers), .typepair = typepair, .link = ISC_LINK_INITIALIZER, }; diff --git a/lib/isc/include/isc/urcu.h b/lib/isc/include/isc/urcu.h index cf62934632..88e68bf80c 100644 --- a/lib/isc/include/isc/urcu.h +++ b/lib/isc/include/isc/urcu.h @@ -30,6 +30,7 @@ #include #include +#include #include #include #include