LCOV - code coverage report
Current view: top level - common - recsel.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 201 276 72.8 %
Date: 2016-09-12 12:29:17 Functions: 5 8 62.5 %

          Line data    Source code
       1             : /* recsel.c - Record selection
       2             :  * Copyright (C) 2014, 2016 Werner Koch
       3             :  *
       4             :  * This file is part of GnuPG.
       5             :  *
       6             :  * This file is free software; you can redistribute it and/or modify
       7             :  * it under the terms of either
       8             :  *
       9             :  *   - the GNU Lesser General Public License as published by the Free
      10             :  *     Software Foundation; either version 3 of the License, or (at
      11             :  *     your option) any later version.
      12             :  *
      13             :  * or
      14             :  *
      15             :  *   - the GNU General Public License as published by the Free
      16             :  *     Software Foundation; either version 2 of the License, or (at
      17             :  *     your option) any later version.
      18             :  *
      19             :  * or both in parallel, as here.
      20             :  *
      21             :  * This file is distributed in the hope that it will be useful,
      22             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      23             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      24             :  * GNU General Public License for more details.
      25             :  *
      26             :  * You should have received a copy of the GNU General Public License
      27             :  * along with this program; if not, see <http://www.gnu.org/licenses/>.
      28             :  */
      29             : 
      30             : #include <config.h>
      31             : #include <stdio.h>
      32             : #include <stdlib.h>
      33             : #include <string.h>
      34             : #include <unistd.h>
      35             : #include <errno.h>
      36             : 
      37             : #include "util.h"
      38             : #include "recsel.h"
      39             : 
      40             : /* Select operators.  */
      41             : typedef enum
      42             :   {
      43             :     SELECT_SAME,
      44             :     SELECT_SUB,
      45             :     SELECT_NONEMPTY,
      46             :     SELECT_ISTRUE,
      47             :     SELECT_EQ, /* Numerically equal.  */
      48             :     SELECT_LE,
      49             :     SELECT_GE,
      50             :     SELECT_LT,
      51             :     SELECT_GT,
      52             :     SELECT_STRLE, /* String is less or equal.  */
      53             :     SELECT_STRGE,
      54             :     SELECT_STRLT,
      55             :     SELECT_STRGT
      56             :   } select_op_t;
      57             : 
      58             : 
      59             : /* Definition for a select expression.  */
      60             : struct recsel_expr_s
      61             : {
      62             :   recsel_expr_t next;
      63             :   select_op_t op;       /* Operation code.  */
      64             :   unsigned int not:1;   /* Negate operators. */
      65             :   unsigned int disjun:1;/* Start of a disjunction.  */
      66             :   unsigned int xcase:1; /* String match is case sensitive.  */
      67             :   const char *value;    /* (Points into NAME.)  */
      68             :   long numvalue;        /* strtol of VALUE.  */
      69             :   char name[1];         /* Name of the property.  */
      70             : };
      71             : 
      72             : 
      73             : /* Helper */
      74             : static inline gpg_error_t
      75           0 : my_error_from_syserror (void)
      76             : {
      77           0 :   return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
      78             : }
      79             : 
      80             : /* Helper */
      81             : static inline gpg_error_t
      82           0 : my_error (gpg_err_code_t ec)
      83             : {
      84           0 :   return gpg_err_make (default_errsource, ec);
      85             : }
      86             : 
      87             : 
      88             : /* This is a case-sensitive version of our memistr.  I wonder why no
      89             :  * standard function memstr exists but I better do not use the name
      90             :  * memstr to avoid future conflicts.
      91             :  *
      92             :  * FIXME: Move this to a stringhelp.c
      93             :  */
      94             : static const char *
      95           2 : my_memstr (const void *buffer, size_t buflen, const char *sub)
      96             : {
      97           2 :   const unsigned char *buf = buffer;
      98           2 :   const unsigned char *t = (const unsigned char *)buf;
      99           2 :   const unsigned char *s = (const unsigned char *)sub;
     100           2 :   size_t n = buflen;
     101             : 
     102          32 :   for ( ; n ; t++, n-- )
     103             :     {
     104          30 :       if (*t == *s)
     105             :         {
     106           0 :           for (buf = t++, buflen = n--, s++; n && *t ==*s; t++, s++, n--)
     107             :             ;
     108           0 :           if (!*s)
     109           0 :             return (const char*)buf;
     110           0 :           t = (const unsigned char *)buf;
     111           0 :           s = (const unsigned char *)sub ;
     112           0 :           n = buflen;
     113             :         }
     114             :     }
     115           2 :   return NULL;
     116             : }
     117             : 
     118             : 
     119             : /* Return a pointer to the next logical connection operator or NULL if
     120             :  * none.  */
     121             : static char *
     122          52 : find_next_lc (char *string)
     123             : {
     124             :   char *p1, *p2;
     125             : 
     126          52 :   p1 = strchr (string, '&');
     127          52 :   if (p1 && p1[1] != '&')
     128           0 :     p1 = NULL;
     129          52 :   p2 = strchr (string, '|');
     130          52 :   if (p2 && p2[1] != '|')
     131           0 :     p2 = NULL;
     132          52 :   if (p1 && !p2)
     133           1 :     return p1;
     134          51 :   if (!p1)
     135          49 :     return p2;
     136           2 :   return p1 < p2 ? p1 : p2;
     137             : }
     138             : 
     139             : 
     140             : /* Parse an expression.  The expression syntax is:
     141             :  *
     142             :  *   [<lc>] {{<flag>} PROPNAME <op> VALUE [<lc>]}
     143             :  *
     144             :  * A [] indicates an optional part, a {} a repetition.  PROPNAME and
     145             :  * VALUE may not be the empty string.  White space between the
     146             :  * elements is ignored.  Numerical values are computed as long int;
     147             :  * standard C notation applies.  <lc> is the logical connection
     148             :  * operator; either "&&" for a conjunction or "||" for a disjunction.
     149             :  * A conjunction is assumed at the begin of an expression and
     150             :  * conjunctions have higher precedence than disjunctions.  If VALUE
     151             :  * starts with one of the characters used in any <op> a space after
     152             :  * the <op> is required.  A VALUE is terminated by an <lc> unless the
     153             :  * "--" <flag> is used in which case the VALUE spans to the end of the
     154             :  * expression.  <op> may be any of
     155             :  *
     156             :  *   =~  Substring must match
     157             :  *   !~  Substring must not match
     158             :  *   =   The full string must match
     159             :  *   <>  The full string must not match
     160             :  *   ==  The numerical value must match
     161             :  *   !=  The numerical value must not match
     162             :  *   <=  The numerical value of the field must be LE than the value.
     163             :  *   <   The numerical value of the field must be LT than the value.
     164             :  *   >=  The numerical value of the field must be GT than the value.
     165             :  *   >=  The numerical value of the field must be GE than the value.
     166             :  *   -n  True if value is not empty (no VALUE parameter allowed).
     167             :  *   -z  True if value is empty (no VALUE parameter allowed).
     168             :  *   -t  Alias for "PROPNAME != 0" (no VALUE parameter allowed).
     169             :  *   -f  Alias for "PROPNAME == 0" (no VALUE parameter allowed).
     170             :  *
     171             :  * Values for <flag> must be space separated and any of:
     172             :  *
     173             :  *   --  VALUE spans to the end of the expression.
     174             :  *   -c  The string match in this part is done case-sensitive.
     175             :  *
     176             :  * For example four calls to recsel_parse_expr() with these values for
     177             :  * EXPR
     178             :  *
     179             :  *  "uid =~ Alfa"
     180             :  *  "&& uid !~ Test"
     181             :  *  "|| uid =~ Alpha"
     182             :  *  "uid !~ Test"
     183             :  *
     184             :  * or the equivalent expression
     185             :  *
     186             :  *  "uid =~ Alfa" && uid !~ Test" || uid =~ Alpha" && "uid !~ Test"
     187             :  *
     188             :  * are making a selector for records where the "uid" property contains
     189             :  * the strings "Alfa" or "Alpha" but not the String "test".
     190             :  *
     191             :  * The caller must pass the address of a selector variable to this
     192             :  * function and initialize the value of the function to NULL before
     193             :  * the first call.  recset_release needs to be called to free the
     194             :  * selector.
     195             :  */
     196             : gpg_error_t
     197          49 : recsel_parse_expr (recsel_expr_t *selector, const char *expression)
     198             : {
     199          49 :   recsel_expr_t se_head = NULL;
     200             :   recsel_expr_t se, se2;
     201             :   char *expr_buffer;
     202             :   char *expr;
     203             :   char *s0, *s;
     204          49 :   int toend = 0;
     205          49 :   int xcase = 0;
     206          49 :   int disjun = 0;
     207          49 :   char *next_lc = NULL;
     208             : 
     209          99 :   while (*expression == ' ' || *expression == '\t')
     210           1 :     expression++;
     211             : 
     212          49 :   expr_buffer = xtrystrdup (expression);
     213          49 :   if (!expr_buffer)
     214           0 :     return my_error_from_syserror ();
     215          49 :   expr = expr_buffer;
     216             : 
     217          49 :   if (*expr == '|' && expr[1] == '|')
     218             :     {
     219           1 :       disjun = 1;
     220           1 :       expr += 2;
     221             :     }
     222          48 :   else if (*expr == '&' && expr[1] == '&')
     223           1 :     expr += 2;
     224             : 
     225             :  next_term:
     226         109 :   while (*expr == ' ' || *expr == '\t')
     227           5 :     expr++;
     228             : 
     229         108 :   while (*expr == '-')
     230             :     {
     231           4 :       switch (*++expr)
     232             :         {
     233           0 :         case '-': toend = 1; break;
     234           4 :         case 'c': xcase = 1; break;
     235             :         default:
     236           0 :           log_error ("invalid flag '-%c' in expression\n", *expr);
     237           0 :           recsel_release (se_head);
     238           0 :           xfree (expr_buffer);
     239           0 :           return my_error (GPG_ERR_INV_FLAG);
     240             :         }
     241           4 :       expr++;
     242          12 :       while (*expr == ' ' || *expr == '\t')
     243           4 :         expr++;
     244             :     }
     245             : 
     246          52 :   next_lc = toend? NULL : find_next_lc (expr);
     247          52 :   if (next_lc)
     248           3 :     *next_lc = 0;  /* Terminate this term.  */
     249             : 
     250          52 :   se = xtrymalloc (sizeof *se + strlen (expr));
     251          52 :   if (!se)
     252           0 :     return my_error_from_syserror ();
     253          52 :   strcpy (se->name, expr);
     254          52 :   se->next = NULL;
     255          52 :   se->not = 0;
     256          52 :   se->disjun = disjun;
     257          52 :   se->xcase = xcase;
     258             : 
     259          52 :   if (!se_head)
     260          49 :     se_head = se;
     261             :   else
     262             :     {
     263           3 :       for (se2 = se_head; se2->next; se2 = se2->next)
     264             :         ;
     265           3 :       se2->next = se;
     266             :     }
     267             : 
     268             : 
     269          52 :   s = strpbrk (expr, "=<>!~-");
     270          52 :   if (!s || s == expr )
     271             :     {
     272           0 :       log_error ("no field name given in expression\n");
     273           0 :       recsel_release (se_head);
     274           0 :       xfree (expr_buffer);
     275           0 :       return my_error (GPG_ERR_NO_NAME);
     276             :     }
     277          52 :   s0 = s;
     278             : 
     279          52 :   if (!strncmp (s, "=~", 2))
     280             :     {
     281           9 :       se->op = SELECT_SUB;
     282           9 :       s += 2;
     283             :     }
     284          43 :   else if (!strncmp (s, "!~", 2))
     285             :     {
     286           7 :       se->op = SELECT_SUB;
     287           7 :       se->not = 1;
     288           7 :       s += 2;
     289             :     }
     290          36 :   else if (!strncmp (s, "<>", 2))
     291             :     {
     292           0 :       se->op = SELECT_SAME;
     293           0 :       se->not = 1;
     294           0 :       s += 2;
     295             :     }
     296          36 :   else if (!strncmp (s, "==", 2))
     297             :     {
     298           1 :       se->op = SELECT_EQ;
     299           1 :       s += 2;
     300             :     }
     301          35 :   else if (!strncmp (s, "!=", 2))
     302             :     {
     303           1 :       se->op = SELECT_EQ;
     304           1 :       se->not = 1;
     305           1 :       s += 2;
     306             :     }
     307          34 :   else if (!strncmp (s, "<=", 2))
     308             :     {
     309           1 :       se->op = SELECT_LE;
     310           1 :       s += 2;
     311             :     }
     312          33 :   else if (!strncmp (s, ">=", 2))
     313             :     {
     314           1 :       se->op = SELECT_GE;
     315           1 :       s += 2;
     316             :     }
     317          32 :   else if (!strncmp (s, "<", 1))
     318             :     {
     319           2 :       se->op = SELECT_LT;
     320           2 :       s += 1;
     321             :     }
     322          30 :   else if (!strncmp (s, ">", 1))
     323             :     {
     324           2 :       se->op = SELECT_GT;
     325           2 :       s += 1;
     326             :     }
     327          28 :   else if (!strncmp (s, "=", 1))
     328             :     {
     329           3 :       se->op = SELECT_SAME;
     330           3 :       s += 1;
     331             :     }
     332          25 :   else if (!strncmp (s, "-z", 2))
     333             :     {
     334           4 :       se->op = SELECT_NONEMPTY;
     335           4 :       se->not = 1;
     336           4 :       s += 2;
     337             :     }
     338          21 :   else if (!strncmp (s, "-n", 2))
     339             :     {
     340           4 :       se->op = SELECT_NONEMPTY;
     341           4 :       s += 2;
     342             :     }
     343          17 :   else if (!strncmp (s, "-f", 2))
     344             :     {
     345           5 :       se->op = SELECT_ISTRUE;
     346           5 :       se->not = 1;
     347           5 :       s += 2;
     348             :     }
     349          12 :   else if (!strncmp (s, "-t", 2))
     350             :     {
     351           5 :       se->op = SELECT_ISTRUE;
     352           5 :       s += 2;
     353             :     }
     354           7 :   else if (!strncmp (s, "-le", 3))
     355             :     {
     356           1 :       se->op = SELECT_STRLE;
     357           1 :       s += 3;
     358             :     }
     359           6 :   else if (!strncmp (s, "-ge", 3))
     360             :     {
     361           1 :       se->op = SELECT_STRGE;
     362           1 :       s += 3;
     363             :     }
     364           5 :   else if (!strncmp (s, "-lt", 3))
     365             :     {
     366           3 :       se->op = SELECT_STRLT;
     367           3 :       s += 3;
     368             :     }
     369           2 :   else if (!strncmp (s, "-gt", 3))
     370             :     {
     371           2 :       se->op = SELECT_STRGT;
     372           2 :       s += 3;
     373             :     }
     374             :   else
     375             :     {
     376           0 :       log_error ("invalid operator in expression\n");
     377           0 :       recsel_release (se_head);
     378           0 :       xfree (expr_buffer);
     379           0 :       return my_error (GPG_ERR_INV_OP);
     380             :     }
     381             : 
     382             :   /* We require that a space is used if the value starts with any of
     383             :      the operator characters.  */
     384          52 :   if (se->op == SELECT_NONEMPTY || se->op == SELECT_ISTRUE)
     385             :     ;
     386          34 :   else if (strchr ("=<>!~", *s))
     387             :     {
     388           0 :       log_error ("invalid operator in expression\n");
     389           0 :       recsel_release (se_head);
     390           0 :       xfree (expr_buffer);
     391           0 :       return my_error (GPG_ERR_INV_OP);
     392             :     }
     393             : 
     394         144 :   while (*s == ' ' || *s == '\t')
     395          40 :     s++;
     396             : 
     397          52 :   if (se->op == SELECT_NONEMPTY || se->op == SELECT_ISTRUE)
     398             :     {
     399          36 :       if (*s)
     400             :         {
     401           0 :           log_error ("value given for -n or -z\n");
     402           0 :           recsel_release (se_head);
     403           0 :           xfree (expr_buffer);
     404           0 :           return my_error (GPG_ERR_SYNTAX);
     405             :         }
     406             :     }
     407             :   else
     408             :     {
     409          34 :       if (!*s)
     410             :         {
     411           0 :           log_error ("no value given in expression\n");
     412           0 :           recsel_release (se_head);
     413           0 :           xfree (expr_buffer);
     414           0 :           return my_error (GPG_ERR_MISSING_VALUE);
     415             :         }
     416             :     }
     417             : 
     418          52 :   se->name[s0 - expr] = 0;
     419          52 :   trim_spaces (se->name);
     420          52 :   if (!se->name[0])
     421             :     {
     422           0 :       log_error ("no field name given in expression\n");
     423           0 :       recsel_release (se_head);
     424           0 :       xfree (expr_buffer);
     425           0 :       return my_error (GPG_ERR_NO_NAME);
     426             :     }
     427             : 
     428          52 :   trim_spaces (se->name + (s - expr));
     429          52 :   se->value = se->name + (s - expr);
     430          52 :   if (!se->value[0] && !(se->op == SELECT_NONEMPTY || se->op == SELECT_ISTRUE))
     431             :     {
     432           0 :       log_error ("no value given in expression\n");
     433           0 :       recsel_release (se_head);
     434           0 :       xfree (expr_buffer);
     435           0 :       return my_error (GPG_ERR_MISSING_VALUE);
     436             :     }
     437             : 
     438          52 :   se->numvalue = strtol (se->value, NULL, 0);
     439             : 
     440          52 :   if (next_lc)
     441             :     {
     442           3 :       disjun = next_lc[1] == '|';
     443           3 :       expr = next_lc + 2;
     444           3 :       goto next_term;
     445             :     }
     446             : 
     447             :   /* Read:y Append to passes last selector.  */
     448          49 :   if (!*selector)
     449          46 :     *selector = se_head;
     450             :   else
     451             :     {
     452           3 :       for (se2 = *selector; se2->next; se2 = se2->next)
     453             :         ;
     454           3 :       se2->next = se_head;
     455             :     }
     456             : 
     457          49 :   xfree (expr_buffer);
     458          49 :   return 0;
     459             : }
     460             : 
     461             : 
     462             : void
     463          46 : recsel_release (recsel_expr_t a)
     464             : {
     465         144 :   while (a)
     466             :     {
     467          52 :       recsel_expr_t tmp = a->next;
     468          52 :       xfree (a);
     469          52 :       a = tmp;
     470             :     }
     471          46 : }
     472             : 
     473             : 
     474             : void
     475           0 : recsel_dump (recsel_expr_t selector)
     476             : {
     477             :   recsel_expr_t se;
     478             : 
     479           0 :   log_debug ("--- Begin selectors ---\n");
     480           0 :   for (se = selector; se; se = se->next)
     481             :     {
     482           0 :       log_debug ("%s %s %s %s '%s'\n",
     483           0 :                  se==selector? "  ": (se->disjun? "||":"&&"),
     484           0 :                  se->xcase?  "-c":"  ",
     485           0 :                  se->name,
     486           0 :                  se->op == SELECT_SAME?    (se->not? "<>":"= "):
     487           0 :                  se->op == SELECT_SUB?     (se->not? "!~":"=~"):
     488           0 :                  se->op == SELECT_NONEMPTY?(se->not? "-z":"-n"):
     489           0 :                  se->op == SELECT_ISTRUE?  (se->not? "-f":"-t"):
     490           0 :                  se->op == SELECT_EQ?      (se->not? "!=":"=="):
     491           0 :                  se->op == SELECT_LT?      "< ":
     492           0 :                  se->op == SELECT_LE?      "<=":
     493           0 :                  se->op == SELECT_GT?      "> ":
     494           0 :                  se->op == SELECT_GE?      ">=":
     495           0 :                  se->op == SELECT_STRLT?   "-lt":
     496           0 :                  se->op == SELECT_STRLE?   "-le":
     497           0 :                  se->op == SELECT_STRGT?   "-gt":
     498           0 :                  se->op == SELECT_STRGE?   "-ge":
     499             :                  /**/                      "[oops]",
     500             :                  se->value);
     501             :     }
     502           0 :   log_debug ("--- End selectors ---\n");
     503           0 : }
     504             : 
     505             : 
     506             : /* Return true if the record RECORD has been selected.  The GETVAL
     507             :  * function is called with COOKIE and the NAME of a property used in
     508             :  * the expression.  */
     509             : int
     510          66 : recsel_select (recsel_expr_t selector,
     511             :                const char *(*getval)(void *cookie, const char *propname),
     512             :                void *cookie)
     513             : {
     514             :   recsel_expr_t se;
     515             :   const char *value;
     516             :   size_t selen, valuelen;
     517             :   long numvalue;
     518          66 :   int result = 1;
     519             : 
     520          66 :   se = selector;
     521         188 :   while (se)
     522             :     {
     523          94 :       value = getval? getval (cookie, se->name) : NULL;
     524          94 :       if (!value)
     525          13 :         value = "";
     526             : 
     527          94 :       if (!*value)
     528             :         {
     529             :           /* Field is empty.  */
     530          17 :           result = 0;
     531             :         }
     532             :       else /* Field has a value.  */
     533             :         {
     534          77 :           valuelen = strlen (value);
     535          77 :           numvalue = strtol (value, NULL, 0);
     536          77 :           selen = strlen (se->value);
     537             : 
     538          77 :           switch (se->op)
     539             :             {
     540             :             case SELECT_SAME:
     541           3 :               if (se->xcase)
     542           1 :                 result = (valuelen==selen && !memcmp (value,se->value,selen));
     543             :               else
     544           2 :                 result = (valuelen==selen && !memicmp (value,se->value,selen));
     545           3 :               break;
     546             :             case SELECT_SUB:
     547          46 :               if (se->xcase)
     548           2 :                 result = !!my_memstr (value, valuelen, se->value);
     549             :               else
     550          44 :                 result = !!memistr (value, valuelen, se->value);
     551          46 :               break;
     552             :             case SELECT_NONEMPTY:
     553           6 :               result = !!valuelen;
     554           6 :               break;
     555             :             case SELECT_ISTRUE:
     556           7 :               result = !!numvalue;
     557           7 :               break;
     558             :             case SELECT_EQ:
     559           2 :               result = (numvalue == se->numvalue);
     560           2 :               break;
     561             :             case SELECT_GT:
     562           2 :               result = (numvalue > se->numvalue);
     563           2 :               break;
     564             :             case SELECT_GE:
     565           1 :               result = (numvalue >= se->numvalue);
     566           1 :               break;
     567             :             case SELECT_LT:
     568           2 :               result = (numvalue < se->numvalue);
     569           2 :               break;
     570             :             case SELECT_LE:
     571           1 :               result = (numvalue <= se->numvalue);
     572           1 :               break;
     573             :             case SELECT_STRGT:
     574           2 :               if (se->xcase)
     575           0 :                 result = strcmp (value, se->value) > 0;
     576             :               else
     577           2 :                 result = strcasecmp (value, se->value) > 0;
     578           2 :               break;
     579             :             case SELECT_STRGE:
     580           1 :               if (se->xcase)
     581           0 :                 result = strcmp (value, se->value) >= 0;
     582             :               else
     583           1 :                 result = strcasecmp (value, se->value) >= 0;
     584           1 :               break;
     585             :             case SELECT_STRLT:
     586           3 :               if (se->xcase)
     587           1 :                 result = strcmp (value, se->value) < 0;
     588             :               else
     589           2 :                 result = strcasecmp (value, se->value) < 0;
     590           3 :               break;
     591             :             case SELECT_STRLE:
     592           1 :               if (se->xcase)
     593           0 :                 result = strcmp (value, se->value) <= 0;
     594             :               else
     595           1 :                 result = strcasecmp (value, se->value) <= 0;
     596           1 :               break;
     597             :             }
     598             :         }
     599             : 
     600          94 :       if (se->not)
     601          29 :         result = !result;
     602             : 
     603          94 :       if (result)
     604             :         {
     605             :           /* This expression evaluated to true.  See wether there are
     606             :              remaining expressions in this conjunction.  */
     607          54 :           if (!se->next || se->next->disjun)
     608             :             break; /* All expressions are true.  Return True.  */
     609          16 :           se = se->next;  /* Test the next.  */
     610             :         }
     611             :       else
     612             :         {
     613             :           /* This expression evaluated to false and thus the
     614             :            * conjuction evaluates to false.  We skip over the
     615             :            * remaining expressions of this conjunction and continue
     616             :            * with the next disjunction if any.  */
     617             :           do
     618          58 :             se = se->next;
     619          58 :           while (se && !se->disjun);
     620             :         }
     621             :     }
     622             : 
     623          66 :   return result;
     624             : }

Generated by: LCOV version 1.11