LCOV - code coverage report
Current view: top level - monetdb5/mal - mal_scenario.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 91 162 56.2 %
Date: 2021-09-14 22:17:06 Functions: 9 15 60.0 %

          Line data    Source code
       1             : /*
       2             :  * This Source Code Form is subject to the terms of the Mozilla Public
       3             :  * License, v. 2.0.  If a copy of the MPL was not distributed with this
       4             :  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
       5             :  *
       6             :  * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
       7             :  */
       8             : 
       9             : /*
      10             :  * (author) M. Kersten
      11             :  * @+ Session Scenarios
      12             :  * In MonetDB multiple languages, optimizers, and execution engines can
      13             :  * be combined at run time to satisfy a wide user-community.
      14             :  * Such an assemblage of components is called a @emph{scenario}
      15             :  * and consists of a @emph{reader}, @emph{parser}, @emph{optimizer},
      16             :  * @emph{tactic scheduler} and @emph{engine}. These hooks allow
      17             :  * for both linked-in and external components.
      18             :  *
      19             :  * The languages supported are SQL, the Monet Assembly Language (MAL), and profiler.
      20             :  * The default scenario handles MAL instructions, which is used
      21             :  * to illustrate the behavior of the scenario steps.
      22             :  *
      23             :  * The MAL reader component handles interaction with
      24             :  * a front-end to obtain a string for subsequent compilation and
      25             :  * execution. The reader uses the common stream package to read
      26             :  * data in large chunks, if possible. In interactive mode the lines
      27             :  * are processed one at a time.
      28             :  *
      29             :  * The MAL parser component turns the string into
      30             :  * an internal representation of the MAL program.
      31             :  * During this phase semantic checks are performed, such that
      32             :  * we end up with a type correct program.
      33             :  *
      34             :  * The code block is subsequently sent to an MAL optimizer.
      35             :  * In the default case the program is left untouched. For other languages,
      36             :  * the optimizer deploys language specific code transformations,
      37             :  * e.g., foreign-key optimizations in joins and remote query execution.
      38             :  * All optimization information is statically derived from the
      39             :  * code blocks and possible catalogues maintained for the query language
      40             :  * at hand. Optimizers leave advice and their findings in properties
      41             :  * in the symbol table, see @ref{Property Management}.
      42             :  *
      43             :  * Once the program has thus been refined, the
      44             :  * MAL scheduler prepares for execution using tactical optimizations.
      45             :  * For example, it may parallelize the code, generate an ad-hoc
      46             :  * user-defined function, or prepare for efficient replication management.
      47             :  * In the default case, the program is handed over to the MAL interpreter
      48             :  * without any further modification.
      49             :  *
      50             :  * The final stage is to choose an execution paradigm,
      51             :  * i.e. interpretative (default), compilation of an ad-hoc user
      52             :  * defined function, dataflow driven interpretation,
      53             :  * or vectorized pipe-line execution by a dedicated engine.
      54             :  *
      55             :  * A failure encountered in any of the steps terminates the scenario
      56             :  * cycle. It returns to the user for a new command.
      57             :  *
      58             :  * @+ Scenario management
      59             :  * Scenarios are captured in modules; they can be dynamically loaded
      60             :  * and remain active until the system is brought to a halt.
      61             :  * The first time a scenario @sc{xyz} is used, the system looks for a scenario
      62             :  * initialization routine @sc{xyzinitSystem()} and executes it.
      63             :  * It is typically used to prepare the server for language specific interactions.
      64             :  * Thereafter its components are set to those required by
      65             :  * the scenario and the client initialization takes place.
      66             :  *
      67             :  * When the last user interested in a particular scenario leaves the
      68             :  * scene, we activate its finalization routine calling @sc{xyzexitSystem()}.
      69             :  * It typically perform cleanup, backup and monitoring functions.
      70             :  *
      71             :  * A scenario is interpreted in a strictly linear fashion,
      72             :  * i.e. performing a symbolic optimization before scheduling decisions
      73             :  * are taken.
      74             :  * The routines associated with each state in
      75             :  * the scenario may patch the code so as to assure that subsequent
      76             :  * execution can use a different scenario, e.g., to handle dynamic
      77             :  * code fragments.
      78             :  *
      79             :  * The state of execution is maintained in the scenario record for
      80             :  * each individual client. Sharing this information between clients
      81             :  * should be dealt with in the implementation of the scenario managers.
      82             :  * Upon need, the client can postpone a session scenario by
      83             :  * pushing a new one(language, optimize, tactic,
      84             :  * processor). Propagation of the state information is
      85             :  * encapsulated a scenario2scenario() call. Not all transformations
      86             :  * may be legal.
      87             :  *
      88             :  * @+ Scenario administration
      89             :  * Administration of scenarios follows the access rules
      90             :  * defined for code modules in general.
      91             :  *
      92             :  */
      93             : #include "monetdb_config.h"
      94             : #include "mal_scenario.h"
      95             : #include "mal_client.h"
      96             : #include "mal_authorize.h"
      97             : #include "mal_exception.h"
      98             : #include "mal_profiler.h"
      99             : #include "mal_private.h"
     100             : #include "mal_session.h"
     101             : 
     102             : #ifdef HAVE_SYS_TIMES_H
     103             : # include <sys/times.h>
     104             : #endif
     105             : 
     106             : static struct SCENARIO scenarioRec[MAXSCEN] = {
     107             :         {"mal", "mal",
     108             :          0, 0,                  /* hardwired MALinit*/
     109             :          0, 0,                  /* implicit */
     110             :          "MALinitClient", (MALfcn) &MALinitClient,
     111             :          "MALexitClient", (MALfcn) &MALexitClient,
     112             :          "MALreader", (MALfcn) &MALreader,
     113             :          "MALparser", (MALfcn) &MALparser,
     114             :          "MALoptimizer", 0,
     115             :          0, 0,
     116             :          "MALengine", (MALfcn) &MALengine,
     117             :          "MALcallback", (MALfcn) &MALcallback },
     118             :         {0, 0,          /* name */
     119             :          0, 0,          /* init */
     120             :          0, 0,          /* exit */
     121             :          0, 0,          /* initClient */
     122             :          0, 0,          /* exitClient */
     123             :          0, 0,          /* reader */
     124             :          0, 0,          /* parser */
     125             :          0, 0,          /* optimizer */
     126             :          0, 0,          /* scheduler */
     127             :          0, 0,          /* callback */
     128             :          0, 0           /* engine */
     129             :          }
     130             : };
     131             : 
     132             : static str fillScenario(Client c, Scenario scen);
     133             : static MT_Lock scenarioLock = MT_LOCK_INITIALIZER(scenarioLock);
     134             : 
     135             : 
     136             : /*
     137             :  * Currently each user can define a new scenario, provided we have a free slot.
     138             :  * Scenarios not hardwired can always be dropped.
     139             :  */
     140             : Scenario
     141         532 : getFreeScenario(void)
     142             : {
     143             :         int i;
     144             :         Scenario scen = NULL;
     145             : 
     146         532 :         MT_lock_set(&scenarioLock);
     147        1330 :         for (i = 0; i < MAXSCEN && scenarioRec[i].name; i++)
     148             :                 ;
     149         532 :         if (i < MAXSCEN)
     150         532 :                 scen = scenarioRec + i;
     151         532 :         MT_lock_unset(&scenarioLock);
     152             : 
     153         532 :         return scen;
     154             : }
     155             : 
     156             : str
     157        2420 : defaultScenario(Client c)
     158             : {
     159        2420 :         return fillScenario(c, scenarioRec);
     160             : }
     161             : 
     162             : /*
     163             :  * The Monet debugger provides an option to inspect the scenarios currently
     164             :  * defined.
     165             :  *
     166             :  */
     167             : static void
     168           0 : print_scenarioCommand(stream *f, str cmd, MALfcn funcptr)
     169             : {
     170           0 :     if (cmd)
     171           0 :         mnstr_printf(f," \"%s%s\",", cmd, (funcptr?"":"?"));
     172             :     else
     173           0 :         mnstr_printf(f," nil,");
     174           0 : }
     175             : 
     176             : void
     177           0 : showScenario(stream *f, Scenario scen)
     178             : {
     179           0 :         mnstr_printf(f, "[ \"%s\",", scen->name);
     180           0 :         print_scenarioCommand(f, scen->initSystem, scen->initSystemCmd);
     181           0 :         print_scenarioCommand(f, scen->exitSystem, scen->exitSystemCmd);
     182           0 :         print_scenarioCommand(f, scen->initClient, scen->initClientCmd);
     183           0 :         print_scenarioCommand(f, scen->exitClient, scen->exitClientCmd);
     184           0 :         print_scenarioCommand(f, scen->parser, scen->parserCmd);
     185           0 :         print_scenarioCommand(f, scen->optimizer, scen->optimizerCmd);
     186           0 :         print_scenarioCommand(f, scen->tactics, scen->tacticsCmd);
     187           0 :         print_scenarioCommand(f, scen->callback, scen->callbackCmd);
     188           0 :         print_scenarioCommand(f, scen->engine, scen->engineCmd);
     189           0 :         mnstr_printf(f, "]\n");
     190           0 : }
     191             : 
     192             : Scenario
     193        5087 : findScenario(str nme)
     194             : {
     195             :         int i;
     196             :         Scenario scen = scenarioRec;
     197             : 
     198        9891 :         for (i = 0; i < MAXSCEN; i++, scen++)
     199        9891 :                 if (scen->name && strcmp(scen->name, nme) == 0)
     200        5087 :                         return scen;
     201             :         return NULL;
     202             : }
     203             : 
     204             : /*
     205             :  * Functions may become resolved only after the corresponding module
     206             :  * has been loaded. This should be announced as part of the module
     207             :  * prelude code.
     208             :  * Beware that after the update, we also have to adjust the client records.
     209             :  * They contain a copy of the functions addresses.
     210             :  */
     211             : void
     212         266 : updateScenario(str nme, str fnme, MALfcn fcn)
     213             : {
     214             :         int phase = -1;
     215         266 :         Scenario scen = findScenario(nme);
     216             : 
     217         266 :         if (scen == NULL)
     218             :                 return;
     219         266 :         if (scen->initSystem && strcmp(scen->initSystem, fnme) == 0)
     220           0 :                 scen->initSystemCmd = fcn;
     221         266 :         if (scen->exitSystem && strcmp(scen->exitSystem, fnme) == 0)
     222           0 :                 scen->exitSystemCmd = fcn;
     223         266 :         if (scen->initClient && strcmp(scen->initClient, fnme) == 0) {
     224           0 :                 scen->initClientCmd = fcn;
     225             :                 phase = MAL_SCENARIO_INITCLIENT;
     226             :         }
     227         266 :         if (scen->exitClient && strcmp(scen->exitClient, fnme) == 0) {
     228           0 :                 scen->exitClientCmd = fcn;
     229             :                 phase = MAL_SCENARIO_EXITCLIENT;
     230             :         }
     231         266 :         if (scen->reader && strcmp(scen->reader, fnme) == 0) {
     232           0 :                 scen->readerCmd = fcn;
     233             :                 phase = MAL_SCENARIO_READER;
     234             :         }
     235         266 :         if (scen->parser && strcmp(scen->parser, fnme) == 0) {
     236           0 :                 scen->parserCmd = fcn;
     237             :                 phase = MAL_SCENARIO_PARSER;
     238             :         }
     239         266 :         if (scen->optimizer && strcmp(scen->optimizer, fnme) == 0) {
     240         266 :                 scen->optimizerCmd = fcn;
     241             :                 phase = MAL_SCENARIO_OPTIMIZE;
     242             :         }
     243         266 :         if (scen->tactics && strcmp(scen->tactics, fnme) == 0) {
     244           0 :                 scen->tacticsCmd = fcn;
     245             :                 phase = MAL_SCENARIO_SCHEDULER;
     246             :         }
     247         266 :         if (scen->callback && strcmp(scen->callback, fnme) == 0) {
     248           0 :                 scen->callbackCmd = fcn;
     249             :                 phase = MAL_SCENARIO_CALLBACK;
     250             :         }
     251         266 :         if (scen->engine && strcmp(scen->engine, fnme) == 0) {
     252           0 :                 scen->engineCmd = fcn;
     253             :                 phase = MAL_SCENARIO_ENGINE;
     254             :         }
     255         266 :         if (phase != -1) {
     256             :                 Client c1;
     257             : 
     258       17230 :                 for (c1 = mal_clients; c1 < mal_clients + MAL_MAXCLIENTS; c1++) {
     259       16964 :                         if (c1->scenario &&
     260         266 :                             strcmp(c1->scenario, scen->name) == 0)
     261         266 :                                 c1->phase[phase] = fcn;
     262       16964 :                         if (c1->oldscenario &&
     263           0 :                             strcmp(c1->oldscenario, scen->name) == 0)
     264           0 :                                 c1->oldphase[phase] = fcn;
     265             :                 }
     266             :         }
     267             : }
     268             : 
     269             : void
     270           0 : showScenarioByName(stream *f, str nme)
     271             : {
     272           0 :         Scenario scen = findScenario(nme);
     273             : 
     274           0 :         if (scen)
     275           0 :                 showScenario(f, scen);
     276           0 : }
     277             : 
     278             : void
     279           0 : showAllScenarios(stream *f)
     280             : {
     281             :         int i;
     282             :         Scenario scen = scenarioRec;
     283             : 
     284           0 :         for (i = 0; i < MAXSCEN && scen->name; i++, scen++)
     285           0 :                 showScenario(f, scen);
     286           0 : }
     287             : 
     288           0 : str getScenarioLanguage(Client c){
     289           0 :         Scenario scen= findScenario(c->scenario);
     290           0 :         if( scen) return scen->language;
     291             :         return "mal";
     292             : }
     293             : /*
     294             :  * Changing the scenario for a particular client invalidates the
     295             :  * state maintained for the previous scenario. The old scenario is
     296             :  * retained in the client record to facilitate propagation of
     297             :  * state information, or to simply switch back to the previous one.
     298             :  * Before we initialize a scenario the client scenario is reset to
     299             :  * the MAL scenario. This implies that all scenarios are initialized
     300             :  * using the same scenario. After the scenario initialization file
     301             :  * has been processed, the scenario phases are replaced with the
     302             :  * proper ones.
     303             :  *
     304             :  * All client records should be initialized with a default
     305             :  * scenario, i.e. the first described in the scenario table.
     306             :  */
     307             : static str
     308        6713 : fillScenario(Client c, Scenario scen)
     309             : {
     310        6713 :         c->scenario = scen->name;
     311             : 
     312        6713 :         c->phase[MAL_SCENARIO_READER] = scen->readerCmd;
     313        6713 :         c->phase[MAL_SCENARIO_PARSER] = scen->parserCmd;
     314        6713 :         c->phase[MAL_SCENARIO_OPTIMIZE] = scen->optimizerCmd;
     315        6713 :         c->phase[MAL_SCENARIO_SCHEDULER] = scen->tacticsCmd;
     316        6713 :         c->phase[MAL_SCENARIO_CALLBACK] = scen->callbackCmd;
     317        6713 :         c->phase[MAL_SCENARIO_ENGINE] = scen->engineCmd;
     318        6713 :         c->phase[MAL_SCENARIO_INITCLIENT] = scen->initClientCmd;
     319        6713 :         c->phase[MAL_SCENARIO_EXITCLIENT] = scen->exitClientCmd;
     320        6713 :         c->state[MAL_SCENARIO_READER] = 0;
     321        6713 :         c->state[MAL_SCENARIO_PARSER] = 0;
     322        6713 :         c->state[MAL_SCENARIO_OPTIMIZE] = 0;
     323        6713 :         c->state[MAL_SCENARIO_SCHEDULER] = 0;
     324        6713 :         c->state[MAL_SCENARIO_ENGINE] = 0;
     325        6713 :         c->state[MAL_SCENARIO_INITCLIENT] = 0;
     326        6713 :         c->state[MAL_SCENARIO_EXITCLIENT] = 0;
     327        6713 :         return(MAL_SUCCEED);
     328             : }
     329             : 
     330             : /*
     331             :  * Setting a new scenario calls for saving the previous state
     332             :  * and execution of the initClientScenario routine.
     333             :  */
     334             : str
     335        4293 : setScenario(Client c, str nme)
     336             : {
     337             :         int i;
     338             :         str msg;
     339             :         Scenario scen;
     340             : 
     341        4293 :         scen = findScenario(nme);
     342        4293 :         if (scen == NULL)
     343           0 :                 throw(MAL, "setScenario", SCENARIO_NOT_FOUND " '%s'", nme);
     344             : 
     345        4293 :         if (c->scenario) {
     346           0 :                 c->oldscenario = c->scenario;
     347           0 :                 for (i = 0; i < SCENARIO_PROPERTIES; i++) {
     348           0 :                         c->oldstate[i] = c->state[i];
     349           0 :                         c->oldphase[i] = c->phase[i];
     350             :                 }
     351             :         }
     352       38637 :         for (i = 0; i < SCENARIO_PROPERTIES; i++)
     353       34344 :                 c->state[i] = 0;
     354             : 
     355        4293 :         msg = fillScenario(c, scen);
     356        4293 :         if (msg) {
     357             :                 /* error occurred, reset the scenario , assume default always works */
     358           0 :                 c->scenario = c->oldscenario;
     359           0 :                 for (i = 0; i < SCENARIO_PROPERTIES; i++) {
     360           0 :                         c->state[i] = c->oldstate[i];
     361           0 :                         c->phase[i] = c->oldphase[i];
     362           0 :                         c->oldstate[i] = NULL;
     363           0 :                         c->oldphase[i] = NULL;
     364             :                 }
     365           0 :                 c->oldscenario = NULL;
     366           0 :                 return msg;
     367             :         }
     368             :         return MAL_SUCCEED;
     369             : }
     370             : 
     371             : /*
     372             :  * After finishing a session in a scenario, we should reset the
     373             :  * state of the previous one. But also call the exitClient
     374             :  * to garbage collect any scenario specific structures.
     375             :  */
     376             : #if 0
     377             : str
     378             : getCurrentScenario(Client c)
     379             : {
     380             :         return c->scenario;
     381             : }
     382             : #endif
     383             : 
     384             : void
     385           0 : resetScenario(Client c)
     386             : {
     387             :         int i;
     388             :         Scenario scen = scenarioRec;
     389             : 
     390           0 :         if (c->scenario == 0)
     391             :                 return;
     392             : 
     393           0 :         scen = findScenario(c->scenario);
     394           0 :         if (scen != NULL && scen->exitClientCmd)
     395           0 :                 (*scen->exitClientCmd) (c);
     396             : 
     397           0 :         c->scenario = c->oldscenario;
     398           0 :         for (i = 0; i < SCENARIO_PROPERTIES; i++) {
     399           0 :                 c->state[i] = c->oldstate[i];
     400           0 :                 c->phase[i] = c->oldphase[i];
     401             :         }
     402           0 :         c->oldscenario = 0;
     403             : }
     404             : 
     405             : /*
     406             :  * The building blocks of scenarios are routines obeying a strict
     407             :  * name signature. They require exclusive access to the client
     408             :  * record. Any specific information should be accessible from
     409             :  * there, e.g., access to a scenario specific state descriptor.
     410             :  * The client scenario initialization and finalization brackets
     411             :  * are  @sc{xyzinitClient()} and @sc{xyzexitClient()}.
     412             :  *
     413             :  * The @sc{xyzparser(Client c)} contains the parser for language XYZ
     414             :  * and should fill the MAL program block associated with the client record.
     415             :  * The latter may have been initialized with variables.
     416             :  * Each language parser may require a catalog with information
     417             :  * on the translation of language specific datastructures into their BAT
     418             :  * equivalent.
     419             :  *
     420             :  * The @sc{xyzoptimizer(Client c)} contains language specific optimizations
     421             :  * using the MAL intermediate code as a starting point.
     422             :  *
     423             :  * The @sc{xyztactics(Client c)} synchronizes the program execution with the
     424             :  * state of the machine, e.g., claiming resources, the history of the client
     425             :  * or alignment of the request with concurrent actions (e.g., transaction
     426             :  * coordination).
     427             :  *
     428             :  * The @sc{xyzengine(Client c)} contains the applicable back-end engine.
     429             :  * The default is the MAL interpreter, which provides good balance
     430             :  * between speed and ability to analysis its behavior.
     431             :  *
     432             :  */
     433             : static const char *phases[] = {
     434             :         [MAL_SCENARIO_CALLBACK] = "scenario callback",
     435             :         [MAL_SCENARIO_ENGINE] = "scenario engine",
     436             :         [MAL_SCENARIO_EXITCLIENT] = "scenario exitclient",
     437             :         [MAL_SCENARIO_INITCLIENT] = "scenario initclient",
     438             :         [MAL_SCENARIO_OPTIMIZE] = "scenario optimize",
     439             :         [MAL_SCENARIO_PARSER] = "scenario parser",
     440             :         [MAL_SCENARIO_READER] = "scenario reader",
     441             :         [MAL_SCENARIO_SCHEDULER] = "scenario scheduler",
     442             : };
     443             : static str
     444      847145 : runPhase(Client c, int phase)
     445             : {
     446             :         str msg = MAL_SUCCEED;
     447      847145 :         if (c->phase[phase]) {
     448      523050 :                 MT_thread_setworking(phases[phase]);
     449      523052 :             return msg = (str) (*c->phase[phase])(c);
     450             :         }
     451             :         return msg;
     452             : }
     453             : 
     454             : /*
     455             :  * Access control enforcement. Except for the server owner
     456             :  * running a scenario should be explicitly permitted.
     457             :  */
     458             : static str
     459        4303 : runScenarioBody(Client c, int once)
     460             : {
     461             :         str msg = MAL_SUCCEED;
     462             : 
     463      178018 :         while (c->mode > FINISHCLIENT && !GDKexiting()) {
     464             :                 // be aware that a MAL call  may initialize a different scenario
     465      173725 :                 if ( !c->state[0] && (msg = runPhase(c, MAL_SCENARIO_INITCLIENT)) )
     466           1 :                         goto wrapup;
     467      173724 :                 if ( c->mode <= FINISHCLIENT ||  (msg = runPhase(c, MAL_SCENARIO_READER)) )
     468           1 :                         goto wrapup;
     469      173724 :                 if ( c->mode <= FINISHCLIENT  || (msg = runPhase(c, MAL_SCENARIO_PARSER)) || c->blkmode)
     470        8594 :                         goto wrapup;
     471      165127 :                 if ( c->mode <= FINISHCLIENT ||  (msg = runPhase(c, MAL_SCENARIO_OPTIMIZE)) )
     472           0 :                         goto wrapup;
     473      165129 :                 if ( c->mode <= FINISHCLIENT || (msg = runPhase(c, MAL_SCENARIO_SCHEDULER)))
     474           0 :                         goto wrapup;
     475      165130 :                 if ( c->mode <= FINISHCLIENT || (msg = runPhase(c, MAL_SCENARIO_ENGINE)))
     476        1620 :                         goto wrapup;
     477      163509 :         wrapup:
     478      173725 :                 if (msg != MAL_SUCCEED){
     479        5567 :                         if (c->phase[MAL_SCENARIO_CALLBACK]) {
     480        5567 :                                 MT_thread_setworking(phases[MAL_SCENARIO_CALLBACK]);
     481        5567 :                                 msg = (str) (*c->phase[MAL_SCENARIO_CALLBACK])(c, msg);
     482             :                         }
     483        5567 :                         if (msg) {
     484           0 :                                 mnstr_printf(c->fdout,"!%s%s", msg, (msg[strlen(msg)-1] == '\n'? "":"\n"));
     485           0 :                                 freeException(msg);
     486             :                                 msg = MAL_SUCCEED;
     487             :                         }
     488             :                 }
     489      173725 :                 if( GDKerrbuf && GDKerrbuf[0])
     490           0 :                         mnstr_printf(c->fdout,"!GDKerror: %s\n",GDKerrbuf);
     491      173725 :                 assert(c->curprg->def->errors == NULL);
     492      173725 :                 if( once) break;
     493             :         }
     494        4303 :         if (once == 0)
     495        4293 :                 msg = runPhase(c, MAL_SCENARIO_EXITCLIENT);
     496        4303 :         return msg;
     497             : }
     498             : 
     499             : str
     500        4303 : runScenario(Client c, int once)
     501             : {
     502             :         str msg = MAL_SUCCEED;
     503             : 
     504        4303 :         if (c == 0 || c->phase[MAL_SCENARIO_READER] == 0)
     505             :                 return msg;
     506        4303 :         msg = runScenarioBody(c,once);
     507        4303 :         if (msg != MAL_SUCCEED &&
     508           0 :                         strcmp(msg,"MALException:client.quit:Server stopped."))
     509           0 :                 mnstr_printf(c->fdout,"!%s\n",msg);
     510             :         return msg;
     511             : }

Generated by: LCOV version 1.14