Skip to content

Instantly share code, notes, and snippets.

@jeremyxu2010
Created December 1, 2019 16:26
Show Gist options
  • Save jeremyxu2010/fdfe20d937cabe0f1c40c062b8918e33 to your computer and use it in GitHub Desktop.
Save jeremyxu2010/fdfe20d937cabe0f1c40c062b8918e33 to your computer and use it in GitHub Desktop.
dupx
#!/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=open(\"$_name\", $_mode)"
echo "set \$xd=dup($_desc)"
echo "call dup2(\$fd, $_desc)"
echo "call close(\$fd)"
if [ $((_mode & 3)) ] && [ -n "$_msgs" ]; then
_len=$(echo -en "$_msgs" | wc -c)
echo "call write(\$xd, \"$_msgs\", $_len)"
fi
echo "call 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment