How to Determine If Pipe Can Be Written

How to check if the pipe is opend before writing?

The correct way is to test the return code of write and then also check errno:

if (write(pipe, msg, strlen(msg)) == -1) {
if (errno == EPIPE) {
/* Closed pipe. */
}
}

But wait: writing to a closed pipe no only returns -1 with errno=EPIPE, it also sends a SIGPIPE signal which terminates your process:

EPIPE fd is connected to a pipe or socket whose reading end is
closed. When this happens the writing process will also receive a
SIGPIPE signal.

So before that testing works you also need to ignore SIGPIPE:

if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
perror("signal");

Check whether named pipe/FIFO is open for writing

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

How to see if a pipe is empty

Your logic is wrong in that read will not return 0 when it runs out of characters; instead, it will block until it receives more, unless you put the file in non-blocking mode, but then it will return -1 and set errno to EWOULDBLOCK or EAGAIN rather than returning 0. The only time read can ever return 0 is when the size argument was 0 or end-of-file has been reached. And, for pipes, end-of-file means the writing end of the pipe has been closed; end-of-file status does not occur just because there's not any input available yet.

With that said, the simplest way to check is:

if (poll(&(struct pollfd){ .fd = fd, .events = POLLIN }, 1, 0)==1) {
/* data available */
}

but unless you're using nonblocking mode, you'll need to make this check before every single read operation. Passing a larger buffer to read rather than doing it a byte-at-a-time would eliminate most of the cost of checking.

How to test that no data is written to an NSPipe

As outlined in the comments, I've found a separate solution which writes arbitrary data to the pipe, executes the log (which may or may not result in more data being written to the pipe), and then retrieves the data from the pipe's availableData.

If the availableData's length is equal to that of the arbitrary data, then it is clear that the log has not been written to the pipe. Otherwise if the length is greater than the data that the test has written, the log has indeed been executed and written its data to the pipe.

let logger = Logger()
let pipe = NSPipe()

logger.pipe = pipe
logger.enabled = false
logger.useCurrentThread = true // The logs generally use a background thread to prevent interrupting the main queue. In this test, the current thread must be used in order for it to be synchronous.

let writtenData = "written data".dataUsingEncoding(NSUTF8StringEncoding)!
logger.pipe!.fileHandleForWriting.writeData(writtenData)

logger.debug("Test message")

XCTAssertEqual(logger.pipe!.fileHandleForReading.availableData.length, writtenData.length)


Related Topics



Leave a reply



Submit