LCOV - code coverage report
Current view: top level - bpfilter/cgen - cgen.c (source / functions) Coverage Total Hit
Test: coverage.lcov Lines: 86.7 % 210 182
Test Date: 2025-11-24 12:34:34 Functions: 92.9 % 14 13
Branches: 44.1 % 186 82

             Branch data     Line data    Source code
       1                 :             : /* SPDX-License-Identifier: GPL-2.0-only */
       2                 :             : /*
       3                 :             :  * Copyright (c) 2023 Meta Platforms, Inc. and affiliates.
       4                 :             :  */
       5                 :             : 
       6                 :             : #include "cgen/cgen.h"
       7                 :             : 
       8                 :             : #include <errno.h>
       9                 :             : #include <stddef.h>
      10                 :             : #include <stdint.h>
      11                 :             : #include <stdlib.h>
      12                 :             : #include <string.h>
      13                 :             : #include <sys/types.h>
      14                 :             : 
      15                 :             : #include <bpfilter/chain.h>
      16                 :             : #include <bpfilter/counter.h>
      17                 :             : #include <bpfilter/dump.h>
      18                 :             : #include <bpfilter/front.h>
      19                 :             : #include <bpfilter/helper.h>
      20                 :             : #include <bpfilter/hook.h>
      21                 :             : #include <bpfilter/io.h>
      22                 :             : #include <bpfilter/list.h>
      23                 :             : #include <bpfilter/logger.h>
      24                 :             : #include <bpfilter/ns.h>
      25                 :             : #include <bpfilter/pack.h>
      26                 :             : 
      27                 :             : #include "cgen/dump.h"
      28                 :             : #include "cgen/prog/link.h"
      29                 :             : #include "cgen/program.h"
      30                 :             : #include "ctx.h"
      31                 :             : #include "opts.h"
      32                 :             : 
      33                 :         107 : static int _bf_cgen_get_chain_pindir_fd(const char *name)
      34                 :             : {
      35                 :         107 :     _cleanup_close_ int bf_fd = -1;
      36                 :         107 :     _cleanup_close_ int chain_fd = -1;
      37                 :             : 
      38                 :             :     bf_assert(name);
      39                 :             : 
      40                 :         107 :     bf_fd = bf_ctx_get_pindir_fd();
      41         [ +  - ]:         107 :     if (bf_fd < 0)
      42                 :             :         return bf_fd;
      43                 :             : 
      44                 :         107 :     chain_fd = bf_opendir_at(bf_fd, name, true);
      45         [ -  + ]:         107 :     if (chain_fd < 0)
      46                 :             :         return chain_fd;
      47                 :             : 
      48                 :         107 :     return TAKE_FD(chain_fd);
      49                 :             : }
      50                 :             : 
      51                 :          57 : int bf_cgen_new(struct bf_cgen **cgen, enum bf_front front,
      52                 :             :                 struct bf_chain **chain)
      53                 :             : {
      54                 :             :     bf_assert(cgen && chain && *chain);
      55                 :             : 
      56                 :          57 :     *cgen = malloc(sizeof(struct bf_cgen));
      57         [ +  - ]:          57 :     if (!*cgen)
      58                 :             :         return -ENOMEM;
      59                 :             : 
      60                 :          57 :     (*cgen)->front = front;
      61                 :          57 :     (*cgen)->program = NULL;
      62                 :             :     (*cgen)->chain = NULL;
      63                 :          57 :     (*cgen)->chain = TAKE_PTR(*chain);
      64                 :             : 
      65                 :          57 :     return 0;
      66                 :             : }
      67                 :             : 
      68                 :           2 : int bf_cgen_new_from_pack(struct bf_cgen **cgen, bf_rpack_node_t node)
      69                 :             : {
      70                 :           2 :     _free_bf_cgen_ struct bf_cgen *_cgen = NULL;
      71                 :             :     bf_rpack_node_t child;
      72                 :             :     int r;
      73                 :             : 
      74                 :             :     bf_assert(cgen);
      75                 :             : 
      76                 :           2 :     _cgen = malloc(sizeof(*_cgen));
      77         [ +  - ]:           2 :     if (!_cgen)
      78                 :             :         return -ENOMEM;
      79                 :             : 
      80                 :           2 :     _cgen->program = NULL;
      81                 :             : 
      82         [ -  + ]:           2 :     r = bf_rpack_kv_enum(node, "front", &_cgen->front);
      83                 :             :     if (r)
      84         [ #  # ]:           0 :         return bf_rpack_key_err(r, "bf_cgen.front");
      85                 :             : 
      86                 :           2 :     r = bf_rpack_kv_obj(node, "chain", &child);
      87         [ -  + ]:           2 :     if (r)
      88         [ #  # ]:           0 :         return bf_rpack_key_err(r, "bf_cgen.chain");
      89                 :             : 
      90                 :           2 :     r = bf_chain_new_from_pack(&_cgen->chain, child);
      91         [ -  + ]:           2 :     if (r)
      92         [ #  # ]:           0 :         return bf_rpack_key_err(r, "bf_cgen.chain");
      93                 :             : 
      94                 :           2 :     r = bf_rpack_kv_node(node, "program", &child);
      95         [ -  + ]:           2 :     if (r)
      96         [ #  # ]:           0 :         return bf_rpack_key_err(r, "bf_cgen.program");
      97         [ +  - ]:           2 :     if (!bf_rpack_is_nil(child)) {
      98                 :           2 :         _cleanup_close_ int dir_fd = -1;
      99                 :             : 
     100         [ -  + ]:           2 :         if ((dir_fd = _bf_cgen_get_chain_pindir_fd(_cgen->chain->name)) < 0) {
     101         [ #  # ]:           0 :             return bf_err_r(dir_fd,
     102                 :             :                             "failed to open chain pin directory for '%s'",
     103                 :             :                             _cgen->chain->name);
     104                 :             :         }
     105                 :             : 
     106                 :           2 :         r = bf_program_new_from_pack(&_cgen->program, _cgen->chain, dir_fd,
     107                 :             :                                      child);
     108         [ +  - ]:           2 :         if (r)
     109                 :             :             return r;
     110                 :             :     }
     111                 :             : 
     112                 :           2 :     *cgen = TAKE_PTR(_cgen);
     113                 :             : 
     114                 :           2 :     return 0;
     115                 :             : }
     116                 :             : 
     117                 :         118 : void bf_cgen_free(struct bf_cgen **cgen)
     118                 :             : {
     119                 :          59 :     _cleanup_close_ int pin_fd = -1;
     120                 :             : 
     121                 :             :     bf_assert(cgen);
     122                 :             : 
     123         [ +  + ]:         118 :     if (!*cgen)
     124                 :             :         return;
     125                 :             : 
     126                 :             :     /* Perform a non-recursive removal of the chain's pin directory: if
     127                 :             :      * the chain hasn't been pinned (e.g. due to a failure), the pin directory
     128                 :             :      * will be empty and will be removed. If the chain is valid and pinned, then
     129                 :             :      * the removal of the pin directory will fail, but that's alright. */
     130   [ +  -  +  - ]:          59 :     if (bf_opts_persist() && (pin_fd = bf_ctx_get_pindir_fd()) >= 0)
     131                 :          59 :         bf_rmdir_at(pin_fd, (*cgen)->chain->name, false);
     132                 :             : 
     133                 :          59 :     bf_program_free(&(*cgen)->program);
     134                 :          59 :     bf_chain_free(&(*cgen)->chain);
     135                 :             : 
     136                 :          59 :     free(*cgen);
     137                 :          59 :     *cgen = NULL;
     138                 :             : }
     139                 :             : 
     140                 :         164 : int bf_cgen_pack(const struct bf_cgen *cgen, bf_wpack_t *pack)
     141                 :             : {
     142                 :             :     bf_assert(cgen);
     143                 :             :     bf_assert(pack);
     144                 :             : 
     145                 :         164 :     bf_wpack_kv_enum(pack, "front", cgen->front);
     146                 :             : 
     147                 :         164 :     bf_wpack_open_object(pack, "chain");
     148                 :         164 :     bf_chain_pack(cgen->chain, pack);
     149                 :         164 :     bf_wpack_close_object(pack);
     150                 :             : 
     151         [ +  - ]:         164 :     if (cgen->program) {
     152                 :         164 :         bf_wpack_open_object(pack, "program");
     153                 :         164 :         bf_program_pack(cgen->program, pack);
     154                 :         164 :         bf_wpack_close_object(pack);
     155                 :             :     } else {
     156                 :             :         bf_wpack_kv_nil(pack, "program");
     157                 :             :     }
     158                 :             : 
     159         [ -  + ]:         164 :     return bf_wpack_is_valid(pack) ? 0 : -EINVAL;
     160                 :             : }
     161                 :             : 
     162                 :          14 : void bf_cgen_dump(const struct bf_cgen *cgen, prefix_t *prefix)
     163                 :             : {
     164                 :             :     bf_assert(cgen);
     165                 :             :     bf_assert(prefix);
     166                 :             : 
     167         [ +  - ]:          14 :     DUMP(prefix, "struct bf_cgen at %p", cgen);
     168                 :             : 
     169                 :          14 :     bf_dump_prefix_push(prefix);
     170         [ +  - ]:          14 :     DUMP(prefix, "front: %s", bf_front_to_str(cgen->front));
     171                 :             : 
     172                 :             :     // Chain
     173         [ +  - ]:          14 :     DUMP(prefix, "chain: struct bf_chain *");
     174                 :          14 :     bf_dump_prefix_push(prefix);
     175                 :          14 :     bf_chain_dump(cgen->chain, bf_dump_prefix_last(prefix));
     176                 :          14 :     bf_dump_prefix_pop(prefix);
     177                 :             : 
     178                 :             :     // Programs
     179         [ +  + ]:          14 :     if (cgen->program) {
     180         [ +  - ]:           2 :         DUMP(bf_dump_prefix_last(prefix), "program: struct bf_program *");
     181                 :           2 :         bf_dump_prefix_push(prefix);
     182                 :           2 :         bf_program_dump(cgen->program, bf_dump_prefix_last(prefix));
     183                 :           2 :         bf_dump_prefix_pop(prefix);
     184                 :             :     } else {
     185         [ +  - ]:          12 :         DUMP(bf_dump_prefix_last(prefix), "program: (struct bf_program *)NULL");
     186                 :             :     }
     187                 :             : 
     188                 :          14 :     bf_dump_prefix_pop(prefix);
     189                 :          14 : }
     190                 :             : 
     191                 :          32 : int bf_cgen_get_counter(const struct bf_cgen *cgen,
     192                 :             :                         enum bf_counter_type counter_idx,
     193                 :             :                         struct bf_counter *counter)
     194                 :             : {
     195                 :             :     bf_assert(cgen && counter);
     196                 :             : 
     197                 :             :     /* There are two more counter than rules. The special counters must
     198                 :             :      * be accessed via the specific values, to avoid confusion. */
     199         [ +  + ]:          32 :     enum bf_counter_type rule_count = bf_list_size(&cgen->chain->rules);
     200         [ +  + ]:          32 :     if (counter_idx == BF_COUNTER_POLICY) {
     201                 :             :         counter_idx = rule_count;
     202         [ +  + ]:          19 :     } else if (counter_idx == BF_COUNTER_ERRORS) {
     203                 :          13 :         counter_idx = rule_count + 1;
     204         [ +  - ]:           6 :     } else if (counter_idx < 0 || counter_idx >= rule_count) {
     205                 :             :         return -EINVAL;
     206                 :             :     }
     207                 :             : 
     208                 :          32 :     return bf_program_get_counter(cgen->program, counter_idx, counter);
     209                 :             : }
     210                 :             : 
     211                 :          45 : int bf_cgen_set(struct bf_cgen *cgen, const struct bf_ns *ns,
     212                 :             :                 struct bf_hookopts **hookopts)
     213                 :             : {
     214                 :          45 :     _free_bf_program_ struct bf_program *prog = NULL;
     215                 :          45 :     _cleanup_close_ int pindir_fd = -1;
     216                 :             :     int r;
     217                 :             : 
     218                 :             :     bf_assert(cgen);
     219                 :             : 
     220         [ +  - ]:          45 :     if (bf_opts_persist()) {
     221                 :          45 :         pindir_fd = _bf_cgen_get_chain_pindir_fd(cgen->chain->name);
     222         [ +  - ]:          45 :         if (pindir_fd < 0)
     223                 :             :             return pindir_fd;
     224                 :             :     }
     225                 :             : 
     226                 :          45 :     r = bf_program_new(&prog, cgen->chain);
     227         [ +  - ]:          45 :     if (r < 0)
     228                 :             :         return r;
     229                 :             : 
     230                 :          45 :     r = bf_program_generate(prog);
     231         [ -  + ]:          45 :     if (r < 0)
     232         [ #  # ]:           0 :         return bf_err_r(r, "failed to generate bf_program");
     233                 :             : 
     234                 :          45 :     r = bf_program_load(prog);
     235         [ -  + ]:          45 :     if (r < 0)
     236         [ #  # ]:           0 :         return bf_err_r(r, "failed to load the chain");
     237                 :             : 
     238         [ +  + ]:          45 :     if (hookopts) {
     239                 :          16 :         r = bf_ns_set(ns, bf_ctx_get_ns());
     240         [ -  + ]:          16 :         if (r)
     241         [ #  # ]:           0 :             return bf_err_r(r, "failed to switch to the client's namespaces");
     242                 :             : 
     243                 :          16 :         r = bf_program_attach(prog, hookopts);
     244         [ +  + ]:          16 :         if (r < 0)
     245         [ +  - ]:           2 :             return bf_err_r(r, "failed to load and attach the chain");
     246                 :             : 
     247         [ -  + ]:          14 :         if (bf_ns_set(bf_ctx_get_ns(), ns))
     248         [ #  # ]:           0 :             bf_abort("failed to restore previous namespaces, aborting");
     249                 :             :     }
     250                 :             : 
     251         [ +  - ]:          43 :     if (bf_opts_persist()) {
     252                 :          43 :         r = bf_program_pin(prog, pindir_fd);
     253         [ +  - ]:          43 :         if (r)
     254                 :             :             return r;
     255                 :             :     }
     256                 :             : 
     257                 :          43 :     cgen->program = TAKE_PTR(prog);
     258                 :             : 
     259                 :          43 :     return r;
     260                 :             : }
     261                 :             : 
     262                 :          12 : int bf_cgen_load(struct bf_cgen *cgen)
     263                 :             : {
     264                 :          12 :     _free_bf_program_ struct bf_program *prog = NULL;
     265                 :          12 :     _cleanup_close_ int pindir_fd = -1;
     266                 :             :     int r;
     267                 :             : 
     268                 :             :     bf_assert(cgen);
     269                 :             : 
     270         [ +  - ]:          12 :     if (bf_opts_persist()) {
     271                 :          12 :         pindir_fd = _bf_cgen_get_chain_pindir_fd(cgen->chain->name);
     272         [ +  - ]:          12 :         if (pindir_fd < 0)
     273                 :             :             return pindir_fd;
     274                 :             :     }
     275                 :             : 
     276                 :          12 :     r = bf_program_new(&prog, cgen->chain);
     277         [ +  - ]:          12 :     if (r < 0)
     278                 :             :         return r;
     279                 :             : 
     280                 :          12 :     r = bf_program_generate(prog);
     281         [ -  + ]:          12 :     if (r < 0)
     282         [ #  # ]:           0 :         return bf_err_r(r, "failed to generate bf_program");
     283                 :             : 
     284                 :          12 :     r = bf_program_load(prog);
     285         [ -  + ]:          12 :     if (r < 0)
     286         [ #  # ]:           0 :         return bf_err_r(r, "failed to load the chain");
     287                 :             : 
     288         [ +  - ]:          12 :     if (bf_opts_persist()) {
     289                 :          12 :         r = bf_program_pin(prog, pindir_fd);
     290         [ +  - ]:          12 :         if (r)
     291                 :             :             return r;
     292                 :             :     }
     293                 :             : 
     294         [ +  - ]:          12 :     bf_info("load %s", cgen->chain->name);
     295                 :          12 :     bf_cgen_dump(cgen, EMPTY_PREFIX);
     296                 :             : 
     297                 :          12 :     cgen->program = TAKE_PTR(prog);
     298                 :             : 
     299                 :          12 :     return r;
     300                 :             : }
     301                 :             : 
     302                 :           9 : int bf_cgen_attach(struct bf_cgen *cgen, const struct bf_ns *ns,
     303                 :             :                    struct bf_hookopts **hookopts)
     304                 :             : {
     305                 :           9 :     _cleanup_close_ int pindir_fd = -1;
     306                 :             :     int r;
     307                 :             : 
     308                 :             :     bf_assert(cgen && ns && hookopts);
     309                 :             : 
     310         [ +  - ]:           9 :     bf_info("attaching %s to %s", cgen->chain->name,
     311                 :             :             bf_hook_to_str(cgen->chain->hook));
     312                 :           9 :     bf_hookopts_dump(*hookopts, EMPTY_PREFIX);
     313                 :             : 
     314         [ +  - ]:           9 :     if (bf_opts_persist()) {
     315                 :           9 :         pindir_fd = _bf_cgen_get_chain_pindir_fd(cgen->chain->name);
     316         [ +  - ]:           9 :         if (pindir_fd < 0)
     317                 :             :             return pindir_fd;
     318                 :             :     }
     319                 :             : 
     320                 :           9 :     r = bf_ns_set(ns, bf_ctx_get_ns());
     321         [ -  + ]:           9 :     if (r)
     322         [ #  # ]:           0 :         return bf_err_r(r, "failed to switch to the client's namespaces");
     323                 :             : 
     324                 :           9 :     r = bf_program_attach(cgen->program, hookopts);
     325         [ +  + ]:           9 :     if (r < 0)
     326         [ +  - ]:           2 :         return bf_err_r(r, "failed to attach chain '%s'", cgen->chain->name);
     327                 :             : 
     328         [ -  + ]:           7 :     if (bf_ns_set(bf_ctx_get_ns(), ns))
     329         [ #  # ]:           0 :         bf_abort("failed to restore previous namespaces, aborting");
     330                 :             : 
     331         [ +  - ]:           7 :     if (bf_opts_persist()) {
     332                 :           7 :         r = bf_link_pin(cgen->program->link, pindir_fd);
     333         [ -  + ]:           7 :         if (r) {
     334                 :           0 :             bf_program_detach(cgen->program);
     335                 :           0 :             return r;
     336                 :             :         }
     337                 :             :     }
     338                 :             : 
     339                 :             :     return r;
     340                 :             : }
     341                 :             : 
     342                 :           3 : int bf_cgen_update(struct bf_cgen *cgen, struct bf_chain **new_chain)
     343                 :             : {
     344                 :           3 :     _free_bf_program_ struct bf_program *new_prog = NULL;
     345                 :           3 :     _cleanup_close_ int pindir_fd = -1;
     346                 :             :     struct bf_program *old_prog;
     347                 :             :     int r;
     348                 :             : 
     349                 :             :     bf_assert(cgen && new_chain);
     350                 :             : 
     351                 :           3 :     old_prog = cgen->program;
     352                 :             : 
     353         [ +  - ]:           3 :     if (bf_opts_persist()) {
     354                 :           3 :         pindir_fd = _bf_cgen_get_chain_pindir_fd((*new_chain)->name);
     355         [ +  - ]:           3 :         if (pindir_fd < 0)
     356                 :             :             return pindir_fd;
     357                 :             :     }
     358                 :             : 
     359                 :           3 :     r = bf_program_new(&new_prog, *new_chain);
     360         [ -  + ]:           3 :     if (r < 0)
     361         [ #  # ]:           0 :         return bf_err_r(r, "failed to create a new bf_program");
     362                 :             : 
     363                 :           3 :     r = bf_program_generate(new_prog);
     364         [ -  + ]:           3 :     if (r < 0) {
     365         [ #  # ]:           0 :         return bf_err_r(r,
     366                 :             :                         "failed to generate the bytecode for a new bf_program");
     367                 :             :     }
     368                 :             : 
     369                 :           3 :     r = bf_program_load(new_prog);
     370         [ -  + ]:           3 :     if (r)
     371         [ #  # ]:           0 :         return bf_err_r(r, "failed to load new program");
     372                 :             : 
     373         [ +  - ]:           3 :     if (bf_opts_persist())
     374                 :           3 :         bf_program_unpin(old_prog, pindir_fd);
     375                 :             : 
     376                 :           3 :     r = bf_link_update(old_prog->link, cgen->chain->hook,
     377                 :           3 :                        new_prog->runtime.prog_fd);
     378         [ -  + ]:           3 :     if (r) {
     379         [ #  # ]:           0 :         bf_err_r(r, "failed to update bf_link object with new program");
     380   [ #  #  #  # ]:           0 :         if (bf_opts_persist() && bf_program_pin(old_prog, pindir_fd) < 0)
     381         [ #  # ]:           0 :             bf_err("failed to repin old program, ignoring");
     382                 :           0 :         return r;
     383                 :             :     }
     384                 :             : 
     385                 :             :     // We updated the old link, we need to store it in the new program
     386                 :           3 :     bf_swap(new_prog->link, old_prog->link);
     387                 :             : 
     388         [ +  - ]:           3 :     if (bf_opts_persist()) {
     389                 :           3 :         r = bf_program_pin(new_prog, pindir_fd);
     390         [ -  + ]:           3 :         if (r)
     391         [ #  # ]:           0 :             bf_warn_r(r, "failed to pin new prog, ignoring");
     392                 :             :     }
     393                 :             : 
     394                 :           3 :     bf_swap(cgen->program, new_prog);
     395                 :             : 
     396                 :           3 :     bf_chain_free(&cgen->chain);
     397                 :           3 :     cgen->chain = TAKE_PTR(*new_chain);
     398                 :             : 
     399                 :           3 :     return 0;
     400                 :             : }
     401                 :             : 
     402                 :           0 : void bf_cgen_detach(struct bf_cgen *cgen)
     403                 :             : {
     404                 :             :     bf_assert(cgen);
     405                 :             : 
     406                 :           0 :     bf_program_detach(cgen->program);
     407                 :           0 : }
     408                 :             : 
     409                 :          36 : void bf_cgen_unload(struct bf_cgen *cgen)
     410                 :             : {
     411                 :          36 :     _cleanup_close_ int chain_fd = -1;
     412                 :             : 
     413                 :             :     bf_assert(cgen);
     414                 :             : 
     415                 :          36 :     chain_fd = _bf_cgen_get_chain_pindir_fd(cgen->chain->name);
     416         [ -  + ]:          36 :     if (chain_fd < 0) {
     417         [ #  # ]:           0 :         bf_err_r(chain_fd, "failed to open pin directory for '%s'",
     418                 :             :                  cgen->chain->name);
     419                 :             :         return;
     420                 :             :     }
     421                 :             : 
     422                 :             :     // The chain's pin directory will be removed in bf_cgen_free()
     423                 :          36 :     bf_program_unpin(cgen->program, chain_fd);
     424                 :          36 :     bf_program_unload(cgen->program);
     425                 :             : }
     426                 :             : 
     427                 :          13 : int bf_cgen_get_counters(const struct bf_cgen *cgen, bf_list *counters)
     428                 :             : {
     429                 :          13 :     bf_list _counters = bf_list_default_from(*counters);
     430                 :             :     int r;
     431                 :             : 
     432                 :             :     bf_assert(cgen && counters);
     433                 :             : 
     434                 :             :     /* Iterate over all the rules, then the policy counter (size(rules)) and
     435                 :             :      * the errors counters (sizeof(rules) + 1)*/
     436         [ +  + ]:          45 :     for (size_t i = 0; i < bf_list_size(&cgen->chain->rules) + 2; ++i) {
     437                 :           0 :         _free_bf_counter_ struct bf_counter *counter = NULL;
     438                 :          32 :         ssize_t idx = (ssize_t)i;
     439                 :             : 
     440         [ +  + ]:          32 :         if (i == bf_list_size(&cgen->chain->rules))
     441                 :             :             idx = BF_COUNTER_POLICY;
     442         [ +  + ]:          19 :         else if (i == bf_list_size(&cgen->chain->rules) + 1)
     443                 :             :             idx = BF_COUNTER_ERRORS;
     444                 :             : 
     445                 :          32 :         r = bf_counter_new(&counter, 0, 0);
     446         [ +  - ]:          32 :         if (r)
     447                 :             :             return r;
     448                 :             : 
     449                 :          32 :         r = bf_cgen_get_counter(cgen, idx, counter);
     450         [ +  - ]:          32 :         if (r)
     451                 :             :             return r;
     452                 :             : 
     453                 :          32 :         r = bf_list_add_tail(&_counters, counter);
     454         [ +  - ]:          32 :         if (r)
     455                 :             :             return r;
     456                 :             : 
     457                 :          32 :         TAKE_PTR(counter);
     458                 :             :     }
     459                 :             : 
     460                 :          13 :     *counters = bf_list_move(_counters);
     461                 :             : 
     462                 :          13 :     return 0;
     463                 :             : }
        

Generated by: LCOV version 2.0-1