c++-gtk-utils
Public Types | Public Member Functions | List of all members
Cgu::PipeFifo Class Reference

A wrapper for unix anonymous pipes. More...

#include <c++-gtk-utils/pipes.h>

Public Types

enum  Fifo_mode { block, non_block }
 

Public Member Functions

 PipeFifo (const PipeFifo &)=delete
 
PipeFifooperator= (const PipeFifo &)=delete
 
void open (Fifo_mode mode)
 
void close ()
 
ssize_t read (char *buf, size_t max_num)
 
int read ()
 
ssize_t write (const char *str)
 
ssize_t write (const char *buf, size_t num)
 
int write (char item)
 
void make_writeonly ()
 
void make_readonly ()
 
int make_write_non_block ()
 
int get_read_fd () const
 
int get_write_fd () const
 
int connect_to_stdin ()
 
int connect_to_stdout ()
 
int connect_to_stderr ()
 
 PipeFifo (Fifo_mode mode)
 
 PipeFifo ()
 
 PipeFifo (PipeFifo &&)
 
PipeFifooperator= (PipeFifo &&)
 
 ~PipeFifo ()
 

Detailed Description

A wrapper for unix anonymous pipes.

See also
SyncPipe

This class provides a simplified front end for anonymous fifo pipes (as created by the pipe() system call). The constructor of the class may be passed an enumerator to indicate whether the pipe is to read in non-blocking mode.

With the write(const char*, int) method, taking an array and int arguments, the value of int (indicating the number of chars in the array to be written) should usually be less than PIPE_BUF, as POSIX write() guarantees that if it is less than PIPE_BUF, either all will be written or, if the pipe is too full, none will be written. This prevents data interleaving if a number of independent writes to the fifo are made. The same is true of the write (const char*) method taking a string - if the string is longer than PIPE_BUF in size, data interleaving may occur.

The pipe is set to read in non-blocking mode if the constructor of PipeFifo, or PipeFifo::open(), is passed the value PipeFifo::non_block. If constructed in this way, the pipe can also be set to write in non-block mode (eg to minimise the impact on another program running in a child process to which the child process has exec()ed when monitoring its stdin or stderr) by calling make_write_non_block(). However use this very sparingly – when set in this way write() will always try to write something. The atomic guarantees mentioned in the preceding paragraph do not apply and data can be interleaved or lost.

PIPE_BUF is defined in limits.h, and is at least 512 bytes, and usually 4096 bytes, but may be calculated from other files included by limits.h.

Where a pipe is used to communicate between parent and child after a call to fork(), the pipe will normally be used unidirectionally unless guards or semaphores are used to prevent a process reading data intended for the other. However, because each process inherits its own duplicate of the file descriptors, this cannot be enforced without closing the read or write file descriptor for the process for which the reading or writing is to be prohibited. This can be done by the process concerned calling the methods PipeFifo::make_writeonly() or PipeFifo::make_readonly() after the fork. If an attempt is made to read or write to a descriptor closed in this way, the PipeFifo::read() or PipeFifo::write() method will ensure that no read or write will take place, and instead -2 will be returned.

The methods PipeFifo::connect_to_stdin(), PipeFifo::connect_to_stdout() and PipeFifo::connect_to_stderr() are available to be used in the child process before it exec()s another program so as to connect the pipe to that program's stdin, stdout or stderr. PipeFifo::connect_to_stdin() cannot be used by a process after that process has called PipeFifo::make_writeonly(), and PipeFifo::connect_to_stdout() and PipeFifo::connect_to_stderr() cannot be used after the process has called PipeFifo::make_readonly(), nor can a pipe in any one process be connected to more than one of stdin, stdout or stderr. If that is attempted the methods will return -2. Furthermore, they should only be used after the process creating the pipe has forked. If the connection to stdin, stdout or stderr is to be made before the fork, this must be done by hand using dup2(), PipeFifo::get_write_fd()/ PipeFifo::get_read_fd() and PipeFifo::make_read_only()/ PipeFifo::make_write_only().

If PipeFifo::connect_to_stdin() is called by a method, PipeFifo::make_readonly() will also be called, and if PipeFifo::connect_to_stdout() or PipeFifo::connect_to_stderr() are called, PipeFifo::make_writeonly() will also be called. This will isolate the use of the pipe by the child process to stdin, stdout or stderr, as appropriate.

It uses no static members, so is thread safe as between different objects, but its methods are not thread safe as regards any one object in the sense that the read() and write() methods check the value of read_fd and write_fd respectively (and get_read_fd() and get_write_fd() return them), and make_writeonly(), make_readonly(), close(), connect_to_stdin(), open(), connect_to_stdout() and connect_to_stderr() change those values. Likewise the read() and write() methods access read_blocking_mode and write_blocking_mode respectively, and these are changed by open() and make_write_non_block(). Provided there is no concurrent use of read(), write(), get_read_fd() or get_write_fd() in one thread with a call of a method which changes read_fd, write_fd, read_blocking_mode or write_blocking_mode as described above in another thread then no mutex is required to ensure thread safety.

All the read() and write() methods check for an interruption of the system call from a signal (EINTR is checked), and will continue to read() or write() where necessary. Users do not need to check EINTR themselves. Where the write file descriptor is flagged as blocking (that is, where PipeFifo::make_write_non_block() has not been called), then in the absence of some other error, everything passed to PipeFifo::write() will be written (but as mentioned above there may be data interleaving if this is greater than PIPE_BUF in size). Where the write file descriptor is flagged as non-blocking, then the result of Unix write is returned, and less bytes than those passed to PipeFifo::write() may have been written, or -1 may be returned with errno set to EAGAIN - it is for the user to check this.

If you are using pipes to write and read between processes, you will probably want to call sigaction() to cause the SIGPIPE signal to be ignored. SIGPIPE has little use in most programming: if SIGPIPE is ignored, and an attempt is made to write to a pipe which has been closed at the read end, then the write will return -1 and errno will be set to EPIPE.

Member Enumeration Documentation

◆ Fifo_mode

Enumerator
block 
non_block 

Constructor & Destructor Documentation

◆ PipeFifo() [1/4]

Cgu::PipeFifo::PipeFifo ( const PipeFifo )
delete

This class cannot be copied. The copy constructor is deleted.

◆ PipeFifo() [2/4]

Cgu::PipeFifo::PipeFifo ( Fifo_mode  mode)

This constructor opens read and write file descriptors for the pipe. All the system calls made in its implementation are async-signal-safe.

Parameters
modeThe mode in which the read file descriptor of the pipe is to be opened - either Cgu::PipeFifo::block or Cgu::PipeFifo::non_block.
Exceptions
Cgu::PipeErrorThis exception is thrown if the opening of the pipe fails. No other exceptions will be thrown.

◆ PipeFifo() [3/4]

Cgu::PipeFifo::PipeFifo ( )

The default constructor does not open any descriptors. open() must be called before the PipeFifo object can be used, or it must be moved to. It is async-signal-safe. It does not throw.

◆ PipeFifo() [4/4]

Cgu::PipeFifo::PipeFifo ( PipeFifo &&  )

The move constructor does not throw. It does not make any system calls.

Since 2.0.19

◆ ~PipeFifo()

Cgu::PipeFifo::~PipeFifo ( )
inline

The destructor does not throw. It is async-signal-safe.

Member Function Documentation

◆ close()

void Cgu::PipeFifo::close ( )

Closes the read and write file descriptors of the pipe (if they are open). This function call is thread-safe to the extent that all the system calls made in its implementation are async-signal-safe, so it may safely be called in a multi-threaded program after a fork() and before an exec(). However, the manipulation of the file descriptors and related variables is not protected by a mutex, so if different threads in the same process are to manipulate the same pipe object with this method, open(), connect_to_stdin(), connect_to_stdout() or connect_to_stderr() (which manipulate both descriptors), make_readonly() or make_write_non_block() (which manipulate the write file descriptor), or make_writeonly() (which manipulates the read file descriptor), or read the value of the file descriptor with get_read_fd() or read() (which access the read file descriptor) or get_write_fd() or write() (which access the write file descriptor) concurrently with such a manipulation, then the user should provide appropriate synchronisation. No exceptions will be thrown.

◆ connect_to_stderr()

int Cgu::PipeFifo::connect_to_stderr ( )

Connect the write file descriptor of the pipe to stderr. Once called, any write to stderr in the process which calls this method will be written to the pipe. It should only be called in a child or parent process after a fork() where the PipeFifo object has been created in the parent process, and typically is called by a child process before one of the exec() functions is invoked by the child process. It should only be called once, and cannot be called after connect_to_stdin(), connect_to_stdout() or make_readonly() has been called in the same process. Once called, the read file descriptor is no longer available to the process calling this method. Typically it would be used in order that when the new process image created by exec() writes to stderr, it writes to the pipe, and the parent process (or another child process) reads from the pipe. This function call is thread safe to the extent that all the system calls made in its implementation are async-signal-safe, so it may safely be called in a multi-threaded program after a fork() and before an exec(). However, the manipulation of the file descriptors and related variables is not protected by a mutex, so if different threads in the same process are to manipulate the same pipe object with this method, open(), close(), connect_to_stdin() or connect_to_stdout() (which manipulate both descriptors), make_readonly() or make_write_non_block() (which manipulate the write file descriptor), or make_writeonly() (which manipulates the read file descriptor), or read the value of the file descriptor with get_read_fd() or read() (which access the read file descriptor) or get_write_fd() or write() (which access the write file descriptor) concurrently with such a manipulation, then the user should provide appropriate synchronisation. It does not throw.

Returns
-2 if the write file descriptor has previously been closed or this method, connect_to_stdin() or connect_to_stdout() has previously been called in the same process, otherwise it returns the value returned by dup2(), that is 0 if the operation succeeds, or -1 (with errno set) if not. It does not throw.

◆ connect_to_stdin()

int Cgu::PipeFifo::connect_to_stdin ( )

Connect the read file descriptor of the pipe to stdin. Once called, any read from stdin in the process which calls this method will be read from the pipe. It should only be called in a child process or parent process after a fork() where the PipeFifo object has been created in the parent process, and typically is called by the child process before one of the exec() functions is invoked by the child process. It should only be called once, and cannot be called after connect_to_stdout(), connect_to_stderr() or make_writeonly() has been called in the same process. Once called, the write file descriptor is no longer available to the process calling this method. Typically it would be used in order that when the new process image created by exec() reads from stdin, it reads from the pipe, and the parent process (or another child process) writes to the pipe. This function call is thread safe to the extent that all the system calls made in its implementation are async-signal-safe, so it may safely be called in a multi-threaded program after a fork() and before an exec(). However, the manipulation of the file descriptors and related variables is not protected by a mutex, so if different threads in the same process are to manipulate the same pipe object with this method, open(), close(), connect_to_stdout() or connect_to_stderr() (which manipulate both descriptors), make_readonly() or make_write_non_block() (which manipulate the write file descriptor), or make_writeonly() (which manipulates the read file descriptor), or read the value of the file descriptor with get_read_fd() or read() (which access the read file descriptor) or get_write_fd() or write() (which access the write file descriptor) concurrently with such a manipulation, then the user should provide appropriate synchronisation. It does not throw.

Returns
-2 if the read file descriptor has previously been closed or this method, connect_to_stdout() or connect_to_stderr() has previously been called in the same process, otherwise it returns the value returned by dup2(), that is 0 if the operation succeeds, or -1 (with errno set) if not. It does not throw.

◆ connect_to_stdout()

int Cgu::PipeFifo::connect_to_stdout ( )

Connect the write file descriptor of the pipe to stdout. Once called, any write to stdout in the process which calls this method will be written to the pipe. It should only be called in a child or parent process after a fork() where the PipeFifo object has been created in the parent process, and typically is called by the child process before one of the exec() functions is invoked by the child process. It should only be called once, and cannot be called after connect_to_stdin(), connect_to_stderr() or make_readonly() has been called in the same process. Once called, the read file descriptor is no longer available to the process calling this method. Typically it would be used in order that when the new process image created by exec() writes to stdout, it writes to the pipe, and the parent process (or another child process) reads from the pipe. This function call is thread safe to the extent that all the system calls made in its implementation are async-signal-safe, so it may safely be called in a multi-threaded program after a fork() and before an exec(). However, the manipulation of the file descriptors and related variables is not protected by a mutex, so if different threads in the same process are to manipulate the same pipe object with this method, open(), close(), connect_to_stdin() or connect_to_stderr() (which manipulate both descriptors), make_readonly() or make_write_non_block() (which manipulate the write file descriptor), or make_writeonly() (which manipulates the read file descriptor), or read the value of the file descriptor with get_read_fd() or read() (which access the read file descriptor) or get_write_fd() or write() (which access the write file descriptor) concurrently with such a manipulation, then the user should provide appropriate synchronisation. It does not throw.

Returns
-2 if the write file descriptor has previously been closed or this method, connect_to_stdin() or connect_to_stderr() has previously been called in the same process, otherwise it returns the value returned by dup2(), that is 0 if the operation succeeds, or -1 (with errno set) if not. It does not throw.

◆ get_read_fd()

int Cgu::PipeFifo::get_read_fd ( ) const
inline

Get the read file descriptor (if any). The fetching of the file descriptor is not protected by a mutex, so if different threads in the same process are to manipulate the same pipe object with open(), close(), connect_to_stdin(), connect_to_stdout() or connect_to_stderr() (which manipulate both descriptors), or make_writeonly() (which manipulates the read file descriptor), or read the value of the file descriptor with this method or read() concurrently with such a manipulation, then the user should provide appropriate synchronisation. No exceptions will be thrown.

Returns
The read file descriptor, or -1 is none is open.

◆ get_write_fd()

int Cgu::PipeFifo::get_write_fd ( ) const
inline

Get the write file descriptor (if any). The fetching of the file descriptor is not protected by a mutex, so if different threads in the same process are to manipulate the same pipe object with open(), close(), connect_to_stdin(), connect_to_stdout() or connect_to_stderr() (which manipulate both descriptors), or make_readonly() or make_write_non_block() (which manipulate the write file descriptor), or read the value of the file descriptor with this method or write() concurrently with such a manipulation, then the user should provide appropriate synchronisation. No exceptions will be thrown.

Returns
The write file descriptor, or -1 is none is open.

◆ make_readonly()

void Cgu::PipeFifo::make_readonly ( )

Make the pipe only available for reading in the calling process, by closing the write file descriptor. This function call is thread-safe to the extent that all the system calls made in its implementation are async-signal-safe, so it may safely be called in a multi-threaded program after a fork() and before an exec(). However, the manipulation of the file descriptor and related variables is not protected by a mutex, so if different threads in the same process are to manipulate the same pipe object with open(), close(), connect_to_stdin(), connect_to_stdout() or connect_to_stderr() (which manipulate both descriptors), or this method or make_write_non_block() (which manipulate the write file descriptor), or read the value of the write file descriptor with get_write_fd() or write() concurrently with such a manipulation, then the user should provide appropriate synchronisation. No exceptions will be thrown.

◆ make_write_non_block()

int Cgu::PipeFifo::make_write_non_block ( )

Makes the write file descriptor non-blocking. This is only very rarely needed. If the write file descriptor is set non-blocking, then there are no atomic write guarantees. This function call is thread safe to the extent that all the system calls made in its implementation are async-signal-safe, so it may safely be called in a multi-threaded program after a fork() and before an exec(). However, the manipulation of the file descriptor and related variables is not protected by a mutex, so if different threads in the same process are to manipulate the same pipe object with open(), close(), connect_to_stdin(), connect_to_stdout() or connect_to_stderr() (which manipulate both descriptors), or this method or make_readonly() (which manipulate the write file descriptor), or read the value of the write file descriptor with get_write_fd() or write() concurrently with such a manipulation, then the user should provide appropriate synchronisation. No exceptions will be thrown.

Returns
0 if the write file descriptor is open, or -1 otherwise.

◆ make_writeonly()

void Cgu::PipeFifo::make_writeonly ( )

Make the pipe only available for writing in the calling process, by closing the read file descriptor. This function call is thread safe to the extent that all the system calls made in its implementation are async-signal-safe, so it may safely be called in a multi-threaded program after a fork() and before an exec(). However, the manipulation of the file descriptor and related variables is not protected by a mutex, so if different threads in the same process are to manipulate the same pipe object with open(), close(), connect_to_stdin(), connect_to_stdout() or connect_to_stderr() (which manipulate both descriptors), or this method (which manipulates the read file descriptor), or read the value of the read file descriptor with get_read_fd() or read() concurrently with such a manipulation, then the user should provide appropriate synchronisation. No exceptions will be thrown.

◆ open()

void Cgu::PipeFifo::open ( Fifo_mode  mode)

Opens read and write file descriptors for the pipe. If the pipe is already open, it will close the existing file descriptors. This function call is thread-safe to the extent that all the system calls made in its implementation are async-signal-safe, so it may safely be called in a multi-threaded program after a fork() and before an exec(). However, the manipulation of the file descriptors and related variables is not protected by a mutex, so if different threads in the same process are to manipulate the same pipe object with this method, close(), connect_to_stdin(), connect_to_stdout() or connect_to_stderr() (which manipulate both descriptors), make_readonly() or make_write_non_block() (which manipulate the write file descriptor), or make_writeonly() (which manipulates the read file descriptor), or read the value of the file descriptor with get_read_fd() or read() (which access the read file descriptor) or get_write_fd() or write() (which access the write file descriptor) concurrently with such a manipulation, then the user should provide appropriate synchronisation.

Parameters
modeThe mode in which the read file descriptor of the pipe is to be opened - either Cgu::PipeFifo::block or Cgu::PipeFifo::non_block.
Exceptions
Cgu::PipeErrorThis exception is thrown if the opening of the pipe fails. No other exceptions will be thrown.

◆ operator=() [1/2]

PipeFifo& Cgu::PipeFifo::operator= ( const PipeFifo )
delete

This class cannot be copied. The assignment operator is deleted.

◆ operator=() [2/2]

PipeFifo& Cgu::PipeFifo::operator= ( PipeFifo &&  )

The move assignment operator does not throw. This function call is thread-safe to the extent that all the system calls made in its implementation are async-signal-safe, so it may safely be called in a multi-threaded program after a fork() and before an exec(), but no other synchronization is carried out. It will close the previously opened file descriptors (if any).

Since 2.0.19

◆ read() [1/2]

int Cgu::PipeFifo::read ( )

Extracts a character from the pipe. This method does not throw. All the system calls made in its implementation are async-signal-safe and it is re-entrant subject to errno being saved. It is thread safe.

Returns
The character at the front of the pipe if available, otherwise -2 if the read file descriptor is invalid, 0 if end of file is reached (ie the write descriptor has been closed), or -1 if there has been an error (but EINTR never returns as an error as the implementation handles that). In the event of an error, errno is set.

◆ read() [2/2]

ssize_t Cgu::PipeFifo::read ( char *  buf,
size_t  max_num 
)

Reads up to max_num characters from the pipe. This read does not carry on blocking until the read request is met: unless there is an interrupt, it just calls POSIX read() once and takes what is available. If a specific number of characters is required, the user should provide an appropriate do/while loop. It does not throw. Any reasonable POSIX implementation will do an atomic read as between threads if max_num is PIPE_BUF or less in size, but the POSIX standard only requires such atomic reads as between processes rather than as between threads. The user does not need to check for EINTR and an interrupt: this method deals with that automatically. All the system calls made in its implementation are async-signal-safe, and it is re-entrant subject to errno being saved. This method does not throw.

Parameters
bufA buffer into which the characters will be placed.
max_numThe maximum number of characters to be read (normally the size of the buffer).
Returns
-2 if the read file descriptor is invalid, otherwise the result of unix read() - that is, the number of characters read, or 0 if end of file is reached (ie the write descriptor has been closed), or -1 if there has been an error (but EINTR never returns as an error as the implementation handles that). In the event of an error, errno is set.

◆ write() [1/3]

int Cgu::PipeFifo::write ( char  item)
inline

Write a character to the pipe. All the system calls made in its implementation are async-signal-safe and it is re-entrant subject to errno being saved. This method does not throw. It is thread safe.

Parameters
itemThe character to be written.
Returns
1 if the character was written, -2 if the write file descriptor is invalid, or -1 if there has been an error (but EINTR never returns as an error as the implementation handles that). In the event of an error, errno is set.

◆ write() [2/3]

ssize_t Cgu::PipeFifo::write ( const char *  buf,
size_t  num 
)

Write a buffer of characters to the pipe. If the write file descriptor is set as blocking (the default) then this method will carry on blocking until everything is sent, even if it exceeds PIPE_BUF in size. Any reasonable POSIX implementation will do an atomic write (with no interleaving) as between threads if num is PIPE_BUF or less in size, but the POSIX standard only requires such atomic writes as between processes rather than as between threads. All the system calls made in its implementation are async-signal-safe and it is re-entrant subject to errno being saved. This method does not throw.

Parameters
bufThe buffer to be written.
numThe number of characters in the buffer.
Returns
-2 if write file descriptor invalid, otherwise it returns the number of bytes written or -1 if there has been an error (but EINTR never returns as an error as the implementation handles that). In the event of an error, errno is set.

◆ write() [3/3]

ssize_t Cgu::PipeFifo::write ( const char *  str)

Write a NUL terminated C string to the pipe, excluding the terminating NUL character. If the write file descriptor is set as blocking (the default) then this method will carry on blocking until everything is sent, even if it exceeds PIPE_BUF in size. Any reasonable POSIX implementation will do an atomic write (with no interleaving) as between threads if the number of characters in the string is PIPE_BUF or less in size, but the POSIX standard only requires such atomic writes as between processes rather than as between threads. All the system calls made in its implementation are async-signal-safe and it is re-entrant subject to errno being saved. This method does not throw.

Parameters
strThe string to be written.
Returns
-2 if write file descriptor invalid, otherwise it returns the number of bytes written or -1 if there has been an error (but EINTR never returns as an error as the implementation handles that). In the event of an error, errno is set.

The documentation for this class was generated from the following file: