resetting manifest requested domain to floor
[platform/upstream/expect.git] / exp_log.c
1 /* exp_log.c - logging routines and other things common to both Expect
2    program and library.  Note that this file must NOT have any
3    references to Tcl except for including tclInt.h
4 */
5
6 #include "expect_cf.h"
7 #include <stdio.h>
8 /*#include <varargs.h>          tclInt.h drags in varargs.h.  Since Pyramid */
9 /*                              objects to including varargs.h twice, just */
10 /*                              omit this one. */
11 #include "tclInt.h"
12 #ifdef NO_STDLIB_H
13 #include "../compat/stdlib.h"
14 #else
15 #include <stdlib.h>             /* for malloc */
16 #endif
17 #include <ctype.h>
18
19 #include "expect_comm.h"
20 #include "exp_int.h"
21 #include "exp_rename.h"
22 #include "exp_command.h"
23 #include "exp_log.h"
24
25 typedef struct ThreadSpecificData {
26     Tcl_Channel diagChannel;
27     Tcl_DString diagFilename;
28     int diagToStderr;
29
30     Tcl_Channel logChannel;
31     Tcl_DString logFilename;    /* if no name, then it came from -open or -leaveopen */
32     int logAppend;
33     int logLeaveOpen;
34     int logAll;                 /* if TRUE, write log of all interactions
35                                  * despite value of logUser - i.e., even if
36                                  * user is not seeing it (via stdout)
37                                  */
38     int logUser;                /* TRUE if user sees interactions on stdout */
39 } ThreadSpecificData;
40
41 static Tcl_ThreadDataKey dataKey;
42
43 /*
44  * create a reasonably large buffer for the bulk of the output routines
45  * that are not too large
46  */
47 static char bigbuf[2000];
48
49 static void expDiagWriteCharsUni _ANSI_ARGS_((Tcl_UniChar *str,int len));
50
51 /*
52  * Following this are several functions that log the conversation.  Some
53  * general notes on all of them:
54  */
55
56 /*
57  * ignore sprintf return value ("character count") because it's not
58  * defined in terms of UTF so it would be misinterpreted if we passed
59  * it on.
60  */
61
62 /*
63  * if necessary, they could be made more efficient by skipping vsprintf based
64  * on booleans
65  */
66
67 /* Most of them have multiple calls to printf-style functions.  */
68 /* At first glance, it seems stupid to reformat the same arguments again */
69 /* but we have no way of telling how long the formatted output will be */
70 /* and hence cannot allocate a buffer to do so. */
71 /* Fortunately, in production code, most of the duplicate reformatting */
72 /* will be skipped, since it is due to handling errors and debugging. */
73
74 /*
75  * Name: expWriteBytesAndLogIfTtyU
76  *
77  * Output to channel (and log if channel is stdout or devtty)
78  *
79  * Returns: TCL_OK or TCL_ERROR;
80  */
81
82 int
83 expWriteBytesAndLogIfTtyU(esPtr,buf,lenChars)
84     ExpState *esPtr;
85     Tcl_UniChar *buf;
86     int lenChars;
87 {
88     int wc;
89     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
90
91     if (esPtr->valid)
92         wc = expWriteCharsUni(esPtr,buf,lenChars);
93
94     if (tsdPtr->logChannel && ((esPtr->fdout == 1) || expDevttyIs(esPtr))) {
95       Tcl_DString ds;
96       Tcl_DStringInit (&ds);
97       Tcl_UniCharToUtfDString (buf,lenChars,&ds);
98       Tcl_WriteChars(tsdPtr->logChannel,Tcl_DStringValue (&ds), Tcl_DStringLength (&ds));
99       Tcl_DStringFree (&ds);
100     }
101     return wc;
102 }
103
104 /*
105  * Name: expLogDiagU
106  *
107  * Send to the Log (and Diag if open).  This is for writing to the log.
108  * (In contrast, expDiagLog... is for writing diagnostics.)
109  */
110
111 void
112 expLogDiagU(buf)
113 char *buf;
114 {
115     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
116
117     expDiagWriteChars(buf,-1);
118     if (tsdPtr->logChannel) {
119         Tcl_WriteChars(tsdPtr->logChannel, buf, -1);
120     }
121 }
122
123 /*
124  * Name: expLogInteractionU
125  *
126  * Show chars to user if they've requested it, UNLESS they're seeing it
127  * already because they're typing it and tty driver is echoing it.
128  * Also send to Diag and Log if appropriate.
129  */
130 void
131 expLogInteractionU(esPtr,buf,buflen)
132     ExpState *esPtr;
133     Tcl_UniChar *buf;
134     int buflen;
135 {
136     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
137
138     if (tsdPtr->logAll || (tsdPtr->logUser && tsdPtr->logChannel)) {
139       Tcl_DString ds;
140       Tcl_DStringInit (&ds);
141       Tcl_UniCharToUtfDString (buf,buflen,&ds);
142       Tcl_WriteChars(tsdPtr->logChannel,Tcl_DStringValue (&ds), Tcl_DStringLength (&ds));
143       Tcl_DStringFree (&ds);
144     }
145
146     /* hmm.... if stdout is closed such as by disconnect, loguser
147        should be forced FALSE */
148
149     /* don't write to user if they're seeing it already, i.e., typing it! */
150     if (tsdPtr->logUser && (!expStdinoutIs(esPtr)) && (!expDevttyIs(esPtr))) {
151         ExpState *stdinout = expStdinoutGet();
152         if (stdinout->valid) {
153             (void) expWriteCharsUni(stdinout,buf,buflen);
154         }
155     }
156     expDiagWriteCharsUni(buf,buflen);
157 }
158
159 /* send to log if open */
160 /* send to stderr if debugging enabled */
161 /* use this for logging everything but the parent/child conversation */
162 /* (this turns out to be almost nothing) */
163 /* uppercase L differentiates if from math function of same name */
164 #define LOGUSER         (tsdPtr->logUser || force_stdout)
165 /*VARARGS*/
166 void
167 expStdoutLog TCL_VARARGS_DEF(int,arg1)
168 {
169     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
170     int force_stdout;
171     char *fmt;
172     va_list args;
173
174     force_stdout = TCL_VARARGS_START(int,arg1,args);
175     fmt = va_arg(args,char *);
176
177     if ((!tsdPtr->logUser) && (!force_stdout) && (!tsdPtr->logAll)) return;
178
179     (void) vsprintf(bigbuf,fmt,args);
180     expDiagWriteBytes(bigbuf,-1);
181     if (tsdPtr->logAll || (LOGUSER && tsdPtr->logChannel)) Tcl_WriteChars(tsdPtr->logChannel,bigbuf,-1);
182     if (LOGUSER) fprintf(stdout,"%s",bigbuf);
183     va_end(args);
184 }
185
186 /* just like log but does no formatting */
187 /* send to log if open */
188 /* use this function for logging the parent/child conversation */
189 void
190 expStdoutLogU(buf,force_stdout)
191 char *buf;
192 int force_stdout;       /* override value of logUser */
193 {
194     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
195     int length;
196
197     if ((!tsdPtr->logUser) && (!force_stdout) && (!tsdPtr->logAll)) return;
198
199     length = strlen(buf);
200     expDiagWriteBytes(buf,length);
201     if (tsdPtr->logAll || (LOGUSER && tsdPtr->logChannel)) Tcl_WriteChars(tsdPtr->logChannel,buf,-1);
202     if (LOGUSER) {
203 #if (TCL_MAJOR_VERSION > 8) || ((TCL_MAJOR_VERSION == 8) && (TCL_MINOR_VERSION >= 1))
204       Tcl_WriteChars (Tcl_GetStdChannel (TCL_STDOUT), buf, length);
205       Tcl_Flush      (Tcl_GetStdChannel (TCL_STDOUT));
206 #else
207       fwrite(buf,1,length,stdout);
208 #endif
209     }
210 }
211
212 /* send to log if open */
213 /* send to stderr */
214 /* use this function for error conditions */
215 /*VARARGS*/
216 void
217 expErrorLog TCL_VARARGS_DEF(char *,arg1)
218 {
219     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
220
221     char *fmt;
222     va_list args;
223
224     fmt = TCL_VARARGS_START(char *,arg1,args);
225     (void) vsprintf(bigbuf,fmt,args);
226
227     expDiagWriteChars(bigbuf,-1);
228     fprintf(stderr,"%s",bigbuf);
229     if (tsdPtr->logChannel) Tcl_WriteChars(tsdPtr->logChannel,bigbuf,-1);
230     
231     va_end(args);
232 }
233
234 /* just like errorlog but does no formatting */
235 /* send to log if open */
236 /* use this function for logging the parent/child conversation */
237 /*ARGSUSED*/
238 void
239 expErrorLogU(buf)
240 char *buf;
241 {
242     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
243
244     int length = strlen(buf);
245     fwrite(buf,1,length,stderr);
246     expDiagWriteChars(buf,-1);
247     if (tsdPtr->logChannel) Tcl_WriteChars(tsdPtr->logChannel,buf,-1);
248 }
249
250
251
252 /* send diagnostics to Diag, Log, and stderr */
253 /* use this function for recording unusual things in the log */
254 /*VARARGS*/
255 void
256 expDiagLog TCL_VARARGS_DEF(char *,arg1)
257 {
258     char *fmt;
259     va_list args;
260
261     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
262
263     if ((tsdPtr->diagToStderr == 0) && (tsdPtr->diagChannel == 0)) return;
264
265     fmt = TCL_VARARGS_START(char *,arg1,args);
266
267     (void) vsprintf(bigbuf,fmt,args);
268
269     expDiagWriteBytes(bigbuf,-1);
270     if (tsdPtr->diagToStderr) {
271         fprintf(stderr,"%s",bigbuf);
272         if (tsdPtr->logChannel) Tcl_WriteChars(tsdPtr->logChannel,bigbuf,-1);
273     }
274
275     va_end(args);
276 }
277
278
279 /* expDiagLog for unformatted strings
280    this also takes care of arbitrary large strings */
281 void
282 expDiagLogU(str)
283 char *str;
284 {
285     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
286
287     if ((tsdPtr->diagToStderr == 0) && (tsdPtr->diagChannel == 0)) return;
288
289     expDiagWriteBytes(str,-1);
290
291     if (tsdPtr->diagToStderr) {
292       fprintf(stderr,"%s",str);
293       if (tsdPtr->logChannel) Tcl_WriteChars(tsdPtr->logChannel,str,-1);
294     }
295 }
296
297 /* expPrintf prints to stderr.  It's just a utility for making
298    debugging easier. */
299
300 /*VARARGS*/
301 void
302 expPrintf TCL_VARARGS_DEF(char *,arg1)
303 {
304   char *fmt;
305   va_list args;
306   char bigbuf[2000];
307   int len, rc;
308
309   fmt = TCL_VARARGS_START(char *,arg1,args);
310   len = vsprintf(bigbuf,arg1,args);
311  retry:
312   rc = write(2,bigbuf,len);
313   if ((rc == -1) && (errno == EAGAIN)) goto retry;
314
315   va_end(args);
316 }
317
318
319 void
320 expDiagToStderrSet(val)
321     int val;
322 {
323     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
324
325     tsdPtr->diagToStderr = val;
326 }
327     
328
329 int
330 expDiagToStderrGet() {
331     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
332     return tsdPtr->diagToStderr;
333 }
334
335 Tcl_Channel
336 expDiagChannelGet()
337 {
338     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
339     return tsdPtr->diagChannel;
340 }
341
342 void
343 expDiagChannelClose(interp)
344     Tcl_Interp *interp;
345 {
346     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
347
348     if (!tsdPtr->diagChannel) return;
349     Tcl_UnregisterChannel(interp,tsdPtr->diagChannel);
350     Tcl_DStringFree(&tsdPtr->diagFilename);
351     tsdPtr->diagChannel = 0;
352 }
353
354 /* currently this registers the channel, however the exp_internal
355    command doesn't currently give the channel name to the user so
356    this is kind of useless - but we might change this someday */
357 int
358 expDiagChannelOpen(interp,filename)
359     Tcl_Interp *interp;
360     char *filename;
361 {
362     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
363     char *newfilename;
364
365     Tcl_ResetResult(interp);
366     newfilename = Tcl_TranslateFileName(interp,filename,&tsdPtr->diagFilename);
367     if (!newfilename) return TCL_ERROR;
368
369     /* Tcl_TildeSubst doesn't store into dstring */
370     /* if no ~, so force string into dstring */
371     /* this is only needed so that next time around */
372     /* we can get dstring for -info if necessary */
373     if (Tcl_DStringValue(&tsdPtr->diagFilename)[0] == '\0') {
374         Tcl_DStringAppend(&tsdPtr->diagFilename,filename,-1);
375     }
376
377     tsdPtr->diagChannel = Tcl_OpenFileChannel(interp,newfilename,"a",0666);
378     if (!tsdPtr->diagChannel) {
379         Tcl_DStringFree(&tsdPtr->diagFilename);
380         return TCL_ERROR;
381     }
382     Tcl_RegisterChannel(interp,tsdPtr->diagChannel);
383     Tcl_SetChannelOption(interp,tsdPtr->diagChannel,"-buffering","none");
384     return TCL_OK;
385 }
386
387 void
388 expDiagWriteObj(obj)
389     Tcl_Obj *obj;
390 {
391     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
392
393     if (!tsdPtr->diagChannel) return;
394
395     Tcl_WriteObj(tsdPtr->diagChannel,obj);
396 }
397
398 /* write 8-bit bytes */
399 void
400 expDiagWriteBytes(str,len)
401 char *str;
402 int len;
403 {
404     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
405
406     if (!tsdPtr->diagChannel) return;
407
408     Tcl_Write(tsdPtr->diagChannel,str,len);
409 }
410
411 /* write UTF chars */
412 void
413 expDiagWriteChars(str,len)
414 char *str;
415 int len;
416 {
417     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
418
419     if (!tsdPtr->diagChannel) return;
420
421     Tcl_WriteChars(tsdPtr->diagChannel,str,len);
422 }
423
424 /* write Unicode chars */
425 static void
426 expDiagWriteCharsUni(str,len)
427 Tcl_UniChar *str;
428 int len;
429 {
430     Tcl_DString ds;
431     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
432
433     if (!tsdPtr->diagChannel) return;
434
435     Tcl_DStringInit (&ds);
436     Tcl_UniCharToUtfDString (str,len,&ds);
437     Tcl_WriteChars(tsdPtr->diagChannel,Tcl_DStringValue (&ds), Tcl_DStringLength (&ds));
438     Tcl_DStringFree (&ds);
439 }
440
441 char *
442 expDiagFilename()
443 {
444     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
445
446     return Tcl_DStringValue(&tsdPtr->diagFilename);
447 }
448
449 void
450 expLogChannelClose(interp)
451     Tcl_Interp *interp;
452 {
453     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
454
455     if (!tsdPtr->logChannel) return;
456
457     if (Tcl_DStringLength(&tsdPtr->logFilename)) {
458         /* it's a channel that we created */
459         Tcl_UnregisterChannel(interp,tsdPtr->logChannel);
460         Tcl_DStringFree(&tsdPtr->logFilename);
461     } else {
462         /* it's a channel that tcl::open created */
463         if (!tsdPtr->logLeaveOpen) {
464             Tcl_UnregisterChannel(interp,tsdPtr->logChannel);
465         }
466     }
467     tsdPtr->logChannel = 0;
468     tsdPtr->logAll = 0; /* can't write to log if none open! */
469 }
470
471 /* currently this registers the channel, however the exp_log_file
472    command doesn't currently give the channel name to the user so
473    this is kind of useless - but we might change this someday */
474 int
475 expLogChannelOpen(interp,filename,append)
476     Tcl_Interp *interp;
477     char *filename;
478     int append;
479 {
480     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
481     char *newfilename;
482     char mode[2];
483
484     if (append) {
485       strcpy(mode,"a");
486     } else {
487       strcpy(mode,"w");
488     }
489
490     Tcl_ResetResult(interp);
491     newfilename = Tcl_TranslateFileName(interp,filename,&tsdPtr->logFilename);
492     if (!newfilename) return TCL_ERROR;
493
494     /* Tcl_TildeSubst doesn't store into dstring */
495     /* if no ~, so force string into dstring */
496     /* this is only needed so that next time around */
497     /* we can get dstring for -info if necessary */
498     if (Tcl_DStringValue(&tsdPtr->logFilename)[0] == '\0') {
499         Tcl_DStringAppend(&tsdPtr->logFilename,filename,-1);
500     }
501
502     tsdPtr->logChannel = Tcl_OpenFileChannel(interp,newfilename,mode,0666);
503     if (!tsdPtr->logChannel) {
504         Tcl_DStringFree(&tsdPtr->logFilename);
505         return TCL_ERROR;
506     }
507     Tcl_RegisterChannel(interp,tsdPtr->logChannel);
508     Tcl_SetChannelOption(interp,tsdPtr->logChannel,"-buffering","none");
509     expLogAppendSet(append);
510     return TCL_OK;
511 }
512
513 int
514 expLogAppendGet()
515 {
516     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
517     return tsdPtr->logAppend;
518 }
519
520 void
521 expLogAppendSet(app)
522     int app;
523 {
524     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
525     tsdPtr->logAppend = app;
526 }
527
528 int
529 expLogAllGet()
530 {
531     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
532     return tsdPtr->logAll;
533 }
534
535 void
536 expLogAllSet(app)
537     int app;
538 {
539     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
540     tsdPtr->logAll = app;
541     /* should probably confirm logChannel != 0 */
542 }
543
544 int
545 expLogToStdoutGet()
546 {
547     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
548     return tsdPtr->logUser;
549 }
550
551 void
552 expLogToStdoutSet(app)
553     int app;
554 {
555     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
556     tsdPtr->logUser = app;
557 }
558
559 int
560 expLogLeaveOpenGet()
561 {
562     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
563     return tsdPtr->logLeaveOpen;
564 }
565
566 void
567 expLogLeaveOpenSet(app)
568     int app;
569 {
570     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
571     tsdPtr->logLeaveOpen = app;
572 }
573
574 Tcl_Channel
575 expLogChannelGet()
576 {
577     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
578     return tsdPtr->logChannel;
579 }
580
581 /* to set to a pre-opened channel (presumably by tcl::open) */
582 int
583 expLogChannelSet(interp,name)
584     Tcl_Interp *interp;
585     char *name;
586 {
587     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
588
589     int mode;
590     
591     if (0 == (tsdPtr->logChannel = Tcl_GetChannel(interp,name,&mode))) {
592         return TCL_ERROR;
593     }
594     if (!(mode & TCL_WRITABLE)) {
595         tsdPtr->logChannel = 0;
596         Tcl_SetResult(interp,"channel is not writable",TCL_VOLATILE);
597         return TCL_ERROR;
598     }
599     return TCL_OK;
600 }
601
602 char *
603 expLogFilenameGet()
604 {
605     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
606
607     return Tcl_DStringValue(&tsdPtr->logFilename);
608 }
609
610 int
611 expLogUserGet()
612 {
613     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
614
615     return tsdPtr->logUser;
616 }
617
618 void
619 expLogUserSet(logUser)
620     int logUser;
621 {
622     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
623
624     tsdPtr->logUser = logUser;
625 }
626
627
628
629 /* generate printable versions of random ASCII strings.  Primarily used */
630 /* in diagnostic mode, "expect -d" */
631 static char *
632 expPrintifyReal(s)
633 char *s;
634 {
635         static int destlen = 0;
636         static char *dest = 0;
637         char *d;                /* ptr into dest */
638         unsigned int need;
639         Tcl_UniChar ch;
640
641         if (s == 0) return("<null>");
642
643         /* worst case is every character takes 4 to printify */
644         need = strlen(s)*6 + 1;
645         if (need > destlen) {
646                 if (dest) ckfree(dest);
647                 dest = ckalloc(need);
648                 destlen = need;
649         }
650
651         for (d = dest;*s;) {
652             s += Tcl_UtfToUniChar(s, &ch);
653             if (ch == '\r') {
654                 strcpy(d,"\\r");                d += 2;
655             } else if (ch == '\n') {
656                 strcpy(d,"\\n");                d += 2;
657             } else if (ch == '\t') {
658                 strcpy(d,"\\t");                d += 2;
659             } else if ((ch < 0x80) && isprint(UCHAR(ch))) {
660                 *d = (char)ch;                  d += 1;
661             } else {
662                 sprintf(d,"\\u%04x",ch);        d += 6;
663             }
664         }
665         *d = '\0';
666         return(dest);
667 }
668
669 /* generate printable versions of random ASCII strings.  Primarily used */
670 /* in diagnostic mode, "expect -d" */
671 static char *
672 expPrintifyRealUni(s,numchars)
673 Tcl_UniChar *s;
674 int numchars;
675 {
676   static int destlen = 0;
677   static char *dest = 0;
678   char *d;              /* ptr into dest */
679   unsigned int need;
680   Tcl_UniChar ch;
681
682   if (s == 0) return("<null>");
683   if (numchars == 0) return("");
684
685   /* worst case is every character takes 6 to printify */
686   need = numchars*6 + 1;
687   if (need > destlen) {
688     if (dest) ckfree(dest);
689     dest = ckalloc(need);
690     destlen = need;
691   }
692
693   for (d = dest;numchars > 0;numchars--) {
694     ch = *s; s++;
695
696     if (ch == '\r') {
697       strcpy(d,"\\r");          d += 2;
698     } else if (ch == '\n') {
699       strcpy(d,"\\n");          d += 2;
700     } else if (ch == '\t') {
701       strcpy(d,"\\t");          d += 2;
702     } else if ((ch < 0x80) && isprint(UCHAR(ch))) {
703       *d = (char)ch;                    d += 1;
704     } else {
705       sprintf(d,"\\u%04x",ch);  d += 6;
706     }
707   }
708   *d = '\0';
709   return(dest);
710 }
711
712 char *
713 expPrintifyObj(obj)
714     Tcl_Obj *obj;
715 {
716     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
717
718     /* don't bother writing into bigbuf if we're not going to ever use it */
719     if ((!tsdPtr->diagToStderr) && (!tsdPtr->diagChannel)) return((char *)0);
720     
721     return expPrintifyReal(Tcl_GetString(obj));
722 }
723
724 char *
725 expPrintify(s) /* INTL */
726 char *s;
727 {
728     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
729
730     /* don't bother writing into bigbuf if we're not going to ever use it */
731     if ((!tsdPtr->diagToStderr) && (!tsdPtr->diagChannel)) return((char *)0);
732
733     return expPrintifyReal(s);
734 }
735  
736 char *
737 expPrintifyUni(s,numchars) /* INTL */
738 Tcl_UniChar *s;
739 int numchars;
740 {
741     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
742
743     /* don't bother writing into bigbuf if we're not going to ever use it */
744     if ((!tsdPtr->diagToStderr) && (!tsdPtr->diagChannel)) return((char *)0);
745
746     return expPrintifyRealUni(s,numchars);
747 }
748  
749 void
750 expDiagInit()
751 {
752     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
753
754     Tcl_DStringInit(&tsdPtr->diagFilename);
755     tsdPtr->diagChannel = 0;
756     tsdPtr->diagToStderr = 0;
757 }
758
759 void
760 expLogInit()
761 {
762     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
763
764     Tcl_DStringInit(&tsdPtr->logFilename);
765     tsdPtr->logChannel = 0;
766     tsdPtr->logAll = FALSE;
767     tsdPtr->logUser = TRUE;
768 }