0030579: Draw Harness, Draw_Interpretor - catch exceptions other than Standard_Failure
[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 static Standard_Integer CommandCmd (ClientData theClientData, Tcl_Interp* interp, Standard_Integer argc, const char* argv[])
116 {
117   static Standard_Integer code;
118   code = TCL_OK;
119   Draw_Interpretor::CallBackData* aCallback = (Draw_Interpretor::CallBackData* )theClientData;
120   Draw_Interpretor& di = *(aCallback->myDI);
121
122   // log command execution, except commands manipulating log itself and echo
123   Standard_Boolean isLogManipulation = (strcmp (argv[0], "dlog") == 0 || 
124                                         strcmp (argv[0], "decho") == 0);
125   Standard_Boolean doLog  = (di.GetDoLog() && ! isLogManipulation);
126   Standard_Boolean doEcho = (di.GetDoEcho() && ! isLogManipulation);
127
128   // flush cerr and cout
129   flush_standard_streams();
130
131   // capture cout and cerr to log
132   int aFDstdout = STDOUT_FILENO;
133   int aFDstderr = STDERR_FILENO;
134   int aFDerr_save = -1;
135   int aFDout_save = -1;
136   if (doLog)
137   {
138     aFDout_save = capture_start (aFDstdout, di.GetLogFileDescriptor());
139     aFDerr_save = capture_start (aFDstderr, di.GetLogFileDescriptor());
140   }
141
142   if (doEcho || doLog)
143     dumpArgs (cout, argc, argv);
144
145   // run command
146   try {
147     OCC_CATCH_SIGNALS
148
149     // get exception if control-break has been pressed 
150     OSD::ControlBreak();
151
152     // OCC680: Transfer UTF-8 directly to OCC commands without locale usage
153       
154     Standard_Integer fres = aCallback->Invoke ( di, argc, argv /*anArgs.GetArgv()*/ );
155     if (fres != 0) 
156       code = TCL_ERROR;
157   }
158   catch (Standard_Failure const& anException) {
159     // fail if Draw_ExitOnCatch is set
160     std::cout << "An exception was caught " << anException << std::endl;
161     const char* toExitOnCatch = Tcl_GetVar (interp, "Draw_ExitOnCatch", TCL_GLOBAL_ONLY);
162     if (toExitOnCatch != NULL && Draw::Atoi (toExitOnCatch))
163     {
164 #ifdef _WIN32
165       Tcl_Exit(0);
166 #else      
167       Tcl_Eval(interp,"exit");
168 #endif
169     }
170
171     // get the error message
172     Standard_SStream ss;
173     ss << "** Exception ** " << anException << ends;
174     Tcl_SetResult(interp,(char*)(ss.str().c_str()),TCL_VOLATILE);
175     code = TCL_ERROR;
176   }
177   catch (std::exception const& theStdException)
178   {
179     std::cout << "An exception was caught " << theStdException.what() << " [" << typeid(theStdException).name() << "]" << std::endl;
180     const char* toExitOnCatch = Tcl_GetVar (interp, "Draw_ExitOnCatch", TCL_GLOBAL_ONLY);
181     if (toExitOnCatch != NULL && Draw::Atoi (toExitOnCatch))
182     {
183     #ifdef _WIN32
184       Tcl_Exit (0);
185     #else
186       Tcl_Eval (interp, "exit");
187     #endif
188     }
189
190     // get the error message
191     Standard_SStream ss;
192     ss << "** Exception ** " << theStdException.what() << " [" << typeid(theStdException).name() << "]" << ends;
193     Tcl_SetResult (interp, (char*)(ss.str().c_str()), TCL_VOLATILE);
194     code = TCL_ERROR;
195   }
196   catch (...)
197   {
198     std::cout << "UNKNOWN exception was caught " << std::endl;
199     const char* toExitOnCatch = Tcl_GetVar (interp, "Draw_ExitOnCatch", TCL_GLOBAL_ONLY);
200     if (toExitOnCatch != NULL && Draw::Atoi (toExitOnCatch))
201     {
202     #ifdef _WIN32
203       Tcl_Exit (0);
204     #else
205       Tcl_Eval (interp,"exit");
206     #endif
207     }
208
209     // get the error message
210     Standard_SStream ss;
211     ss << "** Exception ** UNKNOWN" << ends;
212     Tcl_SetResult (interp, (char* )(ss.str().c_str()), TCL_VOLATILE);
213     code = TCL_ERROR;
214   }
215
216   // log command result
217   if (doLog || doEcho)
218   {
219     const char* aResultStr = Tcl_GetStringResult (interp);
220     if (aResultStr != 0 && aResultStr[0] != '\0' )
221     {
222       std::cout << aResultStr << std::endl;
223     }
224   }
225
226   // flush streams
227   flush_standard_streams();
228
229   // end capturing cout and cerr 
230   if (doLog) 
231   {
232     capture_end (aFDstderr, aFDerr_save);
233     capture_end (aFDstdout, aFDout_save);
234   }
235
236   return code;
237 }
238
239 static void CommandDelete (ClientData theClientData)
240 {
241   Draw_Interpretor::CallBackData* aCallback = (Draw_Interpretor::CallBackData* )theClientData;
242   delete aCallback;
243 }
244
245 //=======================================================================
246 //function : Draw_Interpretor
247 //purpose  : 
248 //=======================================================================
249
250 Draw_Interpretor::Draw_Interpretor() :
251   isAllocated(Standard_False), myDoLog(Standard_False), myDoEcho(Standard_False), myFDLog(-1)
252 {
253 // The tcl interpreter is not created immediately as it is kept 
254 // by a global variable and created and deleted before the main().
255   myInterp  = NULL;
256 }
257
258 //=======================================================================
259 //function : Init
260 //purpose  : It is necessary to call this function
261 //=======================================================================
262
263 void Draw_Interpretor::Init()
264 {
265   if (isAllocated) 
266     Tcl_DeleteInterp(myInterp);
267   isAllocated=Standard_True;
268   myInterp=Tcl_CreateInterp();
269 }
270
271 //=======================================================================
272 //function : Draw_Interpretor
273 //purpose  : 
274 //=======================================================================
275
276 Draw_Interpretor::Draw_Interpretor(const Draw_PInterp& p) :
277   isAllocated(Standard_False),
278   myInterp(p),
279   myDoLog(Standard_False),
280   myDoEcho(Standard_False)
281 {
282 }
283
284 //=======================================================================
285 //function : add
286 //purpose  :
287 //=======================================================================
288 void Draw_Interpretor::add (const Standard_CString          theCommandName,
289                             const Standard_CString          theHelp,
290                             const Standard_CString          theFileName,
291                             Draw_Interpretor::CallBackData* theCallback,
292                             const Standard_CString          theGroup)
293 {
294   Standard_ASSERT_RAISE (myInterp != NULL, "Attempt to add command to Null interpretor");
295
296   Standard_PCharacter aName  = (Standard_PCharacter )theCommandName;
297   Standard_PCharacter aHelp  = (Standard_PCharacter )theHelp;
298   Standard_PCharacter aGroup = (Standard_PCharacter )theGroup;
299   Tcl_CreateCommand (myInterp, aName, CommandCmd, (ClientData )theCallback, CommandDelete);
300
301   // add the help
302   Tcl_SetVar2 (myInterp, "Draw_Helps",  aName,  aHelp, TCL_GLOBAL_ONLY);
303   Tcl_SetVar2 (myInterp, "Draw_Groups", aGroup, aName,
304                      TCL_GLOBAL_ONLY | TCL_APPEND_VALUE | TCL_LIST_ELEMENT);
305
306   // add path to source file (keep not more than two last subdirectories)
307   if (theFileName  == NULL
308    || *theFileName == '\0')
309   {
310     return;
311   }
312
313   OSD_Path aPath (theFileName);
314   Standard_Integer nbTrek = aPath.TrekLength();
315   for (Standard_Integer i = 2; i < nbTrek; ++i)
316   {
317     aPath.RemoveATrek (1);
318   }
319   aPath.SetDisk ("");
320   aPath.SetNode ("");
321   TCollection_AsciiString aSrcPath;
322   aPath.SystemName (aSrcPath);
323   if (aSrcPath.Value(1) == '/')
324     aSrcPath.Remove(1);
325   Tcl_SetVar2 (myInterp, "Draw_Files", aName, aSrcPath.ToCString(), TCL_GLOBAL_ONLY);
326 }
327
328 //=======================================================================
329 //function : Remove
330 //purpose  : 
331 //=======================================================================
332
333 Standard_Boolean Draw_Interpretor::Remove(Standard_CString const n)
334 {
335   Standard_PCharacter pN;
336   //
337   pN=(Standard_PCharacter)n;
338  
339   Standard_Integer result = Tcl_DeleteCommand(myInterp,pN);
340   return result == 0;
341 }
342
343 //=======================================================================
344 //function : Result
345 //purpose  : 
346 //=======================================================================
347
348 Standard_CString Draw_Interpretor::Result() const
349 {
350 #if ((TCL_MAJOR_VERSION > 8) || ((TCL_MAJOR_VERSION == 8) && (TCL_MINOR_VERSION >= 5)))
351   return Tcl_GetStringResult(myInterp);
352 #else
353   return myInterp->result;
354 #endif
355 }
356
357 //=======================================================================
358 //function : Reset
359 //purpose  : 
360 //=======================================================================
361
362 void Draw_Interpretor::Reset()
363 {
364   Tcl_ResetResult(myInterp);
365 }
366
367 //=======================================================================
368 //function : Append
369 //purpose  : 
370 //=======================================================================
371
372 Draw_Interpretor& Draw_Interpretor::Append(const Standard_CString s)
373 {
374 #ifdef TCL_USES_UTF8
375   // Convert string to UTF-8 format for Tcl
376   Tcl_DString TclString;
377   Tcl_ExternalToUtfDString ( NULL, s, -1, &TclString );
378   Tcl_AppendResult ( myInterp, Tcl_DStringValue ( &TclString ), (Standard_CString)0 );
379   Tcl_DStringFree ( &TclString );
380 #else
381   Tcl_AppendResult(myInterp,s,(Standard_CString)0);
382 #endif
383   return *this;
384 }
385
386 //=======================================================================
387 //function : Append
388 //purpose  : 
389 //=======================================================================
390
391 Draw_Interpretor& Draw_Interpretor::Append(const TCollection_AsciiString& s)
392 {
393   return Append (s.ToCString());
394 }
395
396 //=======================================================================
397 //function : Append
398 //purpose  : 
399 //=======================================================================
400
401 Draw_Interpretor& Draw_Interpretor::Append(const TCollection_ExtendedString& theString)
402 {
403 #ifdef TCL_USES_UTF8
404   // Convert string to UTF-8 format for Tcl
405   char *str = new char[theString.LengthOfCString()+1];
406   theString.ToUTF8CString (str);
407   Tcl_AppendResult ( myInterp, str, (Standard_CString)0 );
408   delete[] str;
409 #else
410   // put as ascii string, replacing non-ascii characters by '?'
411   TCollection_AsciiString str (theString, '?');
412   Tcl_AppendResult(myInterp,str.ToCString(),(Standard_CString)0);
413 #endif
414   return *this;
415 }
416
417 //=======================================================================
418 //function : Append
419 //purpose  : 
420 //=======================================================================
421
422 Draw_Interpretor& Draw_Interpretor::Append(const Standard_Integer i)
423 {
424   char c[100];
425   Sprintf(c,"%d",i);
426   Tcl_AppendResult(myInterp,c,(Standard_CString)0);
427   return *this;
428 }
429
430 //=======================================================================
431 //function : Append
432 //purpose  : 
433 //=======================================================================
434
435 Draw_Interpretor& Draw_Interpretor::Append(const Standard_Real r)
436 {
437   char s[100];
438   Sprintf(s,"%.17g",r);
439   Tcl_AppendResult(myInterp,s,(Standard_CString)0);
440   return *this;
441 }
442
443 //=======================================================================
444 //function : Append
445 //purpose  : 
446 //=======================================================================
447
448 Draw_Interpretor& Draw_Interpretor::Append(const Standard_SStream& s)
449 {
450   return Append (s.str().c_str());
451 }
452
453 //=======================================================================
454 //function : AppendElement
455 //purpose  : 
456 //=======================================================================
457
458 void Draw_Interpretor::AppendElement(const Standard_CString s)
459 {
460 #ifdef TCL_USES_UTF8
461   // Convert string to UTF-8 format for Tcl
462   Tcl_DString TclString;
463   Tcl_ExternalToUtfDString ( NULL, s, -1, &TclString );
464   Tcl_AppendElement ( myInterp, Tcl_DStringValue ( &TclString ) );
465   Tcl_DStringFree ( &TclString );
466 #else
467 #ifdef IRIX
468   //AppendElement is declared as (Tcl_Interp *interp, char *string)
469   //on SGI 32
470   Tcl_AppendElement(myInterp,(char*) s);
471 #else
472   Tcl_AppendElement(myInterp, s);
473 #endif
474 #endif
475 }
476
477 //=======================================================================
478 //function : Eval
479 //purpose  : 
480 //=======================================================================
481
482 Standard_Integer Draw_Interpretor::Eval(const Standard_CString line)
483 {
484   return Tcl_Eval(myInterp,line);
485 }
486
487
488 //=======================================================================
489 //function : Eval
490 //purpose  : 
491 //=======================================================================
492
493 Standard_Integer Draw_Interpretor::RecordAndEval(const Standard_CString line,
494                                                  const Standard_Integer flags)
495 {
496   return Tcl_RecordAndEval(myInterp,line,flags);
497 }
498
499 //=======================================================================
500 //function : EvalFile
501 //purpose  : 
502 //=======================================================================
503
504 Standard_Integer Draw_Interpretor::EvalFile(const Standard_CString fname)
505 {
506   return Tcl_EvalFile(myInterp,fname);
507 }
508
509 //=======================================================================
510 //function : PrintHelp
511 //purpose  :
512 //=======================================================================
513
514 Standard_Integer Draw_Interpretor::PrintHelp (const Standard_CString theCommandName)
515 {
516   TCollection_AsciiString aCmd     = TCollection_AsciiString ("help ") + theCommandName;
517   Standard_PCharacter     aLinePtr = (Standard_PCharacter )aCmd.ToCString();
518   return Tcl_Eval (myInterp, aLinePtr);
519 }
520
521 //=======================================================================
522 //function :Complete
523 //purpose  : 
524 //=======================================================================
525
526 Standard_Boolean Draw_Interpretor::Complete(const Standard_CString line)
527 {
528   Standard_PCharacter pLine;
529   //
530   pLine=(Standard_PCharacter)line;
531   return Tcl_CommandComplete (pLine) != 0;
532 }
533
534 //=======================================================================
535 //function : Destroy
536 //purpose  : 
537 //=======================================================================
538
539 Draw_Interpretor::~Draw_Interpretor()
540 {
541   SetDoLog (Standard_False);
542   if (myFDLog >=0)
543   {
544     close (myFDLog);
545     myFDLog = 0;
546   }
547
548   // MKV 01.02.05
549 #if ((TCL_MAJOR_VERSION > 8) || ((TCL_MAJOR_VERSION == 8) && (TCL_MINOR_VERSION >= 4)))
550   try {
551     OCC_CATCH_SIGNALS
552     Tcl_Exit(0);
553   }
554   catch (Standard_Failure) {
555 #ifdef OCCT_DEBUG
556     cout <<"Tcl_Exit have an exeption" << endl;
557 #endif
558   }
559 #else
560 #ifdef _WIN32
561   Tcl_Exit(0);
562 #endif  
563 #endif
564 }
565
566 //=======================================================================
567 //function : Interp
568 //purpose  : 
569 //=======================================================================
570
571 Draw_PInterp Draw_Interpretor::Interp() const
572 {
573   Standard_DomainError_Raise_if (myInterp==NULL , "No call for  Draw_Interpretor::Init()");
574   return myInterp;
575 }
576
577 void Draw_Interpretor::Set(const Draw_PInterp& PIntrp)
578 {
579   if (isAllocated)
580     Tcl_DeleteInterp(myInterp);
581   isAllocated = Standard_False;
582   myInterp = PIntrp;
583 }
584
585 //=======================================================================
586 //function : Logging
587 //purpose  : 
588 //=======================================================================
589
590 void Draw_Interpretor::SetDoLog (Standard_Boolean doLog)
591 {
592   if (myDoLog == doLog)
593     return;
594
595   // create log file if not opened yet
596   if (doLog && myFDLog < 0)
597   {
598 #ifdef _WIN32
599     char tmpfile[L_tmpnam + 1];
600     tmpnam(tmpfile);
601     myFDLog = open (tmpfile, O_RDWR | O_CREAT | O_EXCL | O_TEMPORARY, S_IREAD | S_IWRITE);
602 #else
603     // according to Linux Filesystem Hierarchy Standard, 3.17,
604     // /tmp/ is the right directory for temporary files
605     char tmpfile[256] = "/tmp/occt_draw_XXXXXX";
606     myFDLog = mkstemp (tmpfile);
607     if (myFDLog >= 0)
608     {
609 //      printf ("Tmp file: %s\n", tmpfile);
610       unlink (tmpfile); // make sure the file will be deleted on close
611     }
612 #endif
613     if (myFDLog < 0)
614     {
615       perror ("Error creating temporary file for capturing console output");
616       printf ("path: %s\n", tmpfile);
617       return;
618     }
619   }
620
621   myDoLog = doLog;
622 }
623
624 void Draw_Interpretor::SetDoEcho (Standard_Boolean doEcho)
625 {
626   myDoEcho = doEcho;
627 }
628
629 Standard_Boolean Draw_Interpretor::GetDoLog () const
630 {
631   return myDoLog;
632 }
633
634 Standard_Boolean Draw_Interpretor::GetDoEcho () const
635 {
636   return myDoEcho;
637 }
638
639 void Draw_Interpretor::ResetLog ()
640 {
641   if (myFDLog < 0)
642     return;
643
644   // flush cerr and cout, for the case if they are bound to the log
645   flush_standard_streams();
646
647   lseek (myFDLog, 0, SEEK_SET);
648
649 #ifdef _WIN32
650   if (_chsize_s (myFDLog, 0) != 0)
651 #else
652   if (ftruncate (myFDLog, 0) != 0)
653 #endif
654   {
655     perror ("Error truncating the console log");
656   }
657 }
658
659 void Draw_Interpretor::AddLog (const Standard_CString theStr)
660 {
661   if (myFDLog < 0 || ! theStr || ! theStr[0])
662     return;
663
664   // flush cerr and cout, for the case if they are bound to the log
665   flush_standard_streams();
666
667   // write as plain bytes
668   if (write (myFDLog, theStr, (unsigned int)strlen(theStr)) <0)
669   {
670     perror ("Error writing to console log");
671   }
672 }
673
674 TCollection_AsciiString Draw_Interpretor::GetLog ()
675 {
676   TCollection_AsciiString aLog;
677   if (myFDLog < 0)
678     return aLog;
679
680   // flush cerr and cout
681   flush_standard_streams();
682
683   // rewind the file to its start
684   lseek (myFDLog, 0, SEEK_SET);
685
686   // read the whole log to string; this implementation
687   // is not optimized but should be sufficient
688   const int BUFSIZE = 4096;
689   char buffer[BUFSIZE + 1];
690   for (;;)
691   {
692     int nbRead = read (myFDLog, buffer, BUFSIZE);
693     if (nbRead <= 0)
694     {
695       break;
696     }
697     buffer[nbRead] = '\0';
698     aLog.AssignCat (buffer);
699   }
700
701   return aLog;
702 }