Search code examples
bashnamed-pipeslsof

Check whether named pipe/FIFO is open for writing


I've created a named pipe for some other process to write to and want to check that the other process started correctly, but don't know its PID. The context is running a command in screen, making sure the command started correctly. I was hoping this might work:

mkfifo /tmp/foo
echo hello > /tmp/foo &
lsof /tmp/foo

Sadly, lsof does not report echo. inotifywait might be another option, but isn't always installed and I really want to poll just once, rather than block until some event.

Is there any way to check if a named pipe is open for writing? Even open in general?


UPDATE:

Once both ends are connected lsof seems to work. This actually solves my problem, but for the sake of the question I'd be interested to know if it's possible to detect the initial redirection to the named pipe without a reader.

> mkfifo /tmp/foo
> yes > /tmp/foo &
> lsof /tmp/foo
> cat /tmp/foo > /dev/null &
> lsof /tmp/foo
COMMAND   PID     USER   FD   TYPE DEVICE SIZE/OFF     NODE     NAME
yes     16915     user    1w  FIFO   8,18      0t0 16660270 /tmp/foo
cat     16950     user    3r  FIFO   8,18      0t0 16660270 /tmp/foo

Solution

  • Update 2: After playing with inotify-tools, there doesn't seem to be a way to get a notification that a named pipe has been opened for writing and is blocking. This is probably why lsof doesn't show the pipe until it has a reader and a writer.

    Update: After researching named pipes, I don't believe that there is any method that will work with named pipes by themselves. Reasoning:

    • there is no way to limit the number of writers to a named pipe (without resorting to locking)
    • all writers block if there is no reader
    • no writers block if there is a reader (presumably as long as the kernel buffers aren't full)

    You could try writing nothing to the pipe with a short timeout. If the timeout expires, then the write blocked indicating that someone has already opened the pipe for writing.

    Note: As pointed out in the comments, if a reader exists and presumably is fast enough, our test write will not block and the test essentially fails. Comment out the cat line below to test this.

    #!/bin/bash
    
    is_named_pipe_already_opened_for_writing() {
        local named_pipe="$1"
        # Make sure it's a named pipe
        if ! [ -p "$named_pipe" ]; then
            return 1
        fi
        # Try to write zero bytes in the background
        echo -n > "$named_pipe" &
        pid=$!
        # Wait a short amount of time
        sleep 0.1
        # Kill the background process. If kill succeeds, then
        # the write was blocked indicating that someone
        # else is already writing to the named pipe.
        kill $pid 2>/dev/null
    }
    
    PIPE=/tmp/foo
    
    # Ignore any bash messages from killing below
    trap : TERM
    
    mkfifo $PIPE
    # a writer
    yes > $PIPE &
    # a reader
    cat $PIPE >/dev/null &
    
    if is_named_pipe_already_opened_for_writing "$PIPE"; then
        echo "$PIPE is already being written to by another process"
    else
        echo "$PIPE is NOT being written to by another process"
    fi
    
    jobs -pr | kill 2>/dev/null
    rm -f $PIPE