| 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 | |