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
expWriteBytesAndLogIfTtyU(esPtr,buf,lenChars)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
expLogDiagU(buf)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
expLogInteractionU(esPtr,buf,buflen)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
TCL_VARARGS_DEF(int,arg1)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
expStdoutLogU(buf,force_stdout)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
TCL_VARARGS_DEF(char *,arg1)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
expErrorLogU(buf)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
TCL_VARARGS_DEF(char *,arg1)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
expDiagLogU(str)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
TCL_VARARGS_DEF(char *,arg1)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
expDiagToStderrSet(val)320 expDiagToStderrSet(val)
321     int val;
322 {
323     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
324 
325     tsdPtr->diagToStderr = val;
326 }
327 
328 
329 int
expDiagToStderrGet()330 expDiagToStderrGet() {
331     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
332     return tsdPtr->diagToStderr;
333 }
334 
335 Tcl_Channel
expDiagChannelGet()336 expDiagChannelGet()
337 {
338     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
339     return tsdPtr->diagChannel;
340 }
341 
342 void
expDiagChannelClose(interp)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
expDiagChannelOpen(interp,filename)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",0777);
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
expDiagWriteObj(obj)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
expDiagWriteBytes(str,len)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
expDiagWriteChars(str,len)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
expDiagWriteCharsUni(str,len)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 *
expDiagFilename()442 expDiagFilename()
443 {
444     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
445 
446     return Tcl_DStringValue(&tsdPtr->diagFilename);
447 }
448 
449 void
expLogChannelClose(interp)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
expLogChannelOpen(interp,filename,append)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,0777);
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
expLogAppendGet()514 expLogAppendGet()
515 {
516     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
517     return tsdPtr->logAppend;
518 }
519 
520 void
expLogAppendSet(app)521 expLogAppendSet(app)
522     int app;
523 {
524     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
525     tsdPtr->logAppend = app;
526 }
527 
528 int
expLogAllGet()529 expLogAllGet()
530 {
531     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
532     return tsdPtr->logAll;
533 }
534 
535 void
expLogAllSet(app)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
expLogToStdoutGet()545 expLogToStdoutGet()
546 {
547     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
548     return tsdPtr->logUser;
549 }
550 
551 void
expLogToStdoutSet(app)552 expLogToStdoutSet(app)
553     int app;
554 {
555     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
556     tsdPtr->logUser = app;
557 }
558 
559 int
expLogLeaveOpenGet()560 expLogLeaveOpenGet()
561 {
562     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
563     return tsdPtr->logLeaveOpen;
564 }
565 
566 void
expLogLeaveOpenSet(app)567 expLogLeaveOpenSet(app)
568     int app;
569 {
570     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
571     tsdPtr->logLeaveOpen = app;
572 }
573 
574 Tcl_Channel
expLogChannelGet()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
expLogChannelSet(interp,name)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 *
expLogFilenameGet()603 expLogFilenameGet()
604 {
605     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
606 
607     return Tcl_DStringValue(&tsdPtr->logFilename);
608 }
609 
610 int
expLogUserGet()611 expLogUserGet()
612 {
613     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
614 
615     return tsdPtr->logUser;
616 }
617 
618 void
expLogUserSet(logUser)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 *
expPrintifyReal(s)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 *
expPrintifyRealUni(s,numchars)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 *
expPrintifyObj(obj)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 *
expPrintify(s)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 *
expPrintifyUni(s,numchars)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
expDiagInit()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
expLogInit()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 }
769