I'm not sure if this absurd. Isn't it possible to trace
the read and write of stdout
and stderr
in Tcl?
I have tried the below and found no clue.
% proc tracer {varname args} {
upvar #0 $varname var
puts "$varname value : $var"
}
% trace add variable stdout read tracer
% trace add variable stdout write tracer
% puts stdout hai
hai
% puts hai
hai
% trace add variable stderr write tracer
% trace add variable stderr read tracer
% puts hai
hai
% puts stderr hai
hai
As per the man page of puts
, if no channelId
is specified for puts
command, then it defaults to stdout
which means even with puts hai
, stdout
will be accessed. Right ? (Though it is not working even with the argument as stdout
or stderr
)
The problem with your attempted solution is that stdout
, stderr
and stdin
are not variables but names of the so-called "channels".
In essence, they're in a separated namespace (not a namespace operated by the namespace
Tcl command!): you can get a list of them using the chan names
command but you can't, say, rename
a channel, or assign a value to it or unset
it: these operations simply have no sense on channels and will affect a variable by that name instead.
An approach to trace a channel is to actually subvert it with another—"script-level"—channel and "proxy" all operations. This trick uses a lesser-known Tcl's feature: when you close one of Tcl's standard channels and immediately open a channel (no matter if "real" or "script-level"), that will gets registered in place of that standard channel just closed. Hence if we close a standard channel and immediately create our own "proxy" channel in its place we hence subvert that standard channel.
Those "script-level" ("reflected") channels require Tcl ≥ 8.5.
Here's a sketch of subverting stdout
which requires Linux (for /proc/self/fd/<fileno>
support).
proc traceChan {cmd chan args} {
global stdout
puts stderr "Trace on $chan; cmd=$cmd; args=$args"
switch -- $cmd {
initialize {
return [list initialize finalize watch write configure cget cgetall]
}
finalize {
chan close $stdout
}
watch {
# FIXME: not implemented
}
write {
set data [lindex $args 0]
chan puts -nonewline $stdout $data
return [string length $data]
}
configure {
return [chan config $stdout {*}$args]
}
cget {
return [chan cget $stdout {*}$args
}
cgetall {
return [chan configure $stdout]
}
}
}
set fn [file readlink /proc/self/fd/1]
set conf [chan config stdout]
chan close stdout
chan create write ::traceChan
set stdout [open $fn w]
chan configure stdout {*}$conf
puts [chan names]
puts test
chan flush stdout
chan close stdout
On my system it botches terminal settings (stdout
is connected to a terminal), and requires me to execute reset
followed by stty sane
after the script exits but at least it does the job done.
Actual problems with this script:
- In fact we lie about the number of bytes written: we have no idea how many bytes the underlying
stdout
channel will write as it depends on a number of things.
- We don't handle channel events at all.
These problems can be addressed by writing a C module implementing such a proxying: with the C API, you would have access to the underlying file descriptor/handle (so no requirement for /proc/self/fd/...
) and a Tcl object wrapping it (so you could possibly just clone it right away) and know how many bytes were written by the underlying channel.
Oh, and note that if you're fine with throwing the data sent by the script to the subverted standard channels away, just do no dances with re-opening the real underlying file, closing it, writing data to it etc. The solution will then boild down to performing chan create
immediately after chan close
and tracing writes in the tracing procedure. In response to the write
call your tracing routine will still need to return a number of bytes thrown away.
Please also read the chan
and refchan
manual pages.