Redirect Input and Output For an Existing Process On Linux

Redirecting input and output of an executable is a standard and trivial practice in Linux operations. When launching a process, the user can trivially redirect the output of the process away from stdout via the > operator and can redirect input away from stdin using the < operator.

But that only works if stdin or stdout are switching before the process is launched. What if we have a pre-existing process that we would like to change a file handles for, but we would like to avoid restarting the process? For example, say you have a cron script execute sudo my_command from an environment where you can't provide input (perhaps you meant to use gksudo instead). You might be able to kill the sudo process, but perhaps when sudo exits the script will proceed with undesirable results. You could kill the script too, but assume that you very badly don't want to abort the script in a semi-completed state. (Obviously a well-written script shouldn't have this sort of behavior, but the assumption is that we are in an unusual situation.) The ideal solution would be to redirect input into the hanging sudo process allowing it to succeed and your script to continue.

Thankfully, we can perform redirection on existing processes by explicitly manipulating the existing file descriptors. The method for doing so is fairly straight forward:

  1. Determine which file descriptor you need to change.
  2. Attach to the target process via gdb.
  3. Create a file (to redirect output) or named pipe (to redirect input).
  4. Use gdb to point the desired file descriptor to the file or pipe.
  5. If applicable, send the necessary content through the pipe.

In terminal A, find the PID of the process, call it TARGET_PID. First, list the target's existing file descriptors:

$ ls -l /proc/TARGET_PID/fd/

When we are done we will double check this list to ensure we made the changes we wanted to.

Now you need to determine which file descriptor (hereon "FD") you want to change. Remember, we can only manipulate existing FDs, not add new ones. (For those who don't know: FD 0 is stdin (standard input), FD 1 is stdout (standard output), FD 2 is stderr (standard error output). These are the the base FDs that every process will have, your target process may or may not have more.) Examples:

  • To change the input for a typical terminal program you likely need to to change stdin.
  • To change output file X to a different file Y, you need to find which FD on the list is linked to X.
  • For sudo, to change the input that accepts the user password you actually need to change FD 4, which should point to /dev/tty or something similar.

We'll call the the FD number that you want to change TARGET_FD.

For using a named pipe: First create the pipe using

$ mkfifo /my/file/name

We'll call this path TARGET_FILE. Then provide input to the pipe, or else gdb will not be able to open it in a later step. Provide the content by, for example, echo MyContent > TARGET_FILE from a separate terminal or as a backgrounded process. MyContent should be the content you want to send the process.

For using a normal file: Create an output file called TARGET_FILE.

Attach gdb to the process:

$ gdb -p TARGET_PID

Within gdb, close the file descriptor using the close() system call:

(gdb) p close(TARGET_FD)
$1 = 0

The right-hand side of the output is the return value of the call we just executed. A value of 0 means that all went well.

Now create an FD using the open() system call. This must be done after "close()", because file descriptors are issued sequentially from the lowest unused non-negative integer and we are making use of the fact that once we delete TARGET_FD it is now the lowest unused file descriptor, so the next one created will use the same number.

(gdb) p open("TARGET_FILE",0600)
$2 = TARGET_FD

If the right-hand side number is equal to TARGET_FD, that means we just successfully created an FD and it got the same FD that we just closed, which is perfect. Remember, if you are using a named pipe, this step may (will?) hang if there is no output going into the named pipe.

Now quit gdb:

(gdb) q

At this point, you should be done. If you are redirecting output, the redirection should be under way. If you are redirecting input, the first input should be consumed from the pipe and you can continue to provide input as necessary by sending it into the pipe; when you are done simply delete the pipe using rm.

We can verify hat we were successful by checking the target process's FDs. Run ls -l /proc/TARGET_PID/fd/ again and compare the output against the output from the first time. If all went well then TARGET_FD should be changed to point at TARGET_FILE.