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 - 2019 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_linker.h" /* for getAddress() */ |
96 | #include "mal_client.h" |
97 | #include "mal_authorize.h" |
98 | #include "mal_exception.h" |
99 | #include "mal_profiler.h" |
100 | #include "mal_private.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 | getFreeScenario(void) |
142 | { |
143 | int i; |
144 | Scenario scen = NULL; |
145 | |
146 | MT_lock_set(&scenarioLock); |
147 | for (i = 0; i < MAXSCEN && scenarioRec[i].name; i++) |
148 | ; |
149 | if (i < MAXSCEN) |
150 | scen = scenarioRec + i; |
151 | MT_lock_unset(&scenarioLock); |
152 | |
153 | return scen; |
154 | } |
155 | |
156 | /* |
157 | * A scenario is initialized only once per session. |
158 | * All other requests are silently ignored. |
159 | * After initialization, all state functions should have been set. |
160 | * Initialization includes searching for the scenario startup file in |
161 | * the etc/MonetDB directory. This creates a dependency, because the |
162 | * malInclude also needs a scenario. To break this cycle, the system should |
163 | * call once the routine default scenario for each client first. |
164 | */ |
165 | static str |
166 | initScenario(Client c, Scenario s) |
167 | { |
168 | str l = s->language; |
169 | str msg = MAL_SUCCEED; |
170 | |
171 | if (s->initSystemCmd) |
172 | return(fillScenario(c, s)); |
173 | /* prepare for conclicts */ |
174 | MT_lock_set(&mal_contextLock); |
175 | if (s->initSystem && s->initSystemCmd == 0) { |
176 | s->initSystemCmd = (MALfcn) getAddress(s->initSystem); |
177 | if (s->initSystemCmd) { |
178 | msg = (*s->initSystemCmd) (c); |
179 | } else { |
180 | char buf[BUFSIZ]; |
181 | snprintf(buf,BUFSIZ,"%s.init" , l); |
182 | msg = createException(MAL, buf, "Scenario not initialized" ); |
183 | } |
184 | } |
185 | if (msg) { |
186 | MT_lock_unset(&mal_contextLock); |
187 | return msg; |
188 | } |
189 | |
190 | if (s->exitSystem && s->exitSystemCmd == 0) |
191 | s->exitSystemCmd = (MALfcn) getAddress(s->exitSystem); |
192 | if (s->initClient && s->initClientCmd == 0) |
193 | s->initClientCmd = (MALfcn) getAddress(s->initClient); |
194 | if (s->exitClient && s->exitClientCmd == 0) |
195 | s->exitClientCmd = (MALfcn) getAddress(s->exitClient); |
196 | if (s->reader && s->readerCmd == 0) |
197 | s->readerCmd = (MALfcn) getAddress(s->reader); |
198 | if (s->parser && s->parserCmd == 0) |
199 | s->parserCmd = (MALfcn) getAddress(s->parser); |
200 | if (s->optimizer && s->optimizerCmd == 0) |
201 | s->optimizerCmd = (MALfcn) getAddress(s->optimizer); |
202 | if (s->tactics && s->tacticsCmd == 0) |
203 | s->tacticsCmd = (MALfcn) getAddress(s->tactics); |
204 | if (s->callback && s->callbackCmd == 0) |
205 | s->callbackCmd = (MALfcn) getAddress(s->callback); |
206 | if (s->engine && s->engineCmd == 0) |
207 | s->engineCmd = (MALfcn) getAddress(s->engine); |
208 | MT_lock_unset(&mal_contextLock); |
209 | return(fillScenario(c, s)); |
210 | } |
211 | |
212 | str |
213 | defaultScenario(Client c) |
214 | { |
215 | return initScenario(c, scenarioRec); |
216 | } |
217 | |
218 | /* |
219 | * The Monet debugger provides an option to inspect the scenarios currently |
220 | * defined. |
221 | * |
222 | */ |
223 | static void |
224 | print_scenarioCommand(stream *f, str cmd, MALfcn funcptr) |
225 | { |
226 | if (cmd) |
227 | mnstr_printf(f," \"%s%s\"," , cmd, (funcptr?"" :"?" )); |
228 | else |
229 | mnstr_printf(f," nil," ); |
230 | } |
231 | |
232 | void |
233 | showScenario(stream *f, Scenario scen) |
234 | { |
235 | mnstr_printf(f, "[ \"%s\"," , scen->name); |
236 | print_scenarioCommand(f, scen->initSystem, scen->initSystemCmd); |
237 | print_scenarioCommand(f, scen->exitSystem, scen->exitSystemCmd); |
238 | print_scenarioCommand(f, scen->initClient, scen->initClientCmd); |
239 | print_scenarioCommand(f, scen->exitClient, scen->exitClientCmd); |
240 | print_scenarioCommand(f, scen->parser, scen->parserCmd); |
241 | print_scenarioCommand(f, scen->optimizer, scen->optimizerCmd); |
242 | print_scenarioCommand(f, scen->tactics, scen->tacticsCmd); |
243 | print_scenarioCommand(f, scen->callback, scen->callbackCmd); |
244 | print_scenarioCommand(f, scen->engine, scen->engineCmd); |
245 | mnstr_printf(f, "]\n" ); |
246 | } |
247 | |
248 | Scenario |
249 | findScenario(str nme) |
250 | { |
251 | int i; |
252 | Scenario scen = scenarioRec; |
253 | |
254 | for (i = 0; i < MAXSCEN && scen->name; i++, scen++) |
255 | if (strcmp(scen->name, nme) == 0) |
256 | return scen; |
257 | return NULL; |
258 | } |
259 | |
260 | /* |
261 | * Functions may become resolved only after the corresponding module |
262 | * has been loaded. This should be announced as part of the module |
263 | * prelude code. |
264 | * Beware that after the update, we also have to adjust the client records. |
265 | * They contain a copy of the functions addresses. |
266 | */ |
267 | void |
268 | updateScenario(str nme, str fnme, MALfcn fcn) |
269 | { |
270 | int phase = -1; |
271 | Scenario scen = findScenario(nme); |
272 | |
273 | if (scen == NULL) |
274 | return; |
275 | if (scen->initSystem && strcmp(scen->initSystem, fnme) == 0) |
276 | scen->initSystemCmd = fcn; |
277 | if (scen->exitSystem && strcmp(scen->exitSystem, fnme) == 0) |
278 | scen->exitSystemCmd = fcn; |
279 | if (scen->initClient && strcmp(scen->initClient, fnme) == 0) { |
280 | scen->initClientCmd = fcn; |
281 | phase = MAL_SCENARIO_INITCLIENT; |
282 | } |
283 | if (scen->exitClient && strcmp(scen->exitClient, fnme) == 0) { |
284 | scen->exitClientCmd = fcn; |
285 | phase = MAL_SCENARIO_EXITCLIENT; |
286 | } |
287 | if (scen->reader && strcmp(scen->reader, fnme) == 0) { |
288 | scen->readerCmd = fcn; |
289 | phase = MAL_SCENARIO_READER; |
290 | } |
291 | if (scen->parser && strcmp(scen->parser, fnme) == 0) { |
292 | scen->parserCmd = fcn; |
293 | phase = MAL_SCENARIO_PARSER; |
294 | } |
295 | if (scen->optimizer && strcmp(scen->optimizer, fnme) == 0) { |
296 | scen->optimizerCmd = fcn; |
297 | phase = MAL_SCENARIO_OPTIMIZE; |
298 | } |
299 | if (scen->tactics && strcmp(scen->tactics, fnme) == 0) { |
300 | scen->tacticsCmd = fcn; |
301 | phase = MAL_SCENARIO_SCHEDULER; |
302 | } |
303 | if (scen->callback && strcmp(scen->callback, fnme) == 0) { |
304 | scen->callbackCmd = fcn; |
305 | phase = MAL_SCENARIO_CALLBACK; |
306 | } |
307 | if (scen->engine && strcmp(scen->engine, fnme) == 0) { |
308 | scen->engineCmd = fcn; |
309 | phase = MAL_SCENARIO_ENGINE; |
310 | } |
311 | if (phase != -1) { |
312 | Client c1; |
313 | |
314 | for (c1 = mal_clients; c1 < mal_clients + MAL_MAXCLIENTS; c1++) { |
315 | if (c1->scenario && |
316 | strcmp(c1->scenario, scen->name) == 0) |
317 | c1->phase[phase] = fcn; |
318 | if (c1->oldscenario && |
319 | strcmp(c1->oldscenario, scen->name) == 0) |
320 | c1->oldphase[phase] = fcn; |
321 | } |
322 | } |
323 | } |
324 | |
325 | void |
326 | showScenarioByName(stream *f, str nme) |
327 | { |
328 | Scenario scen = findScenario(nme); |
329 | |
330 | if (scen) |
331 | showScenario(f, scen); |
332 | } |
333 | |
334 | void |
335 | showAllScenarios(stream *f) |
336 | { |
337 | int i; |
338 | Scenario scen = scenarioRec; |
339 | |
340 | for (i = 0; i < MAXSCEN && scen->name; i++, scen++) |
341 | showScenario(f, scen); |
342 | } |
343 | |
344 | str getScenarioLanguage(Client c){ |
345 | Scenario scen= findScenario(c->scenario); |
346 | if( scen) return scen->language; |
347 | return "mal" ; |
348 | } |
349 | /* |
350 | * Changing the scenario for a particular client invalidates the |
351 | * state maintained for the previous scenario. The old scenario is |
352 | * retained in the client record to facilitate propagation of |
353 | * state information, or to simply switch back to the previous one. |
354 | * Before we initialize a scenario the client scenario is reset to |
355 | * the MAL scenario. This implies that all scenarios are initialized |
356 | * using the same scenario. After the scenario initialization file |
357 | * has been processed, the scenario phases are replaced with the |
358 | * proper ones. |
359 | * |
360 | * All client records should be initialized with a default |
361 | * scenario, i.e. the first described in the scenario table. |
362 | */ |
363 | static str |
364 | fillScenario(Client c, Scenario scen) |
365 | { |
366 | c->scenario = scen->name; |
367 | |
368 | c->phase[MAL_SCENARIO_READER] = scen->readerCmd; |
369 | c->phase[MAL_SCENARIO_PARSER] = scen->parserCmd; |
370 | c->phase[MAL_SCENARIO_OPTIMIZE] = scen->optimizerCmd; |
371 | c->phase[MAL_SCENARIO_SCHEDULER] = scen->tacticsCmd; |
372 | c->phase[MAL_SCENARIO_CALLBACK] = scen->callbackCmd; |
373 | c->phase[MAL_SCENARIO_ENGINE] = scen->engineCmd; |
374 | c->phase[MAL_SCENARIO_INITCLIENT] = scen->initClientCmd; |
375 | c->phase[MAL_SCENARIO_EXITCLIENT] = scen->exitClientCmd; |
376 | c->state[MAL_SCENARIO_READER] = 0; |
377 | c->state[MAL_SCENARIO_PARSER] = 0; |
378 | c->state[MAL_SCENARIO_OPTIMIZE] = 0; |
379 | c->state[MAL_SCENARIO_SCHEDULER] = 0; |
380 | c->state[MAL_SCENARIO_ENGINE] = 0; |
381 | c->state[MAL_SCENARIO_INITCLIENT] = 0; |
382 | c->state[MAL_SCENARIO_EXITCLIENT] = 0; |
383 | return(MAL_SUCCEED); |
384 | } |
385 | |
386 | /* |
387 | * Setting a new scenario calls for saving the previous state |
388 | * and execution of the initClientScenario routine. |
389 | */ |
390 | str |
391 | setScenario(Client c, str nme) |
392 | { |
393 | int i; |
394 | str msg; |
395 | Scenario scen; |
396 | |
397 | scen = findScenario(nme); |
398 | if (scen == NULL) |
399 | throw(MAL, "setScenario" , SCENARIO_NOT_FOUND " '%s'" , nme); |
400 | |
401 | if (c->scenario) { |
402 | c->oldscenario = c->scenario; |
403 | for (i = 0; i < SCENARIO_PROPERTIES; i++) { |
404 | c->oldstate[i] = c->state[i]; |
405 | c->oldphase[i] = c->phase[i]; |
406 | } |
407 | } |
408 | for (i = 0; i < SCENARIO_PROPERTIES; i++) |
409 | c->state[i] = 0; |
410 | |
411 | msg = initScenario(c, scen); |
412 | if (msg) { |
413 | /* error occurred, reset the scenario , assume default always works */ |
414 | c->scenario = c->oldscenario; |
415 | for (i = 0; i < SCENARIO_PROPERTIES; i++) { |
416 | c->state[i] = c->oldstate[i]; |
417 | c->phase[i] = c->oldphase[i]; |
418 | c->oldstate[i] = NULL; |
419 | c->oldphase[i] = NULL; |
420 | } |
421 | c->oldscenario = NULL; |
422 | return msg; |
423 | } |
424 | return MAL_SUCCEED; |
425 | } |
426 | |
427 | /* |
428 | * After finishing a session in a scenario, we should reset the |
429 | * state of the previous one. But also call the exitClient |
430 | * to garbage collect any scenario specific structures. |
431 | */ |
432 | #if 0 |
433 | str |
434 | getCurrentScenario(Client c) |
435 | { |
436 | return c->scenario; |
437 | } |
438 | #endif |
439 | |
440 | void |
441 | resetScenario(Client c) |
442 | { |
443 | int i; |
444 | Scenario scen = scenarioRec; |
445 | |
446 | if (c->scenario == 0) |
447 | return; |
448 | |
449 | scen = findScenario(c->scenario); |
450 | if (scen != NULL && scen->exitClientCmd) |
451 | (*scen->exitClientCmd) (c); |
452 | |
453 | c->scenario = c->oldscenario; |
454 | for (i = 0; i < SCENARIO_PROPERTIES; i++) { |
455 | c->state[i] = c->oldstate[i]; |
456 | c->phase[i] = c->oldphase[i]; |
457 | } |
458 | c->oldscenario = 0; |
459 | } |
460 | |
461 | /* |
462 | * The building blocks of scenarios are routines obeying a strict |
463 | * name signature. They require exclusive access to the client |
464 | * record. Any specific information should be accessible from |
465 | * there, e.g., access to a scenario specific state descriptor. |
466 | * The client scenario initialization and finalization brackets |
467 | * are @sc{xyzinitClient()} and @sc{xyzexitClient()}. |
468 | * |
469 | * The @sc{xyzparser(Client c)} contains the parser for language XYZ |
470 | * and should fill the MAL program block associated with the client record. |
471 | * The latter may have been initialized with variables. |
472 | * Each language parser may require a catalog with information |
473 | * on the translation of language specific datastructures into their BAT |
474 | * equivalent. |
475 | * |
476 | * The @sc{xyzoptimizer(Client c)} contains language specific optimizations |
477 | * using the MAL intermediate code as a starting point. |
478 | * |
479 | * The @sc{xyztactics(Client c)} synchronizes the program execution with the |
480 | * state of the machine, e.g., claiming resources, the history of the client |
481 | * or alignment of the request with concurrent actions (e.g., transaction |
482 | * coordination). |
483 | * |
484 | * The @sc{xyzengine(Client c)} contains the applicable back-end engine. |
485 | * The default is the MAL interpreter, which provides good balance |
486 | * between speed and ability to analysis its behavior. |
487 | * |
488 | */ |
489 | static const char *phases[] = { |
490 | [MAL_SCENARIO_CALLBACK] = "scenario callback" , |
491 | [MAL_SCENARIO_ENGINE] = "scenario engine" , |
492 | [MAL_SCENARIO_EXITCLIENT] = "scenario exitclient" , |
493 | [MAL_SCENARIO_INITCLIENT] = "scenario initclient" , |
494 | [MAL_SCENARIO_OPTIMIZE] = "scenario optimize" , |
495 | [MAL_SCENARIO_PARSER] = "scenario parser" , |
496 | [MAL_SCENARIO_READER] = "scenario reader" , |
497 | [MAL_SCENARIO_SCHEDULER] = "scenario scheduler" , |
498 | }; |
499 | static str |
500 | runPhase(Client c, int phase) |
501 | { |
502 | str msg = MAL_SUCCEED; |
503 | if (c->phase[phase]) { |
504 | MT_thread_setworking(phases[phase]); |
505 | return msg = (str) (*c->phase[phase])(c); |
506 | } |
507 | return msg; |
508 | } |
509 | |
510 | /* |
511 | * Access control enforcement. Except for the server owner |
512 | * running a scenario should be explicitly permitted. |
513 | */ |
514 | static str |
515 | runScenarioBody(Client c, int once) |
516 | { |
517 | str msg = MAL_SUCCEED; |
518 | |
519 | while (c->mode > FINISHCLIENT && !GDKexiting()) { |
520 | // be aware that a MAL call may initialize a different scenario |
521 | if ( !c->state[0] && (msg = runPhase(c, MAL_SCENARIO_INITCLIENT)) ) |
522 | goto wrapup; |
523 | if ( c->mode <= FINISHCLIENT || (msg = runPhase(c, MAL_SCENARIO_READER)) ) |
524 | goto wrapup; |
525 | if ( c->mode <= FINISHCLIENT || (msg = runPhase(c, MAL_SCENARIO_PARSER)) || c->blkmode) |
526 | goto wrapup; |
527 | if ( c->mode <= FINISHCLIENT || (msg = runPhase(c, MAL_SCENARIO_OPTIMIZE)) ) |
528 | goto wrapup; |
529 | if ( c->mode <= FINISHCLIENT || (msg = runPhase(c, MAL_SCENARIO_SCHEDULER))) |
530 | goto wrapup; |
531 | if ( c->mode <= FINISHCLIENT || (msg = runPhase(c, MAL_SCENARIO_ENGINE))) |
532 | goto wrapup; |
533 | wrapup: |
534 | if (msg != MAL_SUCCEED){ |
535 | if (c->phase[MAL_SCENARIO_CALLBACK]) { |
536 | MT_thread_setworking(phases[MAL_SCENARIO_CALLBACK]); |
537 | msg = (str) (*c->phase[MAL_SCENARIO_CALLBACK])(c, msg); |
538 | } |
539 | if (msg) { |
540 | mnstr_printf(c->fdout,"!%s%s" , msg, (msg[strlen(msg)-1] == '\n'? "" :"\n" )); |
541 | freeException(msg); |
542 | msg = MAL_SUCCEED; |
543 | } |
544 | } |
545 | if( GDKerrbuf && GDKerrbuf[0]) |
546 | mnstr_printf(c->fdout,"!GDKerror: %s\n" ,GDKerrbuf); |
547 | assert(c->curprg->def->errors == NULL); |
548 | c->actions++; |
549 | if( once) break; |
550 | } |
551 | if (once == 0) |
552 | msg = runPhase(c, MAL_SCENARIO_EXITCLIENT); |
553 | return msg; |
554 | } |
555 | |
556 | str |
557 | runScenario(Client c, int once) |
558 | { |
559 | str msg = MAL_SUCCEED; |
560 | |
561 | if (c == 0 || c->phase[MAL_SCENARIO_READER] == 0) |
562 | return msg; |
563 | msg = runScenarioBody(c,once); |
564 | if (msg != MAL_SUCCEED && |
565 | strcmp(msg,"MALException:client.quit:Server stopped." )) |
566 | mnstr_printf(c->fdout,"!%s\n" ,msg); |
567 | return msg; |
568 | } |
569 | |
570 | |