0027620: Test perf bop boxholes crashes DRAW
[occt.git] / src / Draw / Draw_Interpretor.cxx
1 // Created on: 1995-02-23
2 // Created by: Remi LEQUETTE
3 // Copyright (c) 1995-1999 Matra Datavision
4 // Copyright (c) 1999-2014 OPEN CASCADE SAS
5 //
6 // This file is part of Open CASCADE Technology software library.
7 //
8 // This library is free software; you can redistribute it and/or modify it under
9 // the terms of the GNU Lesser General Public License version 2.1 as published
10 // by the Free Software Foundation, with special exception defined in the file
11 // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
12 // distribution for complete text of the license and disclaimer of any warranty.
13 //
14 // Alternatively, this file may be used under the terms of Open CASCADE
15 // commercial license or contractual agreement.
16
17 #include <Draw_Interpretor.hxx>
18 #include <Draw_Appli.hxx>
19 #include <Standard_SStream.hxx>
20 #include <Standard_RangeError.hxx>
21 #include <Standard_ErrorHandler.hxx>
22 #include <Standard_Macro.hxx>
23
24 #include <TCollection_AsciiString.hxx>
25 #include <TCollection_ExtendedString.hxx>
26 #include <OSD_Process.hxx>
27 #include <OSD_Path.hxx>
28 #include <OSD.hxx>
29 #include <OSD_File.hxx>
30
31 #include <string.h>
32 #include <tcl.h>
33 #include <fcntl.h>
34 #ifndef _WIN32
35 #include <unistd.h>
36 #endif
37
38 // for capturing of cout and cerr (dup(), dup2())
39 #ifdef _WIN32
40 #include <io.h>
41 #include <sys/stat.h>  
42 #endif
43
44 #if ! defined(STDOUT_FILENO)
45 #define STDOUT_FILENO fileno(stdout)
46 #endif
47 #if ! defined(STDERR_FILENO)
48 #define STDERR_FILENO fileno(stderr)
49 #endif
50
51 #if ((TCL_MAJOR_VERSION > 8) || ((TCL_MAJOR_VERSION == 8) && (TCL_MINOR_VERSION >= 1)))
52 #define TCL_USES_UTF8
53 #endif
54
55 // logging helpers
56 namespace {
57   void dumpArgs (Standard_OStream& os, int argc, const char *argv[])
58   {
59     for (int i=0; i < argc; i++)
60       os << argv[i] << " ";
61     os << endl;
62   }
63
64   void flush_standard_streams ()
65   {
66     fflush (stderr);
67     fflush (stdout);
68     cerr << flush;
69     cout << flush;
70   }
71
72   int capture_start (int theFDStd, int theFDLog)
73   {
74     Standard_ASSERT_RETURN (theFDLog >= 0, "Invalid descriptor of log file", -1);
75
76     // Duplicate a file descriptor of the standard stream to be able to restore output to it later
77     int aFDSave = dup (theFDStd);
78     if (aFDSave < 0)
79     {
80       perror ("Error capturing standard stream to log: dup() returned");
81       return -1;
82     }
83
84     // Redirect the stream to the log file
85     if (dup2 (theFDLog, theFDStd) < 0)
86     {
87       close (aFDSave);
88       perror ("Error capturing standard stream to log: dup2() returned");
89       return -1;
90     }
91
92     // remember saved file descriptor of standard stream
93     return aFDSave;
94   }
95
96   void capture_end (int theFDStd, int& theFDSave)
97   {
98     if (theFDSave < 0)
99       return;
100
101     // restore normal descriptors of console stream
102     if (dup2(theFDSave, theFDStd) < 0)
103     {
104       perror ("Error returning capturing standard stream to log: dup2() returned");
105       return;
106     }
107
108     // close saved file descriptor
109     close(theFDSave);
110     theFDSave = -1;
111   }
112
113 } // anonymous namespace
114
115 // MKV 29.03.05
116 #if ((TCL_MAJOR_VERSION > 8) || ((TCL_MAJOR_VERSION == 8) && (TCL_MINOR_VERSION >= 4))) && !defined(USE_NON_CONST)
117 static Standard_Integer CommandCmd 
118 (ClientData theClientData, Tcl_Interp *interp,
119  Standard_Integer argc, const char* argv[])
120 #else
121 static Standard_Integer CommandCmd 
122 (ClientData theClientData, Tcl_Interp *interp,
123  Standard_Integer argc, char* argv[])
124 #endif
125 {
126   static Standard_Integer code;
127   code = TCL_OK;
128   Draw_Interpretor::CallBackData* aCallback = (Draw_Interpretor::CallBackData* )theClientData;
129   Draw_Interpretor& di = *(aCallback->myDI);
130
131   // log command execution, except commands manipulating log itself and echo
132   Standard_Boolean isLogManipulation = (strcmp (argv[0], "dlog") == 0 || 
133                                         strcmp (argv[0], "decho") == 0);
134   Standard_Boolean doLog  = (di.GetDoLog() && ! isLogManipulation);
135   Standard_Boolean doEcho = (di.GetDoEcho() && ! isLogManipulation);
136
137   // flush cerr and cout
138   flush_standard_streams();
139
140   // capture cout and cerr to log
141   int aFDstdout = STDOUT_FILENO;
142   int aFDstderr = STDERR_FILENO;
143   int aFDerr_save = -1;
144   int aFDout_save = -1;
145   if (doLog)
146   {
147     aFDout_save = capture_start (aFDstdout, di.GetLogFileDescriptor());
148     aFDerr_save = capture_start (aFDstderr, di.GetLogFileDescriptor());
149   }
150
151   if (doEcho || doLog)
152     dumpArgs (cout, argc, argv);
153
154   // run command
155   try {
156     OCC_CATCH_SIGNALS
157
158     // get exception if control-break has been pressed 
159     OSD::ControlBreak();
160
161     // OCC680: Transfer UTF-8 directly to OCC commands without locale usage
162       
163     Standard_Integer fres = aCallback->Invoke ( di, argc, argv /*anArgs.GetArgv()*/ );
164     if (fres != 0) 
165       code = TCL_ERROR;
166   }
167   catch (Standard_Failure const& anException) {
168     // fail if Draw_ExitOnCatch is set
169     // MKV 29.03.05
170 #if ((TCL_MAJOR_VERSION > 8) || ((TCL_MAJOR_VERSION == 8) && (TCL_MINOR_VERSION >= 4))) && !defined(USE_NON_CONST)
171     const char*  cc = Tcl_GetVar(interp,
172                           "Draw_ExitOnCatch",TCL_GLOBAL_ONLY);
173 #else
174     char* const cc = Tcl_GetVar(interp,
175                           "Draw_ExitOnCatch",TCL_GLOBAL_ONLY);
176 #endif
177
178     cout << "An exception was caught " << anException << endl;
179
180     if (cc && Draw::Atoi(cc)) {
181 #ifdef _WIN32
182       Tcl_Exit(0);
183 #else      
184       Tcl_Eval(interp,"exit");
185 #endif
186     }
187
188     // get the error message
189     Standard_SStream ss;
190     ss << "** Exception ** " << anException << ends;
191     Tcl_SetResult(interp,(char*)(ss.str().c_str()),TCL_VOLATILE);
192     code = TCL_ERROR;
193   }
194
195   // log command result
196   if (doLog || doEcho)
197   {
198     const char* aResultStr = Tcl_GetStringResult (interp);
199     if (aResultStr != 0 && aResultStr[0] != '\0' )
200     {
201       std::cout << aResultStr << std::endl;
202     }
203   }
204
205   // flush streams
206   flush_standard_streams();
207
208   // end capturing cout and cerr 
209   if (doLog) 
210   {
211     capture_end (aFDstderr, aFDerr_save);
212     capture_end (aFDstdout, aFDout_save);
213   }
214
215   return code;
216 }
217
218 static void CommandDelete (ClientData theClientData)
219 {
220   Draw_Interpretor::CallBackData* aCallback = (Draw_Interpretor::CallBackData* )theClientData;
221   delete aCallback;
222 }
223
224 //=======================================================================
225 //function : Draw_Interpretor
226 //purpose  : 
227 //=======================================================================
228
229 Draw_Interpretor::Draw_Interpretor() :
230   isAllocated(Standard_False), myDoLog(Standard_False), myDoEcho(Standard_False), myFDLog(-1)
231 {
232 // The tcl interpreter is not created immediately as it is kept 
233 // by a global variable and created and deleted before the main().
234   myInterp  = NULL;
235 }
236
237 //=======================================================================
238 //function : Init
239 //purpose  : It is necessary to call this function
240 //=======================================================================
241
242 void Draw_Interpretor::Init()
243 {
244   if (isAllocated) 
245     Tcl_DeleteInterp(myInterp);
246   isAllocated=Standard_True;
247   myInterp=Tcl_CreateInterp();
248 }
249
250 //=======================================================================
251 //function : Draw_Interpretor
252 //purpose  : 
253 //=======================================================================
254
255 Draw_Interpretor::Draw_Interpretor(const Draw_PInterp& p) :
256   isAllocated(Standard_False),
257   myInterp(p),
258   myDoLog(Standard_False),
259   myDoEcho(Standard_False)
260 {
261 }
262
263 //=======================================================================
264 //function : add
265 //purpose  :
266 //=======================================================================
267 void Draw_Interpretor::add (const Standard_CString          theCommandName,
268                             const Standard_CString          theHelp,
269                             const Standard_CString          theFileName,
270                             Draw_Interpretor::CallBackData* theCallback,
271                             const Standard_CString          theGroup)
272 {
273   if (myInterp == NULL)
274   {
275     Init();
276   }
277
278   Standard_PCharacter aName  = (Standard_PCharacter )theCommandName;
279   Standard_PCharacter aHelp  = (Standard_PCharacter )theHelp;
280   Standard_PCharacter aGroup = (Standard_PCharacter )theGroup;
281   Tcl_CreateCommand (myInterp, aName, CommandCmd, (ClientData )theCallback, CommandDelete);
282
283   // add the help
284   Tcl_SetVar2 (myInterp, "Draw_Helps",  aName,  aHelp, TCL_GLOBAL_ONLY);
285   Tcl_SetVar2 (myInterp, "Draw_Groups", aGroup, aName,
286                      TCL_GLOBAL_ONLY | TCL_APPEND_VALUE | TCL_LIST_ELEMENT);
287
288   // add path to source file (keep not more than two last subdirectories)
289   if (theFileName  == NULL
290    || *theFileName == '\0')
291   {
292     return;
293   }
294
295   OSD_Path aPath (theFileName);
296   Standard_Integer nbTrek = aPath.TrekLength();
297   for (Standard_Integer i = 2; i < nbTrek; ++i)
298   {
299     aPath.RemoveATrek (1);
300   }
301   aPath.SetDisk ("");
302   aPath.SetNode ("");
303   TCollection_AsciiString aSrcPath;
304   aPath.SystemName (aSrcPath);
305   if (aSrcPath.Value(1) == '/')
306     aSrcPath.Remove(1);
307   Tcl_SetVar2 (myInterp, "Draw_Files", aName, aSrcPath.ToCString(), TCL_GLOBAL_ONLY);
308 }
309
310 //=======================================================================
311 //function : Remove
312 //purpose  : 
313 //=======================================================================
314
315 Standard_Boolean Draw_Interpretor::Remove(Standard_CString const n)
316 {
317   Standard_PCharacter pN;
318   //
319   pN=(Standard_PCharacter)n;
320  
321   Standard_Integer result = Tcl_DeleteCommand(myInterp,pN);
322   return result == 0;
323 }
324
325 //=======================================================================
326 //function : Result
327 //purpose  : 
328 //=======================================================================
329
330 Standard_CString Draw_Interpretor::Result() const
331 {
332 #if ((TCL_MAJOR_VERSION > 8) || ((TCL_MAJOR_VERSION == 8) && (TCL_MINOR_VERSION >= 5)))
333   return Tcl_GetStringResult(myInterp);
334 #else
335   return myInterp->result;
336 #endif
337 }
338
339 //=======================================================================
340 //function : Reset
341 //purpose  : 
342 //=======================================================================
343
344 void Draw_Interpretor::Reset()
345 {
346   Tcl_ResetResult(myInterp);
347 }
348
349 //=======================================================================
350 //function : Append
351 //purpose  : 
352 //=======================================================================
353
354 Draw_Interpretor& Draw_Interpretor::Append(const Standard_CString s)
355 {
356 #ifdef TCL_USES_UTF8
357   // Convert string to UTF-8 format for Tcl
358   Tcl_DString TclString;
359   Tcl_ExternalToUtfDString ( NULL, s, -1, &TclString );
360   Tcl_AppendResult ( myInterp, Tcl_DStringValue ( &TclString ), (Standard_CString)0 );
361   Tcl_DStringFree ( &TclString );
362 #else
363   Tcl_AppendResult(myInterp,s,(Standard_CString)0);
364 #endif
365   return *this;
366 }
367
368 //=======================================================================
369 //function : Append
370 //purpose  : 
371 //=======================================================================
372
373 Draw_Interpretor& Draw_Interpretor::Append(const TCollection_AsciiString& s)
374 {
375   return Append (s.ToCString());
376 }
377
378 //=======================================================================
379 //function : Append
380 //purpose  : 
381 //=======================================================================
382
383 Draw_Interpretor& Draw_Interpretor::Append(const TCollection_ExtendedString& theString)
384 {
385 #ifdef TCL_USES_UTF8
386   // Convert string to UTF-8 format for Tcl
387   char *str = new char[theString.LengthOfCString()+1];
388   theString.ToUTF8CString (str);
389   Tcl_AppendResult ( myInterp, str, (Standard_CString)0 );
390   delete[] str;
391 #else
392   // put as ascii string, replacing non-ascii characters by '?'
393   TCollection_AsciiString str (theString, '?');
394   Tcl_AppendResult(myInterp,str.ToCString(),(Standard_CString)0);
395 #endif
396   return *this;
397 }
398
399 //=======================================================================
400 //function : Append
401 //purpose  : 
402 //=======================================================================
403
404 Draw_Interpretor& Draw_Interpretor::Append(const Standard_Integer i)
405 {
406   char c[100];
407   Sprintf(c,"%d",i);
408   Tcl_AppendResult(myInterp,c,(Standard_CString)0);
409   return *this;
410 }
411
412 //=======================================================================
413 //function : Append
414 //purpose  : 
415 //=======================================================================
416
417 Draw_Interpretor& Draw_Interpretor::Append(const Standard_Real r)
418 {
419   char s[100];
420   Sprintf(s,"%.17g",r);
421   Tcl_AppendResult(myInterp,s,(Standard_CString)0);
422   return *this;
423 }
424
425 //=======================================================================
426 //function : Append
427 //purpose  : 
428 //=======================================================================
429
430 Draw_Interpretor& Draw_Interpretor::Append(const Standard_SStream& s)
431 {
432   return Append (s.str().c_str());
433 }
434
435 //=======================================================================
436 //function : AppendElement
437 //purpose  : 
438 //=======================================================================
439
440 void Draw_Interpretor::AppendElement(const Standard_CString s)
441 {
442 #ifdef TCL_USES_UTF8
443   // Convert string to UTF-8 format for Tcl
444   Tcl_DString TclString;
445   Tcl_ExternalToUtfDString ( NULL, s, -1, &TclString );
446   Tcl_AppendElement ( myInterp, Tcl_DStringValue ( &TclString ) );
447   Tcl_DStringFree ( &TclString );
448 #else
449 #ifdef IRIX
450   //AppendElement is declared as (Tcl_Interp *interp, char *string)
451   //on SGI 32
452   Tcl_AppendElement(myInterp,(char*) s);
453 #else
454   Tcl_AppendElement(myInterp, s);
455 #endif
456 #endif
457 }
458
459 //=======================================================================
460 //function : Eval
461 //purpose  : 
462 //=======================================================================
463
464 Standard_Integer Draw_Interpretor::Eval(const Standard_CString line)
465 {
466   return Tcl_Eval(myInterp,line);
467 }
468
469
470 //=======================================================================
471 //function : Eval
472 //purpose  : 
473 //=======================================================================
474
475 Standard_Integer Draw_Interpretor::RecordAndEval(const Standard_CString line,
476                                                  const Standard_Integer flags)
477 {
478   return Tcl_RecordAndEval(myInterp,line,flags);
479 }
480
481 //=======================================================================
482 //function : EvalFile
483 //purpose  : 
484 //=======================================================================
485
486 Standard_Integer Draw_Interpretor::EvalFile(const Standard_CString fname)
487 {
488   return Tcl_EvalFile(myInterp,fname);
489 }
490
491 //=======================================================================
492 //function : PrintHelp
493 //purpose  :
494 //=======================================================================
495
496 Standard_Integer Draw_Interpretor::PrintHelp (const Standard_CString theCommandName)
497 {
498   TCollection_AsciiString aCmd     = TCollection_AsciiString ("help ") + theCommandName;
499   Standard_PCharacter     aLinePtr = (Standard_PCharacter )aCmd.ToCString();
500   return Tcl_Eval (myInterp, aLinePtr);
501 }
502
503 //=======================================================================
504 //function :Complete
505 //purpose  : 
506 //=======================================================================
507
508 Standard_Boolean Draw_Interpretor::Complete(const Standard_CString line)
509 {
510   Standard_PCharacter pLine;
511   //
512   pLine=(Standard_PCharacter)line;
513   return Tcl_CommandComplete (pLine) != 0;
514 }
515
516 //=======================================================================
517 //function : Destroy
518 //purpose  : 
519 //=======================================================================
520
521 Draw_Interpretor::~Draw_Interpretor()
522 {
523   SetDoLog (Standard_False);
524   if (myFDLog >=0)
525   {
526     close (myFDLog);
527     myFDLog = 0;
528   }
529
530   // MKV 01.02.05
531 #if ((TCL_MAJOR_VERSION > 8) || ((TCL_MAJOR_VERSION == 8) && (TCL_MINOR_VERSION >= 4)))
532   try {
533     OCC_CATCH_SIGNALS
534     Tcl_Exit(0);
535   }
536   catch (Standard_Failure) {
537 #ifdef OCCT_DEBUG
538     cout <<"Tcl_Exit have an exeption" << endl;
539 #endif
540   }
541 #else
542 #ifdef _WIN32
543   Tcl_Exit(0);
544 #endif  
545 #endif
546 }
547
548 //=======================================================================
549 //function : Interp
550 //purpose  : 
551 //=======================================================================
552
553 Draw_PInterp Draw_Interpretor::Interp() const
554 {
555   Standard_DomainError_Raise_if (myInterp==NULL , "No call for  Draw_Interpretor::Init()");
556   return myInterp;
557 }
558
559 void Draw_Interpretor::Set(const Draw_PInterp& PIntrp)
560 {
561   if (isAllocated)
562     Tcl_DeleteInterp(myInterp);
563   isAllocated = Standard_False;
564   myInterp = PIntrp;
565 }
566
567 //=======================================================================
568 //function : Logging
569 //purpose  : 
570 //=======================================================================
571
572 void Draw_Interpretor::SetDoLog (Standard_Boolean doLog)
573 {
574   if (myDoLog == doLog)
575     return;
576
577   // create log file if not opened yet
578   if (doLog && myFDLog < 0)
579   {
580 #ifdef _WIN32
581     char tmpfile[L_tmpnam + 1];
582     tmpnam(tmpfile);
583     myFDLog = open (tmpfile, O_RDWR | O_CREAT | O_EXCL | O_TEMPORARY, S_IREAD | S_IWRITE);
584 #else
585     // according to Linux Filesystem Hierarchy Standard, 3.17,
586     // /tmp/ is the right directory for temporary files
587     char tmpfile[256] = "/tmp/occt_draw_XXXXXX";
588     myFDLog = mkstemp (tmpfile);
589     if (myFDLog >= 0)
590     {
591 //      printf ("Tmp file: %s\n", tmpfile);
592       unlink (tmpfile); // make sure the file will be deleted on close
593     }
594 #endif
595     if (myFDLog < 0)
596     {
597       perror ("Error creating temporary file for capturing console output");
598       printf ("path: %s\n", tmpfile);
599       return;
600     }
601   }
602
603   myDoLog = doLog;
604 }
605
606 void Draw_Interpretor::SetDoEcho (Standard_Boolean doEcho)
607 {
608   myDoEcho = doEcho;
609 }
610
611 Standard_Boolean Draw_Interpretor::GetDoLog () const
612 {
613   return myDoLog;
614 }
615
616 Standard_Boolean Draw_Interpretor::GetDoEcho () const
617 {
618   return myDoEcho;
619 }
620
621 void Draw_Interpretor::ResetLog ()
622 {
623   if (myFDLog < 0)
624     return;
625
626   // flush cerr and cout, for the case if they are bound to the log
627   flush_standard_streams();
628
629   lseek (myFDLog, 0, SEEK_SET);
630
631 #ifdef _WIN32
632   if (_chsize_s (myFDLog, 0) != 0)
633 #else
634   if (ftruncate (myFDLog, 0) != 0)
635 #endif
636   {
637     perror ("Error truncating the console log");
638   }
639 }
640
641 void Draw_Interpretor::AddLog (const Standard_CString theStr)
642 {
643   if (myFDLog < 0 || ! theStr || ! theStr[0])
644     return;
645
646   // flush cerr and cout, for the case if they are bound to the log
647   flush_standard_streams();
648
649   // write as plain bytes
650   if (write (myFDLog, theStr, (unsigned int)strlen(theStr)) <0)
651   {
652     perror ("Error writing to console log");
653   }
654 }
655
656 TCollection_AsciiString Draw_Interpretor::GetLog ()
657 {
658   TCollection_AsciiString aLog;
659   if (myFDLog < 0)
660     return aLog;
661
662   // flush cerr and cout
663   flush_standard_streams();
664
665   // rewind the file to its start
666   lseek (myFDLog, 0, SEEK_SET);
667
668   // read the whole log to string; this implementation
669   // is not optimized but should be sufficient
670   const int BUFSIZE = 4096;
671   char buffer[BUFSIZE + 1];
672   for (;;)
673   {
674     int nbRead = read (myFDLog, buffer, BUFSIZE);
675     if (nbRead <= 0)
676     {
677       break;
678     }
679     buffer[nbRead] = '\0';
680     aLog.AssignCat (buffer);
681   }
682
683   return aLog;
684 }