/*
 * Copyright (c) 2011-2017, 2021 Jonas 'Sortie' Termansen.
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * pipe.cpp
 * A device with a writing end and a reading end.
 */

#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <stdint.h>
#include <string.h>

#include <sortix/fcntl.h>
#ifndef IOV_MAX
#include <sortix/limits.h>
#endif
#include <sortix/poll.h>
#include <sortix/signal.h>
#include <sortix/stat.h>

#include <sortix/kernel/copy.h>
#include <sortix/kernel/descriptor.h>
#include <sortix/kernel/dtable.h>
#include <sortix/kernel/inode.h>
#include <sortix/kernel/interlock.h>
#include <sortix/kernel/ioctx.h>
#include <sortix/kernel/kernel.h>
#include <sortix/kernel/kthread.h>
#include <sortix/kernel/pipe.h>
#include <sortix/kernel/poll.h>
#include <sortix/kernel/process.h>
#include <sortix/kernel/refcount.h>
#include <sortix/kernel/scheduler.h>
#include <sortix/kernel/signal.h>
#include <sortix/kernel/syscall.h>
#include <sortix/kernel/thread.h>
#include <sortix/kernel/vnode.h>

namespace Sortix {

// A segment contains an optional leading ancillary data buffer and then a
// normal data buffer.
struct segment_header
{
	size_t ancillary;
	size_t normal;
};

// A pipe communication channel in one direction.
//
// The pipe uses a ring buffer containing segments. Each segment is stored as
// its header, followed by an optional ancillary data buffer (cmsg(3)) and a
// normal data buffer. Writing data will append to the last segment, if any, if
// it has a compatible type and isn't finished.
class PipeChannel
{
public:
	PipeChannel(uint8_t* buffer, size_t buffer_size);
	~PipeChannel();
	void CloseReading();
	void CloseWriting();
	bool GetSIGPIPEDelivery();
	void SetSIGPIPEDelivery(bool deliver_sigpipe);
	size_t ReadSize();
	size_t WriteSize();
	bool ReadResize(size_t new_size);
	bool WriteResize(size_t new_size);
	bool WriteBuffer(bool (*copy_from_src)(void*, const void*, size_t),
	                 const void* src_ptr,
	                 size_t amount,
	                 size_t position);
	bool ReadBuffer(bool (*copy_to_dest)(void*, const void*, size_t),
	                void* dest_ptr,
	                size_t amount,
	                size_t position);
	bool Enqueue(bool (*copy_from_src)(void* dest, const void* src, size_t n),
	             const void* src,
	             size_t amount,
	             bool is_ancillary);
	void Unenqueue(bool (*copy_to_dest)(void*, const void*, size_t),
		           void* dest_ptr,
		           size_t amount,
		           size_t saved_last_header_position);
	bool Dequeue(bool (*copy_to_dest)(void* dest, const void* src, size_t n),
	             void* dest,
	             size_t amount,
	             bool peek,
	             size_t peek_offset);
	bool pass();
	void unpass();
	ssize_t readv(ioctx_t* ctx, const struct iovec* iov, int iovcnt);
	ssize_t recv(ioctx_t* ctx, uint8_t* buf, size_t count, int flags);
	ssize_t recvmsg(ioctx_t* ctx, struct msghdr* msg, int flags);
	ssize_t recvmsg_internal(ioctx_t* ctx, struct msghdr* msg, int flags);
	ssize_t send(ioctx_t* ctx, const uint8_t* buf, size_t count, int flags);
	ssize_t sendmsg(ioctx_t* ctx, const struct msghdr* msg, int flags);
	ssize_t sendmsg_internal(ioctx_t* ctx, const struct msghdr* msg, int flags);
	ssize_t writev(ioctx_t* ctx, const struct iovec* iov, int iovcnt);
	int read_poll(ioctx_t* ctx, PollNode* node);
	int write_poll(ioctx_t* ctx, PollNode* node);

private:
	short ReadPollEventStatus();
	short WritePollEventStatus();

private:
	PollChannel read_poll_channel;
	PollChannel write_poll_channel;
	// Lock protecting the pipe's data structrues.
	kthread_mutex_t pipe_lock;
	// Lock protecting passing_count and passing_count only.
	kthread_mutex_t pass_lock;
	// Condition for when readers can wake up.
	kthread_cond_t read_cond;
	// Condition for when writers can wake up.
	kthread_cond_t write_cond;
	// The id of a thread that has plegded to write and can be yielded to.
	uintptr_t sender_system_tid;
	// The id of a thread that has plegded to receive and can be yielded to.
	uintptr_t receiver_system_tid;
	// The ring buffer containing segments.
	uint8_t* buffer;
	// The offset in the ring buffer where data begins.
	size_t buffer_offset;
	// The amount of data used after buffer_offset in the ring buffer.
	size_t buffer_used;
	// The size of the ring buffer.
	size_t buffer_size;
	// The position of the last header in the ring buffer, a new header is
	// required if last_header_position == buffer_used.
	size_t last_header_position;
	// The buffer size as far as the reader is concerned (the writer decides).
	size_t pretended_read_buffer_size;
	// This many readers have pledged to write.
	size_t pledged_write;
	// This many writers have pledged to read.
	size_t pledged_read;
	// How many file descriptors are passed on this socket.
	size_t passing_count;
	// How many times this socket itself is being passed.
	size_t passed_count;
	// Atomically incremented on read/write close, number two does the delete.
	unsigned long closers;
	// Whether anyone still has this channel open for reading.
	bool any_reading;
	// Whether anyone still has this channel open for writing.
	bool any_writing;
	// Whether writing with no readers will send SIGPIPE.
	bool is_sigpipe_enabled;

};

PipeChannel::PipeChannel(uint8_t* buffer, size_t buffer_size)
{
	pipe_lock = KTHREAD_MUTEX_INITIALIZER;
	pass_lock = KTHREAD_MUTEX_INITIALIZER;
	read_cond = KTHREAD_COND_INITIALIZER;
	write_cond = KTHREAD_COND_INITIALIZER;
	sender_system_tid = 0;
	receiver_system_tid = 0;
	this->buffer = buffer;
	buffer_offset = 0;
	buffer_used = 0;
	this->buffer_size = buffer_size;
	last_header_position = 0;
	pretended_read_buffer_size = buffer_size;
	pledged_write = 0;
	pledged_read = 0;
	passing_count = 0;
	passed_count = 0;
	closers = 0;
	any_reading = true;
	any_writing = true;
	is_sigpipe_enabled = true;
}

PipeChannel::~PipeChannel()
{
	// Drain the ring buffer contents and deference passed file descriptors.
	while ( buffer_used )
	{
		struct segment_header header;
		assert(sizeof(header) <= buffer_used);
		ReadBuffer(CopyToKernel, &header, sizeof(header), 0);
		while ( 0 < header.ancillary )
		{
			struct cmsghdr cmsg;
			assert(sizeof(cmsg) <= header.ancillary);
			assert(sizeof(header) <= buffer_used);
			Dequeue(CopyToKernel, &cmsg, sizeof(cmsg), false, 0);
			header.ancillary -= sizeof(cmsg);
			size_t data = cmsg.cmsg_len - sizeof(struct cmsghdr);
			if ( cmsg.cmsg_level == SOL_SOCKET && cmsg.cmsg_type == SCM_RIGHTS )
			{
				size_t fds = data / sizeof(int);
				for ( size_t i = 0; i < fds; i++ )
				{
					uintptr_t ptr;
					assert(sizeof(header) <= buffer_used);
					Dequeue(CopyToKernel, &ptr, sizeof(ptr), false, 0);
					header.ancillary -= sizeof(ptr);
					Ref<Descriptor> desc;
					desc.Import(ptr);
					passing_count--;
				}
			}
			else
			{
				assert(sizeof(header) <= buffer_used);
				Dequeue(NULL, NULL, data, false, 0);
				header.ancillary -= data;
			}
		}
		if ( header.normal )
		{
			assert(sizeof(header) <= buffer_used);
			Dequeue(NULL, NULL, header.normal, false, 0);
			header.normal -= header.normal;
		}
	}
	assert(!passed_count && !passing_count);
	delete[] buffer;
}

void PipeChannel::CloseReading()
{
	kthread_mutex_lock(&pipe_lock);
	any_reading = false;
	kthread_cond_broadcast(&write_cond);
	read_poll_channel.Signal(ReadPollEventStatus());
	write_poll_channel.Signal(WritePollEventStatus());
	kthread_mutex_unlock(&pipe_lock);
	unsigned long count = InterlockedIncrement(&closers).n;
	if ( count == 2 )
		delete this;
}

void PipeChannel::CloseWriting()
{
	kthread_mutex_lock(&pipe_lock);
	any_writing = false;
	kthread_cond_broadcast(&read_cond);
	read_poll_channel.Signal(ReadPollEventStatus());
	write_poll_channel.Signal(WritePollEventStatus());
	kthread_mutex_unlock(&pipe_lock);
	unsigned long count = InterlockedIncrement(&closers).n;
	if ( count == 2 )
		delete this;
}

bool PipeChannel::WriteBuffer(bool (*copy_from_src)(void*, const void*, size_t),
                              const void* src_ptr,
                              size_t amount,
                              size_t position)
{
	size_t write_offset = (buffer_offset + position) % buffer_size;
	size_t linear = buffer_size - write_offset;
	size_t first = linear < amount ? linear : amount;
	const unsigned char* src = (const unsigned char*) src_ptr;
	if ( !copy_from_src(buffer + write_offset, src, first) )
		return false;
	if ( first < amount &&
	     !copy_from_src(buffer, src + first, amount - first) )
		return false;
	return true;
}

bool PipeChannel::ReadBuffer(bool (*copy_to_dest)(void*, const void*, size_t),
                             void* dest_ptr,
                             size_t amount,
                             size_t position)
{
	size_t offset = (buffer_offset + position) % buffer_size;
	size_t linear = buffer_size - offset;
	size_t first = linear < amount ? linear : amount;
	unsigned char* dest = (unsigned char*) dest_ptr;
	if ( !copy_to_dest(dest, buffer + offset, first) )
		return false;
	if ( first < amount &&
	     !copy_to_dest(dest + first, buffer, amount - first) )
		return false;
	return true;
}

bool PipeChannel::Enqueue(bool (*copy_from_src)(void*, const void*, size_t),
                          const void* src_ptr,
                          size_t amount,
                          bool is_ancillary)
{
	struct segment_header header = { 0, 0 };
	assert(last_header_position <= buffer_used);
	// Try to unify with a previous segment header if any or make a new one.
	bool has_header = buffer_used - last_header_position;
	if ( has_header )
		ReadBuffer(CopyToKernel, &header, sizeof(header), last_header_position);
	if ( is_ancillary )
	{
		// Ancillary data must be at the start of the segment.
		if ( header.normal )
		{
			last_header_position = buffer_used;
			assert(last_header_position <= buffer_used);
			has_header = false;
			memset(&header, 0, sizeof(header));
		}
		header.ancillary += amount;
	}
	else
		header.normal += amount;
	assert(has_header || sizeof(header) <= buffer_size - buffer_used);
	size_t header_size = has_header ? 0 : sizeof(header);
	size_t position = buffer_used + header_size;
	assert(amount <= buffer_size - position);
	if ( !WriteBuffer(copy_from_src, src_ptr, amount, position) )
		return false;
	WriteBuffer(CopyFromKernel, &header, sizeof(header), last_header_position);
	buffer_used += amount + header_size;
	kthread_cond_broadcast(&read_cond);
	read_poll_channel.Signal(ReadPollEventStatus());
	write_poll_channel.Signal(WritePollEventStatus());
	return true;
}

void PipeChannel::Unenqueue(bool (*copy_to_dest)(void*, const void*, size_t),
                            void* dest_ptr,
                            size_t amount,
                            size_t saved_last_header_position)
{
	struct segment_header header;
	assert(sizeof(header) <= buffer_used);
	assert(last_header_position <= buffer_used);
	// Remove the data from the last segment header.
	ReadBuffer(CopyToKernel, &header, sizeof(header), last_header_position);
	if ( header.ancillary )
	{
		assert(amount <= header.ancillary);
		header.ancillary -= amount;
	}
	else
	{
		assert(amount <= header.normal);
		header.normal -= amount;
	}
	assert(amount <= buffer_used - sizeof(header));
	if ( copy_to_dest )
		ReadBuffer(CopyToKernel, dest_ptr, amount, buffer_used - amount);
	buffer_used -= amount;
	// Remove the last segment header if it becomes empty.
	if ( header.ancillary || header.normal )
		WriteBuffer(CopyFromKernel, &header, sizeof(header),
		            last_header_position);
	else
	{
		buffer_used -= sizeof(header);
		last_header_position = saved_last_header_position;
	}
	kthread_cond_broadcast(&write_cond);
	read_poll_channel.Signal(ReadPollEventStatus());
	write_poll_channel.Signal(WritePollEventStatus());
}

bool PipeChannel::Dequeue(bool (*copy_to_dest)(void*, const void*, size_t),
                          void* dest_ptr,
                          size_t amount,
                          bool peek,
                          size_t peek_offset)
{
	assert(peek || !peek_offset);
	struct segment_header header;
	assert(sizeof(header) <= buffer_used);
	assert(last_header_position <= buffer_used);
	assert(peek_offset <= buffer_used - sizeof(header));
	if ( copy_to_dest &&
	     !ReadBuffer(copy_to_dest, dest_ptr, amount,
	                 sizeof(header) + peek_offset) )
		return false;
	if ( !peek )
	{
		// Remove the data from the segment.
		ReadBuffer(CopyToKernel, &header, sizeof(header), 0);
		if ( header.ancillary )
		{
			assert(amount <= header.ancillary);
			header.ancillary -= amount;
		}
		else
		{
			assert(amount <= header.normal);
			header.normal -= amount;
		}
		// Shift the segment header.
		if ( header.ancillary || header.normal )
			WriteBuffer(CopyFromKernel, &header, sizeof(header), amount);
		else
			amount += sizeof(header);
		buffer_offset = (buffer_offset + amount) % buffer_size;
		buffer_used -= amount;
		if ( amount <= last_header_position )
			last_header_position -= amount;
		else
			last_header_position = 0;
		kthread_cond_broadcast(&write_cond);
		read_poll_channel.Signal(ReadPollEventStatus());
		write_poll_channel.Signal(WritePollEventStatus());
		// Realign the buffer if it becomes empty.
		if ( !buffer_used )
		{
			buffer_offset = 0;
			last_header_position = 0;
		}
	}
	return true;
}

bool PipeChannel::pass()
{
	// The Unix socket is being passed on another socket.
	ScopedLock lock(&pass_lock);
	// If this socket has descriptors passed on it, then refuse to pass it over
	// another socket to avoid reference count cycles.
	if ( passing_count )
		return false;
	passed_count++;
	return true;
}

void PipeChannel::unpass()
{
	// The Unix socket is no longer being passed on another socket.
	ScopedLock lock(&pass_lock);
	assert(passed_count);
	assert(!passing_count);
	passed_count--;
}

ssize_t PipeChannel::recv(ioctx_t* ctx, uint8_t* buf, size_t count, int flags)
{
	struct iovec iov;
	memset(&iov, 0, sizeof(iov));
	iov.iov_base = (void*) buf;
	iov.iov_len = count;
	struct msghdr msg;
	memset(&msg, 0, sizeof(msg));
	msg.msg_iov = &iov;
	msg.msg_iovlen = 1;
	return recvmsg_internal(ctx, &msg, flags);
}

ssize_t PipeChannel::readv(ioctx_t* ctx, const struct iovec* iov, int iovcnt)
{
	struct msghdr msg;
	memset(&msg, 0, sizeof(msg));
	msg.msg_iov = (struct iovec*) iov;
	msg.msg_iovlen = iovcnt;
	return recvmsg_internal(ctx, &msg, 0);
}

ssize_t PipeChannel::recvmsg(ioctx_t* ctx, struct msghdr* msg_ptr, int flags)
{
	// recvmsg can only be called through Unix sockets and not regular pipes.
	struct msghdr msg;
	if ( !ctx->copy_from_src(&msg, msg_ptr, sizeof(msg)) )
		return -1;
	if ( msg.msg_iovlen < 0 || IOV_MAX < msg.msg_iovlen )
		return errno = EINVAL, -1;
	size_t iov_size = msg.msg_iovlen * sizeof(struct iovec);
	struct iovec* iov = new struct iovec[msg.msg_iovlen];
	if ( !iov )
		return -1;
	struct iovec* user_iov = msg.msg_iov;
	if ( !ctx->copy_from_src(iov, user_iov, iov_size) )
		return delete[] iov, -1;
	msg.msg_iov = iov;
	ssize_t result = recvmsg_internal(ctx, &msg, flags);
	msg.msg_iov = user_iov;
	delete[] iov;
	if ( !ctx->copy_to_dest(msg_ptr, &msg, sizeof(msg)) )
		return -1;
	return result;
}

ssize_t PipeChannel::recvmsg_internal(ioctx_t* ctx,
                                      struct msghdr* msg,
                                      int flags)
{
	msg->msg_flags = 0;
	if ( flags & ~(MSG_PEEK | MSG_WAITALL | MSG_CMSG_CLOEXEC |
	               MSG_CMSG_CLOFORK) )
		return errno = EINVAL, -1;
	if ( SSIZE_MAX < TruncateIOVec(msg->msg_iov, msg->msg_iovlen, SSIZE_MAX) )
		return errno = EINVAL, -1;
	Thread* this_thread = CurrentThread();
	this_thread->yield_to_tid = sender_system_tid;
	ScopedLockSignal lock(&pipe_lock);
	if ( !lock.IsAcquired() )
		return errno = EINTR, -1;
	receiver_system_tid = this_thread->system_tid;
	// Receive the first segment only in the ring buffer and wait for it.
	while ( !buffer_used )
	{
		// EOF if there are no writers left.
		if ( !any_writing )
		{
			msg->msg_controllen = 0;
			return 0;
		}
		// If a thread has plegded to send, yield to it.
		this_thread->yield_to_tid = sender_system_tid;
		if ( pledged_write )
		{
			pledged_read++;
			kthread_mutex_unlock(&pipe_lock);
			kthread_yield();
			kthread_mutex_lock(&pipe_lock);
			pledged_read--;
			continue;
		}
		// Wait for data to arrive in the ring buffer.
		if ( ctx->dflags & O_NONBLOCK )
			return errno = EWOULDBLOCK, -1;
		pledged_read++;
		bool interrupted = !kthread_cond_wait_signal(&read_cond, &pipe_lock);
		pledged_read--;
		if ( interrupted )
			return errno = EINTR, -1;
	}
	// Peeking iterates through the segment without removing data.
	bool peek = flags & MSG_PEEK;
	size_t peeked = 0;
	// The remaining user-space control data (if any), incremented as it is
	// being written to, and the final values are used to tell the caller how
	// much control data was written.
	unsigned char* control = (unsigned char*) msg->msg_control;
	size_t control_length = msg->msg_controllen;
	// Whether reading the control data failed, and whether it was due to a
	// harmless truncation (in which case the system call doesn't fail).
	bool failed = false;
	bool truncated = false;
	// Read the ancillary data, if any, and discard it if the caller didn't
	// expect control data or if reading the control the data failed.
	struct segment_header header;
	assert(sizeof(header) <= buffer_used);
	ReadBuffer(CopyToKernel, &header, sizeof(header), 0);
	while ( 0 < header.ancillary )
	{
		// Read the nested cmsg header and find the control message type.
		struct cmsghdr cmsg;
		assert(sizeof(cmsg) <= header.ancillary);
		Dequeue(CopyToKernel, &cmsg, sizeof(cmsg), peek, peeked);
		if ( peek )
			peeked += sizeof(cmsg);
		header.ancillary -= sizeof(cmsg);
		// Determine how much data the caller gets after any truncation and
		// correct the control message header given to the caller.
		size_t data = cmsg.cmsg_len - sizeof(struct cmsghdr);
		size_t truncated_data = data;
		if ( cmsg.cmsg_level == SOL_SOCKET && cmsg.cmsg_type == SCM_RIGHTS )
			truncated_data = (data / sizeof(int)) * sizeof(int);
		size_t available_data =
			sizeof(cmsg) <= control_length ? control_length - sizeof(cmsg) : 0;
		if ( available_data < truncated_data )
			truncated_data = available_data;
		cmsg.cmsg_len = sizeof(struct cmsghdr) + truncated_data;
		// Copy the control message header to the caller.
		if ( sizeof(cmsg) <= control_length &&
		     ctx->copy_to_dest(control, &cmsg, sizeof(cmsg)) )
		{
			control += sizeof(cmsg);
			control_length -= sizeof(cmsg);
		}
		else if ( !failed )
			truncated = failed = true;
		// Passed file descriptors needs to be unserialized and allocated in the
		// process file descriptor table.
		if ( cmsg.cmsg_level == SOL_SOCKET && cmsg.cmsg_type == SCM_RIGHTS )
		{
			int fdflags = 0;
			if ( flags & MSG_CMSG_CLOEXEC )
				fdflags |= FD_CLOEXEC;
			if ( flags & MSG_CMSG_CLOFORK )
				fdflags |= FD_CLOFORK;
			Process* process = CurrentProcess();
			Ref<DescriptorTable> dtable = process->GetDTable();
			size_t fds = data / sizeof(int);
			// Preallocate the needed file descriptor slots so it doesn't fail
			// later and it becomes possible to do an all-or-nothing operation.
			int reservation = 0;
			if ( !failed &&
			     (INT_MAX < fds || !dtable->Reserve(fds, &reservation)) )
			{
				errno = EMSGSIZE;
				failed = true;
			}
			for ( size_t i = 0; i < fds; i++ )
			{
				uintptr_t ptr;
				Dequeue(CopyToKernel, &ptr, sizeof(ptr), peek, peeked);
				if ( peek )
					peeked += sizeof(ptr);
				header.ancillary -= sizeof(ptr);
				Ref<Descriptor> desc;
				desc.Import(ptr);
				if ( peek )
					desc.Export();
				else
				{
					desc->unpass();
					kthread_mutex_lock(&pass_lock);
					passing_count--;
					kthread_mutex_unlock(&pass_lock);
				}
				if ( failed )
					continue;
				if ( control_length < sizeof(int) )
				{
					truncated = failed = true;
					continue;
				}
				int fd = dtable->Allocate(desc, fdflags, 0, &reservation);
				assert(0 <= fd);
				if ( !ctx->copy_to_dest(control, &fd, sizeof(fd)) )
				{
					// The file descriptor leaks because the caller was faulty.
					failed = true;
					continue;
				}
				control += sizeof(fd);
				control_length -= sizeof(fd);
			}
			dtable->Unreserve(&reservation);
		}
		else
		{
			// Transfer the control data directly to the caller and truncate it
			// if it was too long.
			size_t amount = control_length < data ? control_length : data;
			if ( failed ||
			     !Dequeue(ctx->copy_to_dest, control, amount, peek, peeked) )
			{
				failed = true;
				amount = data;
				Dequeue(NULL, NULL, amount, peek, peeked);
			}
			if ( !failed && amount < data )
				failed = truncated = true;
			if ( peek )
				peeked += data;
			header.ancillary -= data;
		}
		if ( !failed )
		{
			// Zero the alignment padding between the caller's control messages.
			size_t misaligned = CMSG_ALIGN(data) - data;
			if ( control_length <= misaligned &&
			     ctx->zero_dest(control, misaligned) )
			{
				control += misaligned;
				control_length -= misaligned;
			}
			else if ( header.ancillary )
				truncated = failed = true;
		}
	}
	// Fail if the control data couldn't be read and was discarded. It's not an
	// error if the control data was simply truncated.
	if ( !truncated && failed )
		return -1;
	msg->msg_controllen -= control_length;
	if ( truncated )
		msg->msg_flags |= MSG_CTRUNC;
	// Read the regular data in the segment.
	ssize_t so_far = 0;
	int iov_i = 0;
	size_t iov_offset = 0;
	while ( iov_i < msg->msg_iovlen && so_far < SSIZE_MAX )
	{
		size_t maxcount = SSIZE_MAX - so_far;
		struct iovec* iov = &msg->msg_iov[iov_i];
		uint8_t* buf = (uint8_t*) iov->iov_base + iov_offset;
		size_t count = iov->iov_len - iov_offset;
		if ( maxcount < count )
			count = maxcount;
		if ( count == 0 )
		{
			iov_i++;
			iov_offset = 0;
			continue;
		}
		receiver_system_tid = this_thread->system_tid;
		// If we depleted the segment, try to wait for additional data if a
		// writer has committed to writing more data or if MSG_WAITALL wants
		// all its data.
		while ( !header.normal && any_writing )
		{
			if ( peek )
				return so_far;
			// If the segment is empty but the buffer is non-empty, then there
			// already is another segment which shouldn't be received yet.
			if ( buffer_used )
				return so_far;
			this_thread->yield_to_tid = sender_system_tid;
			if ( pledged_write )
			{
				// Yield to the thread that has pledged to write more data.
				pledged_read++;
				kthread_mutex_unlock(&pipe_lock);
				kthread_yield();
				kthread_mutex_lock(&pipe_lock);
				pledged_read--;
			}
			else
			{
				// Wait for the remaining data to arrive if MSG_WAITALL.
				if ( !(flags & MSG_WAITALL) && so_far )
					return so_far;
				if ( ctx->dflags & O_NONBLOCK )
					return errno = EWOULDBLOCK, -1;
				pledged_read++;
				bool interrupted =
					!kthread_cond_wait_signal(&read_cond, &pipe_lock);
				pledged_read--;
				if ( interrupted )
					return so_far ? so_far : (errno = EINTR, -1);
			}
			if ( !buffer_used )
				continue;
			// Reread the header as another thread has updated it.
			assert(sizeof(header) <= buffer_used);
			ReadBuffer(CopyToKernel, &header, sizeof(header), 0);
			// Don't cross into another segment with ancillary data.
			if ( header.ancillary )
				return so_far;
		}
		// EOF if there are no writers left.
		if ( !header.normal && !any_writing )
			break;
		// Transfer the normal data to the caller.
		size_t amount = count;
		if ( header.normal < amount )
			amount = header.normal;
		if ( !Dequeue(ctx->copy_to_dest, buf, amount, peek, peeked) )
			return so_far ? so_far : -1;
		so_far += amount;
		if ( peek )
			peeked += amount;
		iov_offset += amount;
		header.normal -= amount;
		if ( iov_offset == iov->iov_len )
		{
			iov_i++;
			iov_offset = 0;
		}
	}
	return so_far;
}

ssize_t PipeChannel::send(ioctx_t* ctx, const uint8_t* buf, size_t count,
                          int flags)
{
	struct iovec iov;
	memset(&iov, 0, sizeof(iov));
	iov.iov_base = (void*) buf;
	iov.iov_len = count;
	struct msghdr msg;
	memset(&msg, 0, sizeof(msg));
	msg.msg_iov = &iov;
	msg.msg_iovlen = 1;
	return sendmsg_internal(ctx, &msg, flags);
}

ssize_t PipeChannel::writev(ioctx_t* ctx, const struct iovec* iov, int iovcnt)
{
	struct msghdr msg;
	memset(&msg, 0, sizeof(msg));
	msg.msg_iov = (struct iovec*) iov;
	msg.msg_iovlen = iovcnt;
	return sendmsg_internal(ctx, &msg, 0);
}

ssize_t PipeChannel::sendmsg(ioctx_t* ctx,
                             const struct msghdr* msg_ptr,
                             int flags)
{
	// sendmsg can only be called through Unix sockets and not regular pipes.
	struct msghdr msg;
	if ( !ctx->copy_from_src(&msg, msg_ptr, sizeof(msg)) )
		return -1;
	if ( msg.msg_iovlen < 0 || IOV_MAX < msg.msg_iovlen )
		return errno = EINVAL, -1;
	if ( msg.msg_name )
		return errno = EISCONN, -1;
	size_t iov_size = msg.msg_iovlen * sizeof(struct iovec);
	struct iovec* iov = new struct iovec[msg.msg_iovlen];
	if ( !iov )
		return -1;
	if ( !ctx->copy_from_src(iov, msg.msg_iov, iov_size) )
		return delete[] iov, -1;
	msg.msg_iov = iov;
	ssize_t result = sendmsg_internal(ctx, &msg, flags);
	delete[] iov;
	return result;
}

ssize_t PipeChannel::sendmsg_internal(ioctx_t* ctx,
                                      const struct msghdr* msg,
                                      int flags)
{
	if ( flags & ~(MSG_NOSIGNAL) )
		return errno = EINVAL, -1;
	if ( SSIZE_MAX < TruncateIOVec(msg->msg_iov, msg->msg_iovlen, SSIZE_MAX) )
		return errno = EINVAL, -1;
	// Measure how much control data buffer space is required to make sure it
	// can be sent in a single attempt without unnecessary truncation or
	// fragmentation. This is only used as an estimate, if the control data
	// changes between the two reads of it, then the caller loses the ability
	// to know how much control data was sent.
	unsigned char* control_ptr = (unsigned char*) msg->msg_control;
	size_t required = 1; // At least one free byte, plus control below.
	for ( size_t control_offset = 0; control_offset < msg->msg_controllen; )
	{
		// Read the next control message header.
		size_t control_left = msg->msg_controllen - control_offset;
		struct cmsghdr cmsg;
		if ( control_left < sizeof(cmsg) )
			return errno = EINVAL, -1;
		unsigned char* cmsg_ptr = control_ptr + control_offset;
		if ( !ctx->copy_from_src(&cmsg, cmsg_ptr, sizeof(cmsg)) )
			return -1;
		if ( cmsg.cmsg_len < sizeof(struct cmsghdr) ||
		     control_left < cmsg.cmsg_len )
			return errno = EINVAL, -1;
		// Determine how much space is needed for the message.
		size_t needed = cmsg.cmsg_len;
		size_t data_size = cmsg.cmsg_len - sizeof(struct cmsghdr);
		if ( cmsg.cmsg_level == SOL_SOCKET && cmsg.cmsg_type == SCM_RIGHTS )
		{
			size_t pointers_size;
			if ( __builtin_mul_overflow(data_size / sizeof(int),
			                            sizeof(uintptr_t), &pointers_size) ||
			     __builtin_add_overflow(sizeof(struct cmsghdr), pointers_size,
			                            &needed) )
				return errno = EMSGSIZE, -1;
		}
		if ( __builtin_add_overflow(required, needed, &required) )
				return errno = EMSGSIZE, -1;
		control_offset += CMSG_ALIGN(cmsg.cmsg_len);
	}
	Thread* this_thread = CurrentThread();
	this_thread->yield_to_tid = receiver_system_tid;
	ScopedLockSignal lock(&pipe_lock);
	if ( !lock.IsAcquired() )
		return errno = EINTR, -1;
	sender_system_tid = this_thread->system_tid;
	bool need_header;
	struct segment_header header;
	// Wait until we can send all the control data in one attempt plus one byte.
	while ( true )
	{
		// Send SIGPIPE or fail with EPIPE if there are no readers left.
		if ( !any_reading )
		{
			if ( is_sigpipe_enabled && !(flags & MSG_NOSIGNAL) )
				CurrentThread()->DeliverSignal(SIGPIPE);
			return errno = EPIPE, -1;
		}
		if ( buffer_size < sizeof(header) )
			return errno = EMSGSIZE, -1;
		if ( buffer_size - sizeof(header) < required )
			return errno = EMSGSIZE, -1;
		// Check whether there is an existing segment header we can combine.
		need_header = true;
		if ( last_header_position < buffer_used )
		{
			assert(sizeof(header) <= buffer_used - last_header_position);
			ReadBuffer(CopyToKernel, &header, sizeof(header),
			           last_header_position);
			need_header = msg->msg_controllen && header.normal;
		}
		// Check if there's enough buffer space for the request now.
		size_t available = buffer_size - buffer_used;
		if ( need_header &&
		     sizeof(header) <= available &&
		     required <= available - sizeof(header) )
			break;
		if ( !need_header && required <= available )
			break;
		// Wait for more ring buffer space to be available.
		if ( ctx->dflags & O_NONBLOCK )
			return errno = EWOULDBLOCK, -1;
		this_thread->yield_to_tid = receiver_system_tid;
		if ( !kthread_cond_wait_signal(&write_cond, &pipe_lock) )
			return errno = EINTR, -1;
	}
	// Write the control data now that we've taken steps avoid truncation.
	for ( size_t control_offset = 0; control_offset < msg->msg_controllen; )
	{
		// Read the next control message header.
		size_t control_left = msg->msg_controllen - control_offset;
		struct cmsghdr cmsg;
		if ( control_left < sizeof(cmsg) )
			return errno = EINVAL, -1;
		unsigned char* cmsg_ptr = control_ptr + control_offset;
		if ( !ctx->copy_from_src(&cmsg, cmsg_ptr, sizeof(cmsg)) )
			return -1;
		if ( cmsg.cmsg_len < sizeof(struct cmsghdr) ||
		     control_left < cmsg.cmsg_len )
			return errno = EINVAL, -1;
		// Determine how much space is needed for the message.
		size_t needed = cmsg.cmsg_len;
		size_t data_size = cmsg.cmsg_len - sizeof(struct cmsghdr);
		if ( cmsg.cmsg_level == SOL_SOCKET && cmsg.cmsg_type == SCM_RIGHTS )
		{
			size_t pointers_size;
			if ( __builtin_mul_overflow(data_size / sizeof(int),
			                            sizeof(uintptr_t), &pointers_size) ||
			     __builtin_add_overflow(sizeof(struct cmsghdr), pointers_size,
			                            &needed) )
				return errno = EMSGSIZE, -1;
		}
		else
			return errno = EINVAL, -1;
		// Reject the control message if the ancillary data changed midway and
		// more buffer space is required than was provisioned above.
		if ( need_header &&
		     __builtin_add_overflow(needed, sizeof(header), &needed) )
			return errno = EMSGSIZE, -1;
		if ( buffer_size - buffer_used < needed )
			return errno = EMSGSIZE, -1;
		// Take into account potentially needing a segment header.
		size_t saved_last_header_position = last_header_position;
		Enqueue(CopyFromKernel, &cmsg, sizeof(cmsg), true);
		need_header = false;
		unsigned char* data_ptr = control_ptr + control_offset + sizeof(cmsg);
		bool failed = false;
		// File descriptors needs to be serialized as a raw pointer to the
		// underlying object which may be larger than an int.
		if ( cmsg.cmsg_level == SOL_SOCKET && cmsg.cmsg_type == SCM_RIGHTS )
		{
			Process* process = CurrentProcess();
			Ref<DescriptorTable> dtable = process->GetDTable();
			size_t fds = data_size / sizeof(int);
			for ( size_t i = 0; i < fds; i++ )
			{
				unsigned char* buf = data_ptr + sizeof(int) * i;
				int fd;
				if ( !(failed = !ctx->copy_from_src(&fd, buf, sizeof(fd))) )
				{
					Ref<Descriptor> desc = dtable->Get(fd);
					if ( !(failed = !desc) )
					{
						// File descriptors are reference counted and there must
						// never any reference count cycles, which can happen if
						// a socket is sent on a socket. Additionally a tall
						// tower of reference counted objects containing
						// reference counted objects can overflow the stack in
						// the destructors. Other operating systems have a full
						// garbage collection system to avoid these problems,
						// but here these properties are protected with three
						// strict rules.
						kthread_mutex_lock(&pass_lock);
						// 1. Sockets themselves being sent cannot be sent on.
						if ( passed_count )
							failed = true;
						// 2. Sockets cannot be sent on themselves (either
						//    endpoint). Prevent it by marking themselves as
						//    being sent on before asking if the descriptor is
						// not being sent on.
						passing_count++;
						kthread_mutex_unlock(&pass_lock);
						// 3. Sockets cannot send another socket being sent on.
						if ( failed || (failed = !desc->pass()) )
						{
							errno = EPERM;
							kthread_mutex_lock(&pass_lock);
							passing_count--;
							kthread_mutex_unlock(&pass_lock);
						}
						else
						{
							// Pass file descriptors as a leaked reference to
							// the underlying reference counted descriptor.
							uintptr_t ptr = desc.Export();
							Enqueue(CopyFromKernel, &ptr, sizeof(ptr), true);
						}
					}
				}
				// If any file descriptors couldn't be sent, undo the entire
				// control message, so the caller knows that either none of them
				// got sent or all of them.
				if ( failed )
				{
					for ( ; i; i-- )
					{
						uintptr_t ptr;
						Unenqueue(CopyToKernel, &ptr, sizeof(ptr),
						          saved_last_header_position);
						Ref<Descriptor> desc;
						desc.Import(ptr);
						desc->unpass();
						kthread_mutex_lock(&pass_lock);
						passing_count--;
						kthread_mutex_unlock(&pass_lock);
					}
					break;
				}
			}
		}
		else
		{
			if ( !Enqueue(ctx->copy_from_src, data_ptr, data_size, true) )
				failed = true;
		}
		// Undo the control message header if the control message data couldn't
		// be sent.
		if ( failed )
		{
			Unenqueue(NULL, NULL, sizeof(cmsg), saved_last_header_position);
			return -1;
		}
		control_offset += CMSG_ALIGN(cmsg.cmsg_len);
	}
	// Write the normal data to the ring buffer.
	ssize_t so_far = 0;
	int iov_i = 0;
	size_t iov_offset = 0;
	while ( iov_i < msg->msg_iovlen && so_far < SSIZE_MAX )
	{
		size_t max_count = SSIZE_MAX - so_far;
		struct iovec* iov = &msg->msg_iov[iov_i];
		const uint8_t* buf = (const uint8_t*) iov->iov_base + iov_offset;
		size_t count = iov->iov_len - iov_offset;
		if ( max_count < count )
			count = max_count;
		if ( count == 0 )
		{
			iov_i++;
			iov_offset = 0;
			continue;
		}
		// Handle when the buffer space is exhausted.
		sender_system_tid = this_thread->system_tid;
		size_t overhead = need_header ? sizeof(header) : 0;
		while ( any_reading && buffer_size - buffer_used <= overhead )
		{
			// Yield to the thread that pledged to read more data.
			this_thread->yield_to_tid = receiver_system_tid;
			if ( pledged_read )
			{
				pledged_write++;
				kthread_mutex_unlock(&pipe_lock);
				kthread_yield();
				kthread_mutex_lock(&pipe_lock);
				pledged_write--;
			}
			else
			{
				// If the estimated of the required buffer space was accurate,
				// it will always have been possible to write at least one byte
				// of data.
				if ( so_far )
					return so_far;
				// Wait for more buffer space to become available.
				if ( ctx->dflags & O_NONBLOCK )
					return so_far ? so_far : errno = EWOULDBLOCK, -1;
				pledged_write++;
				bool interrupted =
					!kthread_cond_wait_signal(&write_cond, &pipe_lock);
				pledged_write--;
				if ( interrupted )
					return so_far ? so_far : errno = EINTR, -1;
			}
			need_header = last_header_position == buffer_used;
			overhead = need_header ? sizeof(header) : 0;
		}
		// Send SIGPIPE or fail with EPIPE if there are no readers left.
		if ( !any_reading )
		{
			if ( so_far )
				return so_far;
			if ( is_sigpipe_enabled && !(flags & MSG_NOSIGNAL) )
				CurrentThread()->DeliverSignal(SIGPIPE);
			return errno = EPIPE, -1;
		}
		// Write the normal data to the ring buffer.
		size_t available = buffer_size - buffer_used - overhead;
		size_t amount = count;
		if ( available < amount )
			amount = available;
		if ( !Enqueue(ctx->copy_from_src, buf, amount, false) )
			return so_far ? so_far : -1;
		so_far += amount;
		iov_offset += amount;
		if ( iov_offset == iov->iov_len )
		{
			iov_i++;
			iov_offset = 0;
		}
	}
	return so_far;
}

short PipeChannel::ReadPollEventStatus()
{
	short status = 0;
	if ( !any_writing && !buffer_used )
		status |= POLLHUP;
	if ( buffer_used )
		status |= POLLIN | POLLRDNORM;
	return status;
}

short PipeChannel::WritePollEventStatus()
{
	short status = 0;
	if ( !any_reading )
		status |= POLLERR;
	bool need_header = last_header_position == buffer_size;
	size_t needed_space = need_header ? sizeof(segment_header) : 0;
	if ( any_reading && needed_space < buffer_size - buffer_used )
		status |= POLLOUT | POLLWRNORM;
	return status;
}

int PipeChannel::read_poll(ioctx_t* /*ctx*/, PollNode* node)
{
	ScopedLock lock(&pipe_lock);
	short ret_status = ReadPollEventStatus() & node->events;
	if ( ret_status )
		return node->master->revents |= ret_status, 0;
	read_poll_channel.Register(node);
	return errno = EAGAIN, -1;
}

int PipeChannel::write_poll(ioctx_t* /*ctx*/, PollNode* node)
{
	ScopedLock lock(&pipe_lock);
	short ret_status = WritePollEventStatus() & node->events;
	if ( ret_status )
		return node->master->revents |= ret_status, 0;
	write_poll_channel.Register(node);
	return errno = EAGAIN, -1;
}

bool PipeChannel::GetSIGPIPEDelivery()
{
	ScopedLock lock(&pipe_lock);
	return is_sigpipe_enabled;
}

void PipeChannel::SetSIGPIPEDelivery(bool deliver_sigpipe)
{
	ScopedLock lock(&pipe_lock);
	is_sigpipe_enabled = deliver_sigpipe;
}

size_t PipeChannel::ReadSize()
{
	ScopedLock lock(&pipe_lock);
	return pretended_read_buffer_size;
}

size_t PipeChannel::WriteSize()
{
	ScopedLock lock(&pipe_lock);
	return buffer_size;
}

bool PipeChannel::ReadResize(size_t new_size)
{
	ScopedLock lock(&pipe_lock);
	if ( !new_size )
		return errno = EINVAL, false;
	// The reading and writing ends share the same buffer, so let the writing
	// end decide how big a buffer it wants and pretend the reading end can
	// decide too.
	pretended_read_buffer_size = new_size;
	return true;
}

bool PipeChannel::WriteResize(size_t new_size)
{
	ScopedLock lock(&pipe_lock);
	if ( !new_size )
		return errno = EINVAL, false;

	size_t max_pipe_size = 2 * 1024 * 1024;
	if ( max_pipe_size < new_size )
		new_size = max_pipe_size;

	size_t min_pipe_size = sizeof(segment_header) + 1;
	if ( new_size < min_pipe_size )
		new_size = min_pipe_size;

	// Refuse to lose data if the new size would cause truncation.
	if ( new_size < buffer_used )
		new_size = buffer_used;

	uint8_t* new_buffer = new uint8_t[new_size];
	if ( !new_buffer )
		return false;

	for ( size_t i = 0; i < buffer_used; i++ )
		new_buffer[i] = buffer[(buffer_offset + i) % buffer_size];
	delete[] buffer;
	buffer = new_buffer;
	buffer_size = new_size;
	buffer_offset = 0;

	return true;
}

PipeEndpoint::PipeEndpoint()
{
	channel = NULL;
	reading = false;
}

PipeEndpoint::~PipeEndpoint()
{
	if ( channel )
		Disconnect();
}

bool PipeEndpoint::Connect(PipeEndpoint* destination)
{
	assert(!channel);
	assert(!destination->channel);
	const size_t BUFFER_SIZE = 64 * 1024;
	size_t size = BUFFER_SIZE;
	uint8_t* buffer = new uint8_t[size];
	if ( !buffer )
		return false;
	destination->reading = !(reading = false);
	if ( !(destination->channel = channel = new PipeChannel(buffer, size)) )
	{
		delete[] buffer;
		return false;
	}
	return true;
}

void PipeEndpoint::Disconnect()
{
	if ( !channel )
		return;
	if ( reading )
		channel->CloseReading();
	else
		channel->CloseWriting();
	channel = NULL;
}

bool PipeEndpoint::pass()
{
	return channel ? channel->pass() : false;
}

void PipeEndpoint::unpass()
{
	if ( channel )
		channel->unpass();
}

ssize_t PipeEndpoint::recv(ioctx_t* ctx, uint8_t* buf, size_t count, int flags)
{
	if ( !reading )
		return errno = EBADF, -1;
	if ( !channel )
		return 0;
	ssize_t result = channel->recv(ctx, buf, count, flags);
	CurrentThread()->yield_to_tid = 0;
	Scheduler::ScheduleTrueThread();
	return result;
}

ssize_t PipeEndpoint::recvmsg(ioctx_t* ctx, struct msghdr* msg, int flags)
{
	if ( !reading )
		return errno = EBADF, -1;
	if ( !channel )
		return 0;
	ssize_t result = channel->recvmsg(ctx, msg, flags);
	CurrentThread()->yield_to_tid = 0;
	Scheduler::ScheduleTrueThread();
	return result;
}

ssize_t PipeEndpoint::send(ioctx_t* ctx, const uint8_t* buf, size_t count,
                           int flags)
{
	if ( reading )
		return errno = EBADF, -1;
	if ( !channel )
	{
		if ( !(flags & MSG_NOSIGNAL) )
			CurrentThread()->DeliverSignal(SIGPIPE);
		return errno = EPIPE, -1;
	}
	ssize_t result = channel->send(ctx, buf, count, flags);
	CurrentThread()->yield_to_tid = 0;
	Scheduler::ScheduleTrueThread();
	return result;
}

ssize_t PipeEndpoint::sendmsg(ioctx_t* ctx, const struct msghdr* msg, int flags)
{
	if ( reading )
		return errno = EBADF, -1;
	if ( !channel )
	{
		if ( !(flags & MSG_NOSIGNAL) )
			CurrentThread()->DeliverSignal(SIGPIPE);
		return errno = EPIPE, -1;
	}
	ssize_t result = channel->sendmsg(ctx, msg, flags);
	CurrentThread()->yield_to_tid = 0;
	Scheduler::ScheduleTrueThread();
	return result;
}

ssize_t PipeEndpoint::readv(ioctx_t* ctx, const struct iovec* iov, int iovcnt)
{
	if ( !reading )
		return errno = EBADF, -1;
	ssize_t result = channel->readv(ctx, iov, iovcnt);
	if ( !channel )
		return 0;
	CurrentThread()->yield_to_tid = 0;
	Scheduler::ScheduleTrueThread();
	return result;
}

ssize_t PipeEndpoint::writev(ioctx_t* ctx, const struct iovec* iov, int iovcnt)
{
	if ( reading )
		return errno = EBADF, -1;
	if ( !channel )
	{
		CurrentThread()->DeliverSignal(SIGPIPE);
		return errno = EPIPE, -1;
	}
	ssize_t result = channel->writev(ctx, iov, iovcnt);
	CurrentThread()->yield_to_tid = 0;
	Scheduler::ScheduleTrueThread();
	return result;
}

int PipeEndpoint::poll(ioctx_t* ctx, PollNode* node)
{
	if ( !channel )
		return 0;
	return reading ? channel->read_poll(ctx, node)
	               : channel->write_poll(ctx, node);
}

bool PipeEndpoint::GetSIGPIPEDelivery()
{
	if ( !channel )
		return errno = EINVAL, true;
	return !reading ? channel->GetSIGPIPEDelivery() : false;
}

bool PipeEndpoint::SetSIGPIPEDelivery(bool deliver_sigpipe)
{
	if ( !channel )
		return errno = EINVAL, false;
	if ( !reading )
		channel->SetSIGPIPEDelivery(deliver_sigpipe);
	else if ( reading && deliver_sigpipe != false )
		return errno = EINVAL, false;
	return true;
}

size_t PipeEndpoint::Size()
{
	if ( !channel )
		return errno = EINVAL, 0;
	return reading ? channel->ReadSize()
	               : channel->WriteSize();
}

bool PipeEndpoint::Resize(size_t new_size)
{
	if ( !channel )
		return errno = EINVAL, false;
	return reading ? channel->ReadResize(new_size)
	               : channel->WriteResize(new_size);
}

class PipeNode : public AbstractInode
{
public:
	PipeNode(dev_t dev, uid_t owner, gid_t group, mode_t mode);
	virtual ~PipeNode();
	virtual bool pass();
	virtual void unpass();
	virtual ssize_t readv(ioctx_t* ctx, const struct iovec* iov, int iovcnt);
	virtual ssize_t writev(ioctx_t* ctx, const struct iovec* iov, int iovcnt);
	virtual int poll(ioctx_t* ctx, PollNode* node);
	// Pipes must not provide sendmsg/recvmsg that can do file descriptor
	// passing. S_IFNEVERWRAP in type must be set if this was to be supported,
	// and the kernel would need to be audited for the assumption that only
	// filesystem sockets can do file descriptor passing.

public:
	bool Connect(PipeNode* destination);

private:
	PipeEndpoint endpoint;

};

bool PipeNode::Connect(PipeNode* destination)
{
	return endpoint.Connect(&destination->endpoint);
}

PipeNode::PipeNode(dev_t dev, uid_t owner, gid_t group, mode_t mode)
{
	inode_type = INODE_TYPE_STREAM;
	this->dev = dev;
	this->ino = (ino_t) this;
	this->stat_uid = owner;
	this->stat_gid = group;
	this->type = S_IFIFO;
	this->stat_mode = (mode & S_SETABLE) | this->type;
	supports_iovec = true;
}

PipeNode::~PipeNode()
{
}

bool PipeNode::pass()
{
	return endpoint.pass();
}

void PipeNode::unpass()
{
	endpoint.unpass();
}

ssize_t PipeNode::readv(ioctx_t* ctx, const struct iovec* iov, int iovcnt)
{
	return endpoint.readv(ctx, iov, iovcnt);
}

ssize_t PipeNode::writev(ioctx_t* ctx, const struct iovec* iov, int iovcnt)
{
	return endpoint.writev(ctx, iov, iovcnt);
}

int PipeNode::poll(ioctx_t* ctx, PollNode* node)
{
	return endpoint.poll(ctx, node);
}

int sys_pipe2(int* pipefd, int flags)
{
	int fdflags = 0;
	if ( flags & O_CLOEXEC ) fdflags |= FD_CLOEXEC;
	if ( flags & O_CLOFORK ) fdflags |= FD_CLOFORK;
	flags &= ~(O_CLOEXEC | O_CLOFORK);

	if ( flags & ~(O_NONBLOCK) )
		return errno = EINVAL, -1;

	Process* process = CurrentProcess();
	uid_t uid = process->uid;
	uid_t gid = process->gid;
	mode_t mode = 0600;

	Ref<PipeNode> recv_inode(new PipeNode(0, uid, gid, mode));
	if ( !recv_inode ) return -1;
	Ref<PipeNode> send_inode(new PipeNode(0, uid, gid, mode));
	if ( !send_inode ) return -1;

	if ( !send_inode->Connect(recv_inode.Get()) )
		return -1;

	Ref<Vnode> recv_vnode(new Vnode(recv_inode, Ref<Vnode>(NULL), 0, 0));
	Ref<Vnode> send_vnode(new Vnode(send_inode, Ref<Vnode>(NULL), 0, 0));
	if ( !recv_vnode || !send_vnode ) return -1;

	Ref<Descriptor> recv_desc(new Descriptor(recv_vnode, O_READ | flags));
	Ref<Descriptor> send_desc(new Descriptor(send_vnode, O_WRITE | flags));
	if ( !recv_desc || !send_desc ) return -1;

	Ref<DescriptorTable> dtable = process->GetDTable();
	int reservation;
	if ( !dtable->Reserve(2, &reservation) )
		return -1;
	int recv_index = dtable->Allocate(recv_desc, fdflags, 0, &reservation);
	int send_index = dtable->Allocate(send_desc, fdflags, 0, &reservation);
	assert(0 <= recv_index);
	assert(0 <= send_index);
	int ret[2] = { recv_index, send_index };
	if ( !CopyToUser(pipefd, ret, sizeof(ret)) )
		return -1;

	return 0;
}

} // namespace Sortix
