|
#!/bin/bash |
|
|
|
THIS=$0 |
|
ARGS=$@ |
|
name=$(basename $THIS) |
|
|
|
usage () { |
|
cat <<EOF |
|
Usage: $name: redirect input/output/error of a running process |
|
Two ways to run this program: |
|
$name [-q][-o <outfile>] [-e <errorfile>] [-i <inputfile>] [-n <fd>:<filename> ] <pid> |
|
or simply: |
|
$name [-q] <pid> |
|
The first form files specified in -o, -e, and -i options will become |
|
standard output, standard error, and standard input. At least one |
|
option must be specified, or second form will be assumed. |
|
For example, using Bash syntax: |
|
bash -c 'sleep 1m && echo "rise and shine"' & |
|
$name -o /tmp/test \$! |
|
The same with explicit stdout descriptor number (1) using -n option: |
|
bash -c 'sleep 1m && echo "rise and shine"' & |
|
$name -n 0:/tmp/test \$! |
|
|
|
In the second form, current in/out/err are remapped into the process. |
|
For example, using Bash syntax: |
|
bash -c 'sleep 1m && echo "rise and shine"' & |
|
$name \$! >>/tmp/test |
|
These examples should be equivalent (i.e. -o foo will append to file foo, |
|
if it already exists) with respect to stdout, but in the second stdin and |
|
stderr are remapped as well. |
|
|
|
Option (-n) allows explicit specification of file descriptors. |
|
It can be given multiple times, e.g.: |
|
$name -n 0:/tmp/stdin -n 1:/tmp/stdout -n 2:/tmp/stderr PID |
|
is equivalent to, using Bash syntax: |
|
$name PID </tmp/stdin >/tmp/stdout 2>/tmp/stderr |
|
All files specified in (-n) option are opened in read/write & append mode. |
|
|
|
Summary of options: |
|
-i <stdin> specify filename of the new standard input |
|
-o <stdout> specify filename of the new standard output |
|
-e <stderr> specify filename of the new standard error |
|
-n <fd>:<filename> specify descriptor number and filename to remap to |
|
-q be quiet |
|
-h this help |
|
|
|
EOF |
|
} |
|
|
|
|
|
warn () { |
|
echo "$name: $@" >&2 |
|
} |
|
|
|
# XXX add multiple -n option |
|
quiet="no" |
|
nopt="" |
|
while getopts "ho:e:i:qn:" opt; do |
|
case $opt in |
|
( o ) stdout=$OPTARG; ;; |
|
( e ) stderr=$OPTARG; ;; |
|
( i ) stdin=$OPTARG; ;; |
|
( n ) nopt="$nopt $OPTARG"; ;; |
|
( q ) quiet="yes"; ;; |
|
( * ) usage; exit 0; ;; |
|
esac |
|
done |
|
|
|
shift $((OPTIND-1)) |
|
|
|
|
|
if [ $# != 1 ]; then |
|
usage |
|
exit 1 |
|
fi |
|
|
|
if [ -n "$stdout" ] && ! 2>/dev/null : >> $stdout ; then |
|
warn "Cannot write to (-o) $stdout" |
|
exit 1 |
|
fi |
|
|
|
if [ -n "$stderr" ] && ! 2>/dev/null : >> $stderr ; then |
|
warn "Cannot write to (-e) $stderr" |
|
exit 1 |
|
fi |
|
|
|
if [ -n "$stdin" ] && ! 2>/dev/null : >> $stdin ; then |
|
warn "Cannot write to (-i) $stdin" |
|
exit 1 |
|
fi |
|
|
|
fds="" |
|
if [ -n "$nopt" ]; then |
|
for n_f in $nopt; do |
|
n=${n_f%%:*} |
|
f=${n_f##*:} |
|
if [ -n "${n//[0-9]/}" ] || [ -z "$f" ]; then |
|
warn "Error parsing descriptor (-n $n_f)" |
|
exit 1 |
|
fi |
|
if ! 2>/dev/null : >> $f; then |
|
warn "Cannot write to (-n $n_f) $f" |
|
exit 1 |
|
fi |
|
fds="$fds $n" |
|
fns[$n]=$f |
|
done |
|
fi |
|
if [ -z "$stdout" ] && [ -z "$stderr" ] && [ -z "$stdin" ] && [ -z "$nopt" ]; then |
|
#second invocation form: dup to my own in/err/out |
|
[ -e /proc/$$/fd/0 ] && stdin=$(readlink /proc/$$/fd/0) |
|
[ -e /proc/$$/fd/1 ] && stdout=$(readlink /proc/$$/fd/1) |
|
[ -e /proc/$$/fd/2 ] && stderr=$(readlink /proc/$$/fd/2) |
|
if [ -z "$stdout" ] && [ -z "$stderr" ] && [ -z "$stdin" ]; then |
|
warn "Could not determine current standard in/out/err" |
|
exit 1 |
|
fi |
|
fi |
|
|
|
PID=$1 |
|
if ! 2>/dev/null kill -0 $PID; then |
|
warn "Error accessing PID $PID" |
|
exit 1 |
|
fi |
|
|
|
if [ "$quiet" != "yes" ]; then |
|
msg_stdout="Remaining standard output of $PID is redirected to $stdout\n" |
|
msg_stderr="Remaining standard error of $PID is redircted to $stderr\n" |
|
fi |
|
|
|
gdb_cmds () { |
|
local _name=$1 |
|
local _mode=$2 |
|
local _desc=$3 |
|
local _msgs=$4 |
|
local _len |
|
|
|
[ -w "/proc/$PID/fd/$_desc" ] || _msgs="" |
|
if [ -d "/proc/$PID/fd" ] && ! [ -e "/proc/$PID/fd/$_desc" ]; then |
|
warn "Attempting to remap non-existent fd $n of PID ($PID)" |
|
fi |
|
|
|
[ -z "$_name" ] && return |
|
|
|
echo "set \$fd=(int) open(\"$_name\", $_mode)" |
|
echo "set \$xd=(int) dup($_desc)" |
|
echo "call (int) dup2(\$fd, $_desc)" |
|
echo "call (int) close(\$fd)" |
|
if [ $((_mode & 3)) ] && [ -n "$_msgs" ]; then |
|
_len=$(echo -en "$_msgs" | wc -c) |
|
echo "call (int) write(\$xd, \"$_msgs\", $_len)" |
|
fi |
|
echo "call (int) close(\$xd)" |
|
} |
|
|
|
trap '/bin/rm -f $GDBCMD' EXIT |
|
GDBCMD=$(mktemp /tmp/gdbcmd.XXXX) |
|
{ |
|
#Linux file flags (from /usr/include/bits/fcntl.sh) |
|
O_RDONLY=00 |
|
O_WRONLY=01 |
|
O_RDWR=02 |
|
O_CREAT=0100 |
|
O_APPEND=02000 |
|
echo "#gdb script generated by running '$0 $ARGS'" |
|
echo "attach $PID" |
|
gdb_cmds "$stdin" $((O_RDONLY)) 0 "$msg_stdin" |
|
gdb_cmds "$stdout" $((O_WRONLY|O_CREAT|O_APPEND)) 1 "$msg_stdout" |
|
gdb_cmds "$stderr" $((O_WRONLY|O_CREAT|O_APPEND)) 2 "$msg_stderr" |
|
for n in $fds; do |
|
msg="Descriptor $n of $PID is remapped to ${fns[$n]}\n" |
|
gdb_cmds ${fns[$n]} $((O_RDWR|O_CREAT|O_APPEND)) $n "$msg" |
|
done |
|
#echo "quit" |
|
} > $GDBCMD |
|
|
|
if gdb -batch -n -x $GDBCMD >/dev/null </dev/null; then |
|
[ "$quiet" != "yes" ] && echo "Success" >&2 |
|
else |
|
warn "Remapping failed" |
|
fi |
|
#cp $GDBCMD /tmp/gdbcmd |
|
rm -f $GDBCMD |