drivers/pipes: return after short write if buffer is full

The write should return even in case of O_NONBLOCK if at least some
bytes were written.

The previous state where always all bytes were written was breaking a
common combination with poll, because poll would signal POLLOUT and some
bytes would really be consumed but write could still block afterwards.
That would prevent from execution returning to the poll loop again.

None the less it is also the standard C library behavior for the write
function.
This commit is contained in:
Karel Kočí
2024-11-05 17:10:59 +01:00
committed by Xiang Xiao
parent 4ba8723ab8
commit d0680fd1bc
+44 -85
View File
@@ -506,7 +506,6 @@ ssize_t pipecommon_write(FAR struct file *filep, FAR const char *buffer,
FAR struct inode *inode = filep->f_inode;
FAR struct pipe_dev_s *dev = inode->i_private;
ssize_t nwritten = 0;
ssize_t last;
int ret;
DEBUGASSERT(dev);
@@ -546,97 +545,32 @@ ssize_t pipecommon_write(FAR struct file *filep, FAR const char *buffer,
return ret;
}
/* Loop until all of the bytes have been written */
/* REVISIT: "If all file descriptors referring to the read end of a
* pipe have been closed, then a write will cause a SIGPIPE signal to
* be generated for the calling process. If the calling process is
* ignoring this signal, then write(2) fails with the error EPIPE."
*/
last = 0;
for (; ; )
if (dev->d_nreaders <= 0 && PIPE_IS_POLICY_0(dev->d_flags))
{
/* REVISIT: "If all file descriptors referring to the read end of a
* pipe have been closed, then a write will cause a SIGPIPE signal to
* be generated for the calling process. If the calling process is
* ignoring this signal, then write(2) fails with the error EPIPE."
*/
nxrmutex_unlock(&dev->d_bflock);
return -EPIPE;
}
if (dev->d_nreaders <= 0 && PIPE_IS_POLICY_0(dev->d_flags))
/* Would the next write overflow the circular buffer? */
while (circbuf_is_full(&dev->d_buffer))
{
if (filep->f_oflags & O_NONBLOCK)
{
/* If O_NONBLOCK was set, then return EGAIN. */
nxrmutex_unlock(&dev->d_bflock);
return nwritten == 0 ? -EPIPE : nwritten;
}
/* Would the next write overflow the circular buffer? */
if (!circbuf_is_full(&dev->d_buffer))
{
/* Loop until all of the bytes have been written */
nwritten += circbuf_write(&dev->d_buffer,
buffer + nwritten, len - nwritten);
if ((size_t)nwritten == len)
{
/* Notify all poll/select waiters that they can read from the
* FIFO when buffer used exceeds poll threshold.
*/
if (circbuf_used(&dev->d_buffer) > dev->d_pollinthrd)
{
poll_notify(dev->d_fds, CONFIG_DEV_PIPE_NPOLLWAITERS,
POLLIN);
}
/* Yes.. Notify all of the waiting readers that more data is
* available.
*/
pipecommon_wakeup(&dev->d_rdsem);
/* Return the number of bytes written */
nxrmutex_unlock(&dev->d_bflock);
return len;
}
return -EAGAIN;
}
else
{
/* There is not enough room for the next byte. Was anything
* written in this pass?
*/
if (last < nwritten)
{
/* Notify all poll/select waiters that they can read from the
* FIFO.
*/
poll_notify(dev->d_fds, CONFIG_DEV_PIPE_NPOLLWAITERS, POLLIN);
/* Yes.. Notify all of the waiting readers that more data is
* available.
*/
pipecommon_wakeup(&dev->d_rdsem);
}
last = nwritten;
/* If O_NONBLOCK was set, then return partial bytes written or
* EGAIN.
*/
if (filep->f_oflags & O_NONBLOCK)
{
if (nwritten == 0)
{
nwritten = -EAGAIN;
}
nxrmutex_unlock(&dev->d_bflock);
return nwritten;
}
/* There is more to be written.. wait for data to be removed from
* the pipe
*/
/* Wait for data to be removed from the pipe. */
nxrmutex_unlock(&dev->d_bflock);
ret = nxsem_wait(&dev->d_wrsem);
@@ -646,10 +580,35 @@ ssize_t pipecommon_write(FAR struct file *filep, FAR const char *buffer,
* received or if the task was canceled.
*/
return nwritten == 0 ? (ssize_t)ret : nwritten;
return (ssize_t)ret;
}
}
}
/* Write data to buffer. */
nwritten = circbuf_write(&dev->d_buffer, buffer, len);
/* Notify all poll/select waiters that they can read from the
* FIFO when buffer used exceeds poll threshold.
*/
if (circbuf_used(&dev->d_buffer) > dev->d_pollinthrd)
{
poll_notify(dev->d_fds, CONFIG_DEV_PIPE_NPOLLWAITERS,
POLLIN);
}
/* Yes.. Notify all of the waiting readers that more data is
* available.
*/
pipecommon_wakeup(&dev->d_rdsem);
/* Return the number of bytes written */
nxrmutex_unlock(&dev->d_bflock);
return nwritten;
}
/****************************************************************************