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/* (author) M.L. Kersten
10 */
11#include "monetdb_config.h"
12#include "mal_session.h"
13#include "mal_instruction.h" /* for pushEndInstruction() */
14#include "mal_interpreter.h" /* for runMAL(), garbageElement() */
15#include "mal_parser.h" /* for parseMAL() */
16#include "mal_namespace.h"
17#include "mal_authorize.h"
18#include "mal_builder.h"
19#include "msabaoth.h"
20#include "mal_private.h"
21#include "gdk.h" /* for opendir and friends */
22
23/*
24 * The MonetDB server uses a startup script to boot the system.
25 * This script is an ordinary MAL program, but will mostly
26 * consist of include statements to load modules of general interest.
27 * The startup script is run as user Admin.
28 */
29str
30malBootstrap(void)
31{
32 Client c;
33 str msg = MAL_SUCCEED;
34 str bootfile = "mal_init";
35
36 c = MCinitClient((oid) 0, NULL, NULL);
37 if(c == NULL) {
38 throw(MAL, "malBootstrap", "Failed to initialize client");
39 }
40 assert(c != NULL);
41 c->curmodule = c->usermodule = userModule();
42 if(c->usermodule == NULL) {
43 MCfreeClient(c);
44 throw(MAL, "malBootstrap", "Failed to initialize client MAL module");
45 }
46 if ( (msg = defaultScenario(c)) ) {
47 MCfreeClient(c);
48 return msg;
49 }
50 if((msg = MSinitClientPrg(c, "user", "main")) != MAL_SUCCEED) {
51 MCfreeClient(c);
52 return msg;
53 }
54 if( MCinitClientThread(c) < 0){
55 MCfreeClient(c);
56 throw(MAL, "malBootstrap", "Failed to create client thread");
57 }
58 if ((msg = malInclude(c, bootfile, 0)) != MAL_SUCCEED) {
59 MCfreeClient(c);
60 return msg;
61 }
62 pushEndInstruction(c->curprg->def);
63 chkProgram(c->usermodule, c->curprg->def);
64 if ( (msg= c->curprg->def->errors) != MAL_SUCCEED ) {
65 MCfreeClient(c);
66 return msg;
67 }
68 msg = MALengine(c);
69 MCfreeClient(c);
70 return msg;
71}
72
73/*
74 * Every client has a 'main' function to collect the statements. Once
75 * the END instruction has been found, it is added to the symbol table
76 * and a fresh container is being constructed. Note, this scheme makes
77 * testing for recursive function calls a little more difficult.
78 * Therefore, type checking should be performed afterwards.
79 *
80 * In interactive mode, the closing statement is never reached. The
81 * 'main' procedure is typically cleaned between successive external
82 * messages except for its variables, which are considerd global. This
83 * storage container is re-used when during the previous call nothing
84 * was added. At the end of the session we have to garbage collect the
85 * BATs introduced.
86 */
87static str
88MSresetClientPrg(Client cntxt, str mod, str fcn)
89{
90 MalBlkPtr mb;
91 InstrPtr p;
92
93 cntxt->itrace = 0; /* turn off any debugging */
94 mb = cntxt->curprg->def;
95 mb->stop = 1;
96 mb->errors = MAL_SUCCEED;
97 p = mb->stmt[0];
98
99 p->gc = 0;
100 p->retc = 1;
101 p->argc = 1;
102 p->argv[0] = 0;
103
104#ifdef _DEBUG_SESSION_
105 fprintf(stderr,"reset sym %s %s to %s, id %d\n",
106 cntxt->curprg->name, getFunctionId(p), nme, findVariable(mb,nme) );
107 fprintf(stderr,"vtop %d\n", mb->vtop);
108 if( mb->vtop)
109 fprintf(stderr,"first var %s\n", mb->var[0].id);
110#endif
111
112 setModuleId(p, mod);
113 setFunctionId(p, fcn);
114 if( findVariable(mb,fcn) < 0)
115 p->argv[0] = newVariable(mb, fcn, strlen(fcn), TYPE_void);
116
117 setVarType(mb, findVariable(mb, fcn), TYPE_void);
118 /* remove any MAL history */
119 if (mb->history) {
120 freeMalBlk(mb->history);
121 mb->history = 0;
122 }
123 return MAL_SUCCEED;
124}
125
126/*
127 * Create a new container block
128 */
129
130str
131MSinitClientPrg(Client cntxt, str mod, str nme)
132{
133 int idx;
134
135 if (cntxt->curprg && idcmp(nme, cntxt->curprg->name) == 0)
136 return MSresetClientPrg(cntxt, putName(mod), putName(nme));
137 cntxt->curprg = newFunction(putName(mod), putName(nme), FUNCTIONsymbol);
138 if( cntxt->curprg == 0)
139 throw(MAL, "initClientPrg", SQLSTATE(HY001) MAL_MALLOC_FAIL);
140 if( (idx= findVariable(cntxt->curprg->def,"main")) >=0)
141 setVarType(cntxt->curprg->def, idx, TYPE_void);
142 insertSymbol(cntxt->usermodule,cntxt->curprg);
143
144 if (cntxt->glb == NULL )
145 cntxt->glb = newGlobalStack(MAXGLOBALS + cntxt->curprg->def->vsize);
146 if( cntxt->glb == NULL)
147 throw(MAL,"initClientPrg", SQLSTATE(HY001) MAL_MALLOC_FAIL);
148 assert(cntxt->curprg->def != NULL);
149 assert(cntxt->curprg->def->vtop >0);
150 return MAL_SUCCEED;
151}
152
153/*
154 * The default method to interact with the database server is to connect
155 * using a port number. The first line received should contain
156 * authorization information, such as user name.
157 *
158 * The scheduleClient receives a challenge response consisting of
159 * endian:user:password:lang:database:
160 */
161static void
162exit_streams( bstream *fin, stream *fout )
163{
164 if (fout && fout != GDKstdout) {
165 mnstr_flush(fout);
166 close_stream(fout);
167 }
168 if (fin)
169 bstream_destroy(fin);
170}
171
172const char* mal_enableflag = "mal_for_all";
173
174void
175MSscheduleClient(str command, str challenge, bstream *fin, stream *fout, protocol_version protocol, size_t blocksize)
176{
177 char *user = command, *algo = NULL, *passwd = NULL, *lang = NULL;
178 char *database = NULL, *s;
179 const char *dbname;
180 str msg = MAL_SUCCEED;
181 bool filetrans = false;
182 Client c;
183
184 /* decode BIG/LIT:user:{cypher}passwordchal:lang:database: line */
185
186 /* byte order */
187 s = strchr(user, ':');
188 if (s) {
189 *s = 0;
190 mnstr_set_bigendian(fin->s, strcmp(user, "BIG") == 0);
191 user = s + 1;
192 } else {
193 mnstr_printf(fout, "!incomplete challenge '%s'\n", user);
194 exit_streams(fin, fout);
195 GDKfree(command);
196 return;
197 }
198
199 /* passwd */
200 s = strchr(user, ':');
201 if (s) {
202 *s = 0;
203 passwd = s + 1;
204 /* decode algorithm, i.e. {plain}mypasswordchallenge */
205 if (*passwd != '{') {
206 mnstr_printf(fout, "!invalid password entry\n");
207 exit_streams(fin, fout);
208 GDKfree(command);
209 return;
210 }
211 algo = passwd + 1;
212 s = strchr(algo, '}');
213 if (!s) {
214 mnstr_printf(fout, "!invalid password entry\n");
215 exit_streams(fin, fout);
216 GDKfree(command);
217 return;
218 }
219 *s = 0;
220 passwd = s + 1;
221 } else {
222 mnstr_printf(fout, "!incomplete challenge '%s'\n", user);
223 exit_streams(fin, fout);
224 GDKfree(command);
225 return;
226 }
227
228 /* lang */
229 s = strchr(passwd, ':');
230 if (s) {
231 *s = 0;
232 lang = s + 1;
233 } else {
234 mnstr_printf(fout, "!incomplete challenge, missing language\n");
235 exit_streams(fin, fout);
236 GDKfree(command);
237 return;
238 }
239
240 /* database */
241 s = strchr(lang, ':');
242 if (s) {
243 *s = 0;
244 database = s + 1;
245 /* we can have stuff following, make it void */
246 s = strchr(database, ':');
247 if (s)
248 *s++ = 0;
249 }
250
251 if (s && strncmp(s, "FILETRANS:", 10) == 0) {
252 s += 10;
253 filetrans = true;
254 }
255
256 dbname = GDKgetenv("gdk_dbname");
257 if (database != NULL && database[0] != '\0' &&
258 strcmp(database, dbname) != 0)
259 {
260 mnstr_printf(fout, "!request for database '%s', "
261 "but this is database '%s', "
262 "did you mean to connect to monetdbd instead?\n",
263 database, dbname);
264 /* flush the error to the client, and abort further execution */
265 exit_streams(fin, fout);
266 GDKfree(command);
267 return;
268 } else {
269 str err;
270 oid uid;
271 sabdb *stats = NULL;
272
273 /* access control: verify the credentials supplied by the user,
274 * no need to check for database stuff, because that is done per
275 * database itself (one gets a redirect) */
276 err = AUTHcheckCredentials(&uid, NULL, user, passwd, challenge, algo);
277 if (err != MAL_SUCCEED) {
278 mnstr_printf(fout, "!%s\n", err);
279 exit_streams(fin, fout);
280 freeException(err);
281 GDKfree(command);
282 return;
283 }
284
285 if (!GDKinmemory()) {
286 err = msab_getMyStatus(&stats);
287 if (err != NULL) {
288 /* this is kind of awful, but we need to get rid of this
289 * message */
290 fprintf(stderr, "!msab_getMyStatus: %s\n", err);
291 free(err);
292 mnstr_printf(fout, "!internal server error, "
293 "please try again later\n");
294 exit_streams(fin, fout);
295 GDKfree(command);
296 return;
297 }
298 if (stats->locked) {
299 if (uid == 0) {
300 mnstr_printf(fout, "#server is running in "
301 "maintenance mode\n");
302 } else {
303 mnstr_printf(fout, "!server is running in "
304 "maintenance mode, please try again later\n");
305 exit_streams(fin, fout);
306 msab_freeStatus(&stats);
307 GDKfree(command);
308 return;
309 }
310 }
311 msab_freeStatus(&stats);
312 }
313
314 c = MCinitClient(uid, fin, fout);
315 if (c == NULL) {
316 if ( MCshutdowninprogress())
317 mnstr_printf(fout, "!system shutdown in progress, please try again later\n");
318 else
319 mnstr_printf(fout, "!maximum concurrent client limit reached "
320 "(%d), please try again later\n", MAL_MAXCLIENTS);
321 exit_streams(fin, fout);
322 GDKfree(command);
323 return;
324 }
325 c->filetrans = filetrans;
326 /* move this back !! */
327 if (c->usermodule == 0) {
328 c->curmodule = c->usermodule = userModule();
329 if(c->curmodule == NULL) {
330 mnstr_printf(fout, "!could not allocate space\n");
331 exit_streams(fin, fout);
332 GDKfree(command);
333 return;
334 }
335 }
336
337 if ((s = setScenario(c, lang)) != NULL) {
338 mnstr_printf(c->fdout, "!%s\n", s);
339 mnstr_flush(c->fdout);
340 GDKfree(s);
341 c->mode = FINISHCLIENT;
342 }
343 if (!GDKgetenv_isyes(mal_enableflag) &&
344 (strncasecmp("sql", lang, 3) != 0 && uid != 0)) {
345
346 mnstr_printf(fout, "!only the 'monetdb' user can use non-sql languages. "
347 "run mserver5 with --set %s=yes to change this.\n", mal_enableflag);
348 exit_streams(fin, fout);
349 GDKfree(command);
350 return;
351 }
352 }
353
354 if((msg = MSinitClientPrg(c, "user", "main")) != MAL_SUCCEED) {
355 mnstr_printf(fout, "!could not allocate space\n");
356 exit_streams(fin, fout);
357 freeException(msg);
358 GDKfree(command);
359 return;
360 }
361
362 GDKfree(command);
363
364 /* NOTE ABOUT STARTING NEW THREADS
365 * At this point we have conducted experiments (Jun 2012) with
366 * reusing threads. The implementation used was a lockless array of
367 * semaphores to wake up threads to do work. Experimentation on
368 * Linux, Solaris and Darwin showed no significant improvements, in
369 * most cases no improvements at all. Hence the following
370 * conclusion: thread reuse doesn't save up on the costs of just
371 * forking new threads. Since the latter means no difficulties of
372 * properly maintaining a pool of threads and picking the workers
373 * out of them, it is favourable just to start new threads on
374 * demand. */
375
376 /* fork a new thread to handle this client */
377
378 c->protocol = protocol;
379 c->blocksize = blocksize;
380
381 mnstr_settimeout(c->fdin->s, 50, GDKexiting);
382 msg = MSserveClient(c);
383 if (msg != MAL_SUCCEED) {
384 mnstr_printf(fout, "!could not serve client\n");
385 exit_streams(fin, fout);
386 freeException(msg);
387 }
388}
389
390/*
391 * After the client initialization has been finished, we can start the
392 * interaction protocol. This involves parsing the input in the context
393 * of an already defined procedure and upon success, its execution.
394 *
395 * In essence, this calls for an incremental parsing operation, because
396 * we should wait until a complete basic block has been detected. Test,
397 * first collect the instructions before we take them all.
398 *
399 * In interactive mode, we should remove the instructions before
400 * accepting new ones. The function signature remains the same and the
401 * symbol table should also not be affected. Aside from removing
402 * instruction, we should also condense the variable stack, i.e.
403 * removing at least the temporary variables, but maybe everything
404 * beyond a previous defined point.
405 *
406 * Beware that we have to cleanup the global stack as well. This to
407 * avoid subsequent calls to find garbage information. However, this
408 * action is only required after a successful execution. Otherwise,
409 * garbage collection is not needed.
410 */
411void
412MSresetInstructions(MalBlkPtr mb, int start)
413{
414 int i;
415 InstrPtr p;
416
417 for (i = start; i < mb->ssize; i++) {
418 p = getInstrPtr(mb, i);
419 if (p)
420 freeInstruction(p);
421 mb->stmt[i] = NULL;
422 }
423 mb->stop = start;
424}
425
426/*
427 * Determine the variables being used and clear non-used onces.
428 */
429void
430MSresetVariables(Client cntxt, MalBlkPtr mb, MalStkPtr glb, int start)
431{
432 int i;
433
434#ifdef _DEBUG_SESSION_
435 fprintf(stderr,"resetVarables %d vtop %d errors %s\n", start, mb->vtop,mb->errors);
436#endif
437 for (i = 0; i < start && i < mb->vtop ; i++)
438 setVarUsed(mb,i);
439 if (mb->errors == MAL_SUCCEED)
440 for (i = start; i < mb->vtop; i++) {
441 if (isVarUsed(mb,i) || !isTmpVar(mb,i)){
442 assert(!mb->var[i].value.vtype || isVarConstant(mb, i));
443 setVarUsed(mb,i);
444 }
445 if (glb && !isVarUsed(mb,i)) {
446 if (isVarConstant(mb, i))
447 garbageElement(cntxt, &glb->stk[i]);
448 /* clean stack entry */
449 glb->stk[i].vtype = TYPE_int;
450 glb->stk[i].len = 0;
451 glb->stk[i].val.pval = 0;
452 }
453 }
454
455#ifdef _DEBUG_SESSION_
456 fprintf(stderr,"resetVar %s %d\n", getFunctionId(mb->stmt[0]), mb->var[mb->stmt[0]->argv[0]].used);
457#endif
458 if (mb->errors == MAL_SUCCEED)
459 trimMalVariables_(mb, glb);
460#ifdef _DEBUG_SESSION_
461 fprintf(stderr,"after trim %s %d\n", getFunctionId(mb->stmt[0]), mb->vtop);
462#endif
463}
464
465/*
466 * Here we start the client. We need to initialize and allocate space
467 * for the global variables. Thereafter it is up to the scenario
468 * interpreter to process input.
469 */
470str
471MSserveClient(Client c)
472{
473 MalBlkPtr mb;
474 str msg = 0;
475
476 if (MCinitClientThread(c) < 0) {
477 MCcloseClient(c);
478 return MAL_SUCCEED;
479 }
480 /*
481 * A stack frame is initialized to keep track of global variables.
482 * The scenarios are run until we finally close the last one.
483 */
484 mb = c->curprg->def;
485 if (c->glb == NULL)
486 c->glb = newGlobalStack(MAXGLOBALS + mb->vsize);
487 if (c->glb == NULL) {
488 c->mode = RUNCLIENT;
489 throw(MAL, "serveClient", SQLSTATE(HY001) MAL_MALLOC_FAIL);
490 } else {
491 c->glb->stktop = mb->vtop;
492 c->glb->blk = mb;
493 }
494
495 if (c->scenario == 0)
496 msg = defaultScenario(c);
497 if (msg) {
498 c->mode = RUNCLIENT;
499 return msg;
500 } else {
501 do {
502 do {
503 MT_thread_setworking("running scenario");
504 msg = runScenario(c,0);
505 freeException(msg);
506 if (c->mode == FINISHCLIENT)
507 break;
508 resetScenario(c);
509 } while (c->scenario && !GDKexiting());
510 } while (c->scenario && c->mode != FINISHCLIENT && !GDKexiting());
511 }
512 MT_thread_setworking("exiting");
513 /* pre announce our exiting: cleaning up may take a while and we
514 * don't want to get killed during that time for fear of
515 * deadlocks */
516 MT_exiting_thread();
517 /*
518 * At this stage we should clean out the MAL block
519 */
520 if (c->backup) {
521 assert(0);
522 freeSymbol(c->backup);
523 c->backup = 0;
524 }
525
526 /*
527 if (c->curprg) {
528 freeSymbol(c->curprg);
529 c->curprg = 0;
530 }
531 */
532
533 MCcloseClient(c);
534 if (c->usermodule /*&& strcmp(c->usermodule->name, "user") == 0*/) {
535 freeModule(c->usermodule);
536 c->usermodule = NULL;
537 }
538 return MAL_SUCCEED;
539}
540
541/*
542 * The stages of processing user requests are controlled by a scenario.
543 * The routines below are the default implementation. The main issues
544 * to deal after parsing it to clean out the Admin.main function from
545 * any information added erroneously.
546 *
547 * Ideally this involves resetting the state of the client 'main'
548 * function, i.e. the symbol table is reset and any instruction added
549 * should be cleaned. Beware that the instruction table may have grown
550 * in size.
551 */
552str
553MALinitClient(Client c)
554{
555 assert(c->state[0] == NULL);
556 c->state[0] = c;
557 return NULL;
558}
559
560str
561MALexitClient(Client c)
562{
563 if (c->glb && c->curprg->def->errors == MAL_SUCCEED)
564 garbageCollector(c, c->curprg->def, c->glb, TRUE);
565 c->mode = FINISHCLIENT;
566 if (c->backup) {
567 assert(0);
568 freeSymbol(c->backup);
569 c->backup = NULL;
570 }
571 /* should be in the usermodule */
572 c->curprg = NULL;
573 if (c->usermodule){
574 freeModule(c->usermodule);
575 c->usermodule = NULL;
576 }
577 return NULL;
578}
579
580str
581MALreader(Client c)
582{
583 if (MCreadClient(c) > 0)
584 return MAL_SUCCEED;
585 MT_lock_set(&mal_contextLock);
586 c->mode = FINISHCLIENT;
587 MT_lock_unset(&mal_contextLock);
588 if (c->fdin)
589 c->fdin->buf[c->fdin->pos] = 0;
590 return MAL_SUCCEED;
591}
592
593str
594MALparser(Client c)
595{
596 InstrPtr p;
597 MalBlkRecord oldstate;
598 str msg= MAL_SUCCEED;
599
600 assert(c->curprg->def->errors == NULL);
601 c->curprg->def->errors = 0;
602 oldstate = *c->curprg->def;
603
604 prepareMalBlk(c->curprg->def, CURRENT(c));
605 parseMAL(c, c->curprg, 0, INT_MAX);
606
607 /* now the parsing is done we should advance the stream */
608 c->fdin->pos += c->yycur;
609 c->yycur = 0;
610
611 /* check for unfinished blocks */
612 if(!c->curprg->def->errors && c->blkmode)
613 return MAL_SUCCEED;
614 /* empty files should be skipped as well */
615 if (c->curprg->def->stop == 1){
616 if ( (msg =c->curprg->def->errors) )
617 c->curprg->def->errors = 0;
618 return msg;
619 }
620
621 p = getInstrPtr(c->curprg->def, 0);
622 if (p->token != FUNCTIONsymbol) {
623 msg =c->curprg->def->errors;
624 c->curprg->def->errors = 0;
625 MSresetVariables(c, c->curprg->def, c->glb, oldstate.vtop);
626 resetMalBlk(c->curprg->def, 1);
627 return msg;
628 }
629 pushEndInstruction(c->curprg->def);
630 chkProgram(c->usermodule, c->curprg->def);
631 if ( (msg =c->curprg->def->errors) ){
632 c->curprg->def->errors = 0;
633 MSresetVariables(c, c->curprg->def, c->glb, oldstate.vtop);
634 resetMalBlk(c->curprg->def, 1);
635 return msg;
636 }
637 return MAL_SUCCEED;
638}
639
640int
641MALcommentsOnly(MalBlkPtr mb)
642{
643 int i;
644
645 for (i = 1; i < mb->stop; i++)
646 if (mb->stmt[i]->token != REMsymbol)
647 return 0;
648 return 1;
649}
650
651str
652MALcallback(Client c, str msg)
653{
654 if (msg) {
655 /* don't print exception decoration, just the message */
656 char *n = NULL;
657 char *o = msg;
658 while ((n = strchr(o, '\n')) != NULL) {
659 if (*o == '!')
660 o++;
661 mnstr_printf(c->fdout, "!%.*s\n", (int) (n - o), o);
662 o = ++n;
663 }
664 if (*o != 0) {
665 if (*o == '!')
666 o++;
667 mnstr_printf(c->fdout, "!%s\n", o);
668 }
669 freeException(msg);
670 }
671 return MAL_SUCCEED;
672}
673
674str
675MALengine(Client c)
676{
677 Symbol prg;
678 str msg = MAL_SUCCEED;
679 MalBlkRecord oldstate = *c->curprg->def;
680 oldstate.stop = 0;
681
682 if (c->blkmode)
683 return MAL_SUCCEED;
684 prg = c->curprg;
685 if (prg == NULL)
686 throw(SYNTAX, "mal.engine", SYNTAX_SIGNATURE);
687 if (prg->def == NULL)
688 throw(SYNTAX, "mal.engine", SYNTAX_SIGNATURE);
689
690 if (prg->def->errors != MAL_SUCCEED) {
691 msg = prg->def->errors;
692 prg->def->errors = NULL;
693 MSresetVariables(c, c->curprg->def, c->glb, oldstate.vtop);
694 resetMalBlk(c->curprg->def, 1);
695 return msg;
696 }
697 if (prg->def->stop == 1 || MALcommentsOnly(prg->def))
698 return 0; /* empty block */
699 if (c->glb) {
700 if (prg->def && c->glb->stksize < prg->def->vsize){
701 c->glb = reallocGlobalStack(c->glb, prg->def->vsize);
702 if( c->glb == NULL)
703 throw(MAL, "mal.engine", SQLSTATE(HY001) MAL_MALLOC_FAIL);
704 }
705 c->glb->stktop = prg->def->vtop;
706 c->glb->blk = prg->def;
707 c->glb->cmd = (c->itrace && c->itrace != 'C') ? 'n' : 0;
708 }
709
710 /*
711 * In interactive mode we should avoid early garbage collection of values.
712 * This can be controlled by the clean up control at the instruction level
713 * and marking all non-temporary variables as being (potentially) used.
714 */
715 if (c->glb) {
716 c->glb->pcup = 0;
717 c->glb->keepAlive = TRUE; /* no garbage collection */
718 }
719 if (prg->def->errors == MAL_SUCCEED)
720 msg = (str) runMAL(c, prg->def, 0, c->glb);
721 if (msg) {
722 /* ignore "internal" exceptions */
723 if (strstr(msg, "client.quit") ) {
724 freeException(msg);
725 msg = MAL_SUCCEED;
726 }
727 }
728 MSresetVariables(c, prg->def, c->glb, 0);
729 resetMalBlk(prg->def, 1);
730 if (c->glb) {
731 /* for global stacks avoid reinitialization from this point */
732 c->glb->stkbot = prg->def->vtop;
733 }
734
735 if (prg->def->errors)
736 GDKfree(prg->def->errors);
737 prg->def->errors = NULL;
738 if (c->itrace)
739 mnstr_printf(c->fdout, "mdb>#EOD\n");
740 return msg;
741}
742
743