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
8 /*#include <varargs.h> tclInt.h drags in varargs.h. Since Pyramid */
9 /* objects to including varargs.h twice, just */
13 #include "../compat/stdlib.h"
15 #include <stdlib.h> /* for malloc */
19 #include "expect_comm.h"
21 #include "exp_rename.h"
22 #include "exp_command.h"
25 typedef struct ThreadSpecificData {
26 Tcl_Channel diagChannel;
27 Tcl_DString diagFilename;
30 Tcl_Channel logChannel;
31 Tcl_DString logFilename; /* if no name, then it came from -open or -leaveopen */
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)
38 int logUser; /* TRUE if user sees interactions on stdout */
41 static Tcl_ThreadDataKey dataKey;
44 * create a reasonably large buffer for the bulk of the output routines
45 * that are not too large
47 static char bigbuf[2000];
49 static void expDiagWriteCharsUni _ANSI_ARGS_((Tcl_UniChar *str,int len));
52 * Following this are several functions that log the conversation. Some
53 * general notes on all of them:
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
63 * if necessary, they could be made more efficient by skipping vsprintf based
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. */
75 * Name: expWriteBytesAndLogIfTtyU
77 * Output to channel (and log if channel is stdout or devtty)
79 * Returns: TCL_OK or TCL_ERROR;
83 expWriteBytesAndLogIfTtyU(esPtr,buf,lenChars)
89 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
92 wc = expWriteCharsUni(esPtr,buf,lenChars);
94 if (tsdPtr->logChannel && ((esPtr->fdout == 1) || expDevttyIs(esPtr))) {
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);
107 * Send to the Log (and Diag if open). This is for writing to the log.
108 * (In contrast, expDiagLog... is for writing diagnostics.)
115 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
117 expDiagWriteChars(buf,-1);
118 if (tsdPtr->logChannel) {
119 Tcl_WriteChars(tsdPtr->logChannel, buf, -1);
124 * Name: expLogInteractionU
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.
131 expLogInteractionU(esPtr,buf,buflen)
136 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
138 if (tsdPtr->logAll || (tsdPtr->logUser && tsdPtr->logChannel)) {
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);
146 /* hmm.... if stdout is closed such as by disconnect, loguser
147 should be forced FALSE */
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);
156 expDiagWriteCharsUni(buf,buflen);
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)
167 expStdoutLog TCL_VARARGS_DEF(int,arg1)
169 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
174 force_stdout = TCL_VARARGS_START(int,arg1,args);
175 fmt = va_arg(args,char *);
177 if ((!tsdPtr->logUser) && (!force_stdout) && (!tsdPtr->logAll)) return;
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);
186 /* just like log but does no formatting */
187 /* send to log if open */
188 /* use this function for logging the parent/child conversation */
190 expStdoutLogU(buf,force_stdout)
192 int force_stdout; /* override value of logUser */
194 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
197 if ((!tsdPtr->logUser) && (!force_stdout) && (!tsdPtr->logAll)) return;
199 length = strlen(buf);
200 expDiagWriteBytes(buf,length);
201 if (tsdPtr->logAll || (LOGUSER && tsdPtr->logChannel)) Tcl_WriteChars(tsdPtr->logChannel,buf,-1);
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));
207 fwrite(buf,1,length,stdout);
212 /* send to log if open */
214 /* use this function for error conditions */
217 expErrorLog TCL_VARARGS_DEF(char *,arg1)
219 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
224 fmt = TCL_VARARGS_START(char *,arg1,args);
225 (void) vsprintf(bigbuf,fmt,args);
227 expDiagWriteChars(bigbuf,-1);
228 fprintf(stderr,"%s",bigbuf);
229 if (tsdPtr->logChannel) Tcl_WriteChars(tsdPtr->logChannel,bigbuf,-1);
234 /* just like errorlog but does no formatting */
235 /* send to log if open */
236 /* use this function for logging the parent/child conversation */
242 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
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);
252 /* send diagnostics to Diag, Log, and stderr */
253 /* use this function for recording unusual things in the log */
256 expDiagLog TCL_VARARGS_DEF(char *,arg1)
261 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
263 if ((tsdPtr->diagToStderr == 0) && (tsdPtr->diagChannel == 0)) return;
265 fmt = TCL_VARARGS_START(char *,arg1,args);
267 (void) vsprintf(bigbuf,fmt,args);
269 expDiagWriteBytes(bigbuf,-1);
270 if (tsdPtr->diagToStderr) {
271 fprintf(stderr,"%s",bigbuf);
272 if (tsdPtr->logChannel) Tcl_WriteChars(tsdPtr->logChannel,bigbuf,-1);
279 /* expDiagLog for unformatted strings
280 this also takes care of arbitrary large strings */
285 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
287 if ((tsdPtr->diagToStderr == 0) && (tsdPtr->diagChannel == 0)) return;
289 expDiagWriteBytes(str,-1);
291 if (tsdPtr->diagToStderr) {
292 fprintf(stderr,"%s",str);
293 if (tsdPtr->logChannel) Tcl_WriteChars(tsdPtr->logChannel,str,-1);
297 /* expPrintf prints to stderr. It's just a utility for making
302 expPrintf TCL_VARARGS_DEF(char *,arg1)
309 fmt = TCL_VARARGS_START(char *,arg1,args);
310 len = vsprintf(bigbuf,arg1,args);
312 rc = write(2,bigbuf,len);
313 if ((rc == -1) && (errno == EAGAIN)) goto retry;
320 expDiagToStderrSet(val)
323 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
325 tsdPtr->diagToStderr = val;
330 expDiagToStderrGet() {
331 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
332 return tsdPtr->diagToStderr;
338 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
339 return tsdPtr->diagChannel;
343 expDiagChannelClose(interp)
346 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
348 if (!tsdPtr->diagChannel) return;
349 Tcl_UnregisterChannel(interp,tsdPtr->diagChannel);
350 Tcl_DStringFree(&tsdPtr->diagFilename);
351 tsdPtr->diagChannel = 0;
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 */
358 expDiagChannelOpen(interp,filename)
362 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
365 Tcl_ResetResult(interp);
366 newfilename = Tcl_TranslateFileName(interp,filename,&tsdPtr->diagFilename);
367 if (!newfilename) return TCL_ERROR;
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);
377 tsdPtr->diagChannel = Tcl_OpenFileChannel(interp,newfilename,"a",0777);
378 if (!tsdPtr->diagChannel) {
379 Tcl_DStringFree(&tsdPtr->diagFilename);
382 Tcl_RegisterChannel(interp,tsdPtr->diagChannel);
383 Tcl_SetChannelOption(interp,tsdPtr->diagChannel,"-buffering","none");
391 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
393 if (!tsdPtr->diagChannel) return;
395 Tcl_WriteObj(tsdPtr->diagChannel,obj);
398 /* write 8-bit bytes */
400 expDiagWriteBytes(str,len)
404 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
406 if (!tsdPtr->diagChannel) return;
408 Tcl_Write(tsdPtr->diagChannel,str,len);
411 /* write UTF chars */
413 expDiagWriteChars(str,len)
417 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
419 if (!tsdPtr->diagChannel) return;
421 Tcl_WriteChars(tsdPtr->diagChannel,str,len);
424 /* write Unicode chars */
426 expDiagWriteCharsUni(str,len)
431 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
433 if (!tsdPtr->diagChannel) return;
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);
444 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
446 return Tcl_DStringValue(&tsdPtr->diagFilename);
450 expLogChannelClose(interp)
453 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
455 if (!tsdPtr->logChannel) return;
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);
462 /* it's a channel that tcl::open created */
463 if (!tsdPtr->logLeaveOpen) {
464 Tcl_UnregisterChannel(interp,tsdPtr->logChannel);
467 tsdPtr->logChannel = 0;
468 tsdPtr->logAll = 0; /* can't write to log if none open! */
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 */
475 expLogChannelOpen(interp,filename,append)
480 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
490 Tcl_ResetResult(interp);
491 newfilename = Tcl_TranslateFileName(interp,filename,&tsdPtr->logFilename);
492 if (!newfilename) return TCL_ERROR;
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);
502 tsdPtr->logChannel = Tcl_OpenFileChannel(interp,newfilename,mode,0777);
503 if (!tsdPtr->logChannel) {
504 Tcl_DStringFree(&tsdPtr->logFilename);
507 Tcl_RegisterChannel(interp,tsdPtr->logChannel);
508 Tcl_SetChannelOption(interp,tsdPtr->logChannel,"-buffering","none");
509 expLogAppendSet(append);
516 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
517 return tsdPtr->logAppend;
524 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
525 tsdPtr->logAppend = app;
531 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
532 return tsdPtr->logAll;
539 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
540 tsdPtr->logAll = app;
541 /* should probably confirm logChannel != 0 */
547 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
548 return tsdPtr->logUser;
552 expLogToStdoutSet(app)
555 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
556 tsdPtr->logUser = app;
562 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
563 return tsdPtr->logLeaveOpen;
567 expLogLeaveOpenSet(app)
570 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
571 tsdPtr->logLeaveOpen = app;
577 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
578 return tsdPtr->logChannel;
581 /* to set to a pre-opened channel (presumably by tcl::open) */
583 expLogChannelSet(interp,name)
587 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
591 if (0 == (tsdPtr->logChannel = Tcl_GetChannel(interp,name,&mode))) {
594 if (!(mode & TCL_WRITABLE)) {
595 tsdPtr->logChannel = 0;
596 Tcl_SetResult(interp,"channel is not writable",TCL_VOLATILE);
605 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
607 return Tcl_DStringValue(&tsdPtr->logFilename);
613 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
615 return tsdPtr->logUser;
619 expLogUserSet(logUser)
622 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
624 tsdPtr->logUser = logUser;
629 /* generate printable versions of random ASCII strings. Primarily used */
630 /* in diagnostic mode, "expect -d" */
635 static int destlen = 0;
636 static char *dest = 0;
637 char *d; /* ptr into dest */
641 if (s == 0) return("<null>");
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);
652 s += Tcl_UtfToUniChar(s, &ch);
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;
662 sprintf(d,"\\u%04x",ch); d += 6;
669 /* generate printable versions of random ASCII strings. Primarily used */
670 /* in diagnostic mode, "expect -d" */
672 expPrintifyRealUni(s,numchars)
676 static int destlen = 0;
677 static char *dest = 0;
678 char *d; /* ptr into dest */
682 if (s == 0) return("<null>");
683 if (numchars == 0) return("");
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);
693 for (d = dest;numchars > 0;numchars--) {
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;
705 sprintf(d,"\\u%04x",ch); d += 6;
716 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
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);
721 return expPrintifyReal(Tcl_GetString(obj));
725 expPrintify(s) /* INTL */
728 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
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);
733 return expPrintifyReal(s);
737 expPrintifyUni(s,numchars) /* INTL */
741 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
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);
746 return expPrintifyRealUni(s,numchars);
752 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
754 Tcl_DStringInit(&tsdPtr->diagFilename);
755 tsdPtr->diagChannel = 0;
756 tsdPtr->diagToStderr = 0;
762 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
764 Tcl_DStringInit(&tsdPtr->logFilename);
765 tsdPtr->logChannel = 0;
766 tsdPtr->logAll = FALSE;
767 tsdPtr->logUser = TRUE;