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 | */ |
29 | str |
30 | malBootstrap(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 | */ |
87 | static str |
88 | MSresetClientPrg(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 | |
130 | str |
131 | MSinitClientPrg(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 | */ |
161 | static void |
162 | exit_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 | |
172 | const char* mal_enableflag = "mal_for_all" ; |
173 | |
174 | void |
175 | MSscheduleClient(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 | */ |
411 | void |
412 | MSresetInstructions(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 | */ |
429 | void |
430 | MSresetVariables(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 | */ |
470 | str |
471 | MSserveClient(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 | */ |
552 | str |
553 | MALinitClient(Client c) |
554 | { |
555 | assert(c->state[0] == NULL); |
556 | c->state[0] = c; |
557 | return NULL; |
558 | } |
559 | |
560 | str |
561 | MALexitClient(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 | |
580 | str |
581 | MALreader(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 | |
593 | str |
594 | MALparser(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 | |
640 | int |
641 | (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 | |
651 | str |
652 | MALcallback(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 | |
674 | str |
675 | MALengine(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 | |