/*
 * Copyright (c) 2012-2017, 2021-2022, 2024 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.
 *
 * fs/user.cpp
 * User-space filesystem.
 */

#include <sys/types.h>

#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <signal.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <timespec.h>

#include <sortix/dirent.h>
#include <sortix/fcntl.h>
#include <sortix/ioctl.h>
#include <sortix/stat.h>
#include <sortix/timespec.h>
#include <sortix/winsize.h>

#include <fsmarshall-msg.h>

#include <sortix/kernel/descriptor.h>
#include <sortix/kernel/inode.h>
#include <sortix/kernel/ioctx.h>
#include <sortix/kernel/kernel.h>
#include <sortix/kernel/kthread.h>
#include <sortix/kernel/mtable.h>
#include <sortix/kernel/poll.h>
#include <sortix/kernel/process.h>
#include <sortix/kernel/refcount.h>
#include <sortix/kernel/scheduler.h>
#include <sortix/kernel/syscall.h>
#include <sortix/kernel/thread.h>
#include <sortix/kernel/vnode.h>

namespace Sortix {
namespace UserFS {

class ChannelDirection;
class Channel;
class ChannelNode;
class Server;
class ServerNode;
class Unode;

class ChannelDirection
{
public:
	ChannelDirection();
	~ChannelDirection();
	size_t Send(ioctx_t* ctx, const void* ptr, size_t least, size_t max);
	size_t Recv(ioctx_t* ctx, void* ptr, size_t least, size_t max);
	void SendClose();
	void RecvClose();

private:
	static const size_t BUFFER_SIZE = 8192;
	uint8_t buffer[BUFFER_SIZE];
	size_t buffer_used;
	size_t buffer_offset;
	kthread_mutex_t transfer_lock;
	kthread_cond_t not_empty;
	kthread_cond_t not_full;
	bool still_reading;
	bool still_writing;

public:
	uintptr_t sender_system_tid;
	uintptr_t receiver_system_tid;

};

class Channel
{
public:
	Channel(ioctx_t* ioctx);
	~Channel();

public:
	void InformSystemTids(uintptr_t client_tid, uintptr_t server_tid);

public:
	bool KernelSend(ioctx_t* ctx, const void* ptr, size_t count)
	{
		return KernelSend(ctx, ptr, count, count) == count;
	}
	size_t KernelSend(ioctx_t* ctx, const void* ptr, size_t least, size_t max);
	bool KernelRecv(ioctx_t* ctx, void* ptr, size_t count)
	{
		return KernelRecv(ctx, ptr, count, count) == count;
	}
	size_t KernelRecv(ioctx_t* ctx, void* ptr, size_t least, size_t max);
	void KernelClose();

public:
	bool UserSend(ioctx_t* ctx, const void* ptr, size_t count)
	{
		return UserSend(ctx, ptr, count, count) == count;
	}
	size_t UserSend(ioctx_t* ctx, const void* ptr, size_t least, size_t max);
	bool UserRecv(ioctx_t* ctx, void* ptr, size_t count)
	{
		return UserRecv(ctx, ptr, count, count) == count;
	}
	size_t UserRecv(ioctx_t* ctx, void* ptr, size_t least, size_t max);
	void UserClose();

private:
	kthread_mutex_t kernel_lock;
	kthread_mutex_t user_lock;
	kthread_mutex_t destruction_lock;
	ChannelDirection from_kernel;
	ChannelDirection from_user;
	bool kernel_closed;
	bool user_closed;

public:
	uid_t uid;
	gid_t gid;

};

class ChannelNode : public AbstractInode
{
public:
	ChannelNode();
	ChannelNode(Channel* channel);
	virtual ~ChannelNode();
	void Construct(Channel* channel);
	virtual ssize_t read(ioctx_t* ctx, uint8_t* buf, size_t count);
	virtual ssize_t write(ioctx_t* ctx, const uint8_t* buf, size_t count);

private:
	Channel* channel;

};

class Server : public Refcountable
{
public:
	Server();
	virtual ~Server();
	void Disconnect();
	void Unmount();
	Channel* Connect(ioctx_t* ctx);
	Channel* Accept(ioctx_t* ctx);
	Ref<Inode> BootstrapNode(ino_t ino, mode_t type);
	Ref<Inode> OpenNode(ino_t ino, mode_t type);

private:
	kthread_mutex_t connect_lock;
	kthread_cond_t connecting_cond;
	kthread_cond_t connectable_cond;
	uintptr_t listener_system_tid;
	uintptr_t connecter_system_tid;
	Channel* connecting;
	bool disconnected;
	bool unmounted;

};

class ServerNode : public AbstractInode
{
public:
	ServerNode(Ref<Server> server);
	virtual ~ServerNode();
	virtual Ref<Inode> accept4(ioctx_t* ctx, uint8_t* addr, size_t* addrlen,
	                           int flags);

private:
	Ref<Server> server;

};

class Unode : public Inode
{
public:
	Unode(Ref<Server> server, ino_t ino, mode_t type);
	virtual ~Unode();
	virtual bool pass();
	virtual void unpass();
	virtual void linked();
	virtual void unlinked();
	virtual int sync(ioctx_t* ctx);
	virtual int stat(ioctx_t* ctx, struct stat* st);
	virtual int statvfs(ioctx_t* ctx, struct statvfs* stvfs);
	virtual int chmod(ioctx_t* ctx, mode_t mode);
	virtual int chown(ioctx_t* ctx, uid_t owner, gid_t group);
	virtual int truncate(ioctx_t* ctx, off_t length);
	virtual off_t lseek(ioctx_t* ctx, off_t offset, int whence);
	virtual ssize_t read(ioctx_t* ctx, uint8_t* buf, size_t count);
	virtual ssize_t readv(ioctx_t* ctx, const struct iovec* iov, int iovcnt);
	virtual ssize_t pread(ioctx_t* ctx, uint8_t* buf, size_t count, off_t off);
	virtual ssize_t preadv(ioctx_t* ctx, const struct iovec* iov, int iovcnt,
	                       off_t off);
	virtual ssize_t write(ioctx_t* ctx, const uint8_t* buf, size_t count);
	virtual ssize_t writev(ioctx_t* ctx, const struct iovec* iov, int iovcnt);
	virtual ssize_t pwrite(ioctx_t* ctx, const uint8_t* buf, size_t count,
	                       off_t off);
	virtual ssize_t pwritev(ioctx_t* ctx, const struct iovec* iov, int iovcnt,
	                       off_t off);

	virtual int utimens(ioctx_t* ctx, const struct timespec* times);
	virtual int isatty(ioctx_t* ctx);
	virtual ssize_t readdirents(ioctx_t* ctx, struct dirent* dirent,
	                            size_t size, off_t start);
	virtual Ref<Inode> open(ioctx_t* ctx, const char* filename, int flags,
	                        mode_t mode);
	virtual Ref<Inode> factory(ioctx_t* ctx, const char* filename, int flags,
	                           mode_t mode);
	virtual int mkdir(ioctx_t* ctx, const char* filename, mode_t mode);
	virtual int link(ioctx_t* ctx, const char* filename, Ref<Inode> node);
	virtual int link_raw(ioctx_t* ctx, const char* filename, Ref<Inode> node);
	virtual int unlink(ioctx_t* ctx, const char* filename);
	virtual int unlink_raw(ioctx_t* ctx, const char* filename);
	virtual int rmdir(ioctx_t* ctx, const char* filename);
	virtual int rmdir_me(ioctx_t* ctx);
	virtual int symlink(ioctx_t* ctx, const char* oldname,
	                    const char* filename);
	virtual ssize_t readlink(ioctx_t* ctx, char* buf, size_t bufsiz);
	virtual int tcgetwincurpos(ioctx_t* ctx, struct wincurpos* wcp);
	virtual int ioctl(ioctx_t* ctx, int cmd, uintptr_t arg);
	virtual int tcsetpgrp(ioctx_t* ctx, pid_t pgid);
	virtual pid_t tcgetpgrp(ioctx_t* ctx);
	virtual int settermmode(ioctx_t* ctx, unsigned mode);
	virtual int gettermmode(ioctx_t* ctx, unsigned* mode);
	virtual int poll(ioctx_t* ctx, PollNode* node);
	virtual int rename_here(ioctx_t* ctx, Ref<Inode> from, const char* oldname,
	                        const char* newname);
	virtual Ref<Inode> accept4(ioctx_t* ctx, uint8_t* addr, size_t* addrlen,
	                           int flags);
	virtual int bind(ioctx_t* ctx, const uint8_t* addr, size_t addrlen);
	virtual int connect(ioctx_t* ctx, const uint8_t* addr, size_t addrlen);
	virtual int listen(ioctx_t* ctx, int backlog);
	virtual ssize_t recv(ioctx_t* ctx, uint8_t* buf, size_t count, int flags);
	virtual ssize_t recvmsg(ioctx_t* ctx, struct msghdr* msg, int flags);
	virtual ssize_t send(ioctx_t* ctx, const uint8_t* buf, size_t count,
	                     int flags);
	virtual ssize_t sendmsg(ioctx_t* ctx, const struct msghdr* msg, int flags);
	virtual int getsockopt(ioctx_t* ctx, int level, int option_name,
	                       void* option_value, size_t* option_size_ptr);
	virtual int setsockopt(ioctx_t* ctx, int level, int option_name,
	                       const void* option_value, size_t option_size);
	virtual ssize_t tcgetblob(ioctx_t* ctx, const char* name, void* buffer, size_t count);
	virtual ssize_t tcsetblob(ioctx_t* ctx, const char* name, const void* buffer, size_t count);
	virtual int unmounted(ioctx_t* ctx);
	virtual int tcdrain(ioctx_t* ctx);
	virtual int tcflow(ioctx_t* ctx, int action);
	virtual int tcflush(ioctx_t* ctx, int queue_selector);
	virtual int tcgetattr(ioctx_t* ctx, struct termios* tio);
	virtual pid_t tcgetsid(ioctx_t* ctx);
	virtual int tcsendbreak(ioctx_t* ctx, int duration);
	virtual int tcsetattr(ioctx_t* ctx, int actions, const struct termios* tio);
	virtual int shutdown(ioctx_t* ctx, int how);
	virtual int getpeername(ioctx_t* ctx, uint8_t* addr, size_t* addrsize);
	virtual int getsockname(ioctx_t* ctx, uint8_t* addr, size_t* addrsize);

private:
	bool SendMessage(Channel* channel, size_t type, void* ptr, size_t size,
	                 size_t extra = 0);
	bool RecvMessage(Channel* channel, size_t type, void* ptr, size_t size);
	void RecvError(Channel* channel);
	bool RecvBoolean(Channel* channel);
	void UnexpectedResponse(Channel* channel, struct fsm_msg_header* hdr);

private:
	ioctx_t kctx;
	Ref<Server> server;

};

//
// Implementation of Channel Directory.
//

ChannelDirection::ChannelDirection()
{
	buffer_used = 0;
	buffer_offset = 0;
	transfer_lock = KTHREAD_MUTEX_INITIALIZER;
	not_empty = KTHREAD_COND_INITIALIZER;
	not_full = KTHREAD_COND_INITIALIZER;
	still_reading = true;
	still_writing = true;
	sender_system_tid = 0;
	receiver_system_tid = 0;
}

ChannelDirection::~ChannelDirection()
{
}

size_t ChannelDirection::Send(ioctx_t* ctx, const void* ptr, size_t least, size_t max)
{
	const uint8_t* src = (const uint8_t*) ptr;
	size_t sofar = 0;
	CurrentThread()->yield_to_tid = receiver_system_tid;
	ScopedLock inner_lock(&transfer_lock);
	sender_system_tid = CurrentThread()->system_tid;
	while ( true )
	{
		while ( true )
		{
			if ( !still_reading )
				return errno = ECONNRESET, sofar;
			if ( buffer_used < BUFFER_SIZE )
				break;
			if ( least <= sofar )
				return sofar;
			if ( !kthread_cond_wait_signal(&not_full, &transfer_lock) )
				return errno = EINTR, sofar;
		}

		size_t use_offset = (buffer_offset + buffer_used) % BUFFER_SIZE;
		size_t count = max - sofar;
		size_t available_to_end = BUFFER_SIZE - use_offset;
		size_t available = BUFFER_SIZE - buffer_used;
		if ( available_to_end < available )
			available = available_to_end;
		if ( available < count )
			count = available;
		if ( !ctx->copy_from_src(buffer + use_offset, src + sofar, count) )
			return sofar;
		if ( !buffer_used )
			kthread_cond_signal(&not_empty);
		buffer_used += count;
		sofar += count;
		if ( sofar == max )
			return sofar;
	}
}

size_t ChannelDirection::Recv(ioctx_t* ctx, void* ptr, size_t least, size_t max)
{
	uint8_t* dst = (uint8_t*) ptr;
	size_t sofar = 0;
	CurrentThread()->yield_to_tid = sender_system_tid;
	ScopedLock inner_lock(&transfer_lock);
	receiver_system_tid = CurrentThread()->system_tid;
	while ( true )
	{
		while ( true )
		{
			if ( buffer_used )
				break;
			if ( least <= sofar )
				return sofar;
			if ( !still_writing )
				return errno = ECONNRESET, sofar;
			if ( !kthread_cond_wait_signal(&not_empty, &transfer_lock) )
				return errno = EINTR, sofar;
		}

		size_t use_offset = buffer_offset;
		size_t count = max - sofar;
		size_t available_to_end = BUFFER_SIZE - use_offset;
		size_t available = buffer_used;
		if ( available_to_end < available )
			available = available_to_end;
		if ( available < count )
			count = available;
		if ( !ctx->copy_to_dest(dst + sofar, buffer + use_offset, count) )
			return sofar;
		if ( buffer_used == BUFFER_SIZE )
			kthread_cond_signal(&not_full);
		buffer_offset = (buffer_offset + count) % BUFFER_SIZE;
		buffer_used -= count;
		sofar += count;
		if ( sofar == max )
			return sofar;
	}
}

void ChannelDirection::SendClose()
{
	ScopedLock lock(&transfer_lock);
	still_writing = false;
	kthread_cond_signal(&not_empty);
}

void ChannelDirection::RecvClose()
{
	ScopedLock lock(&transfer_lock);
	still_reading = false;
	kthread_cond_signal(&not_full);
}

//
// Implementation of Channel.
//

Channel::Channel(ioctx_t* ctx)
{
	kernel_lock = KTHREAD_MUTEX_INITIALIZER;
	user_lock = KTHREAD_MUTEX_INITIALIZER;
	destruction_lock = KTHREAD_MUTEX_INITIALIZER;
	user_closed = false;
	kernel_closed = false;
	uid = ctx ? ctx->uid : 0;
	gid = ctx ? ctx->gid : 0;
}

Channel::~Channel()
{
}

void Channel::InformSystemTids(uintptr_t client_tid, uintptr_t server_tid)
{
	from_kernel.sender_system_tid = client_tid;
	from_kernel.receiver_system_tid = server_tid;
	from_user.sender_system_tid = server_tid;
	from_user.receiver_system_tid = client_tid;
}

size_t Channel::KernelSend(ioctx_t* ctx, const void* ptr, size_t least,
                            size_t max)
{
	ScopedLockSignal outer_lock(&kernel_lock);
	if ( !outer_lock.IsAcquired() )
		return errno = EINTR, 0;
	size_t ret = from_kernel.Send(ctx, ptr, least, max);
	CurrentThread()->yield_to_tid = 0;
	Scheduler::ScheduleTrueThread();
	return ret;
}

size_t Channel::KernelRecv(ioctx_t* ctx, void* ptr, size_t least, size_t max)
{
	ScopedLockSignal outer_lock(&kernel_lock);
	if ( !outer_lock.IsAcquired() )
		return errno = EINTR, 0;
	CurrentThread()->yield_to_tid = 0;
	Scheduler::ScheduleTrueThread();
	return from_user.Recv(ctx, ptr, least, max);
}

void Channel::KernelClose()
{
	// No lock needed, this thread is the last to use this object as kernel.
	from_kernel.SendClose();
	from_user.RecvClose();
	kthread_mutex_lock(&destruction_lock);
	kernel_closed = true;
	bool delete_this = user_closed;
	kthread_mutex_unlock(&destruction_lock);
	if ( delete_this )
		delete this;
}

size_t Channel::UserSend(ioctx_t* ctx, const void* ptr, size_t least,
                         size_t max)
{
	ScopedLockSignal outer_lock(&user_lock);
	if ( !outer_lock.IsAcquired() )
		return errno = EINTR, 0;
	size_t ret = from_user.Send(ctx, ptr, least, max);
	CurrentThread()->yield_to_tid = 0;
	Scheduler::ScheduleTrueThread();
	return ret;
}

size_t Channel::UserRecv(ioctx_t* ctx, void* ptr, size_t least, size_t max)
{
	ScopedLockSignal outer_lock(&user_lock);
	if ( !outer_lock.IsAcquired() )
		return errno = EINTR, 0;
	size_t ret = from_kernel.Recv(ctx, ptr, least, max);
	CurrentThread()->yield_to_tid = 0;
	Scheduler::ScheduleTrueThread();
	return ret;
}

void Channel::UserClose()
{
	// No lock needed, this thread is the last to use this object as user.
	from_kernel.RecvClose();
	from_user.SendClose();
	kthread_mutex_lock(&destruction_lock);
	user_closed = true;
	bool delete_this = kernel_closed;
	kthread_mutex_unlock(&destruction_lock);
	if ( delete_this )
		delete this;
}

//
// Implementation of ChannelNode.
//

ChannelNode::ChannelNode()
{
	channel = NULL;
}

ChannelNode::ChannelNode(Channel* channel)
{
	Construct(channel);
}

ChannelNode::~ChannelNode()
{
	if ( channel )
		channel->UserClose();
}

void ChannelNode::Construct(Channel* channel)
{
	inode_type = INODE_TYPE_STREAM;
	this->channel = channel;
	this->type = S_IFCHR;
	this->dev = (dev_t) this;
	this->ino = 0;
	// TODO: Set uid, gid, mode.
}

ssize_t ChannelNode::read(ioctx_t* ctx, uint8_t* buf, size_t count)
{
	return channel->UserRecv(ctx, buf, /*1*/ count, count);
}

ssize_t ChannelNode::write(ioctx_t* ctx, const uint8_t* buf, size_t count)
{
	return channel->UserSend(ctx, buf, /*1*/ count, count);
}

//
// Implementation of Server.
//

Server::Server()
{
	connect_lock = KTHREAD_MUTEX_INITIALIZER;
	connecting_cond = KTHREAD_COND_INITIALIZER;
	connectable_cond = KTHREAD_COND_INITIALIZER;
	listener_system_tid = 0;
	connecting = NULL;
	disconnected = false;
	unmounted = false;
}

Server::~Server()
{
}

void Server::Disconnect()
{
	ScopedLock lock(&connect_lock);
	disconnected = true;
	kthread_cond_signal(&connectable_cond);
}

void Server::Unmount()
{
	ScopedLock lock(&connect_lock);
	unmounted = true;
	kthread_cond_signal(&connecting_cond);
}

Channel* Server::Connect(ioctx_t* ctx)
{
	Channel* channel = new Channel(ctx);
	if ( !channel )
		return NULL;
	CurrentThread()->yield_to_tid = listener_system_tid;
	ScopedLock lock(&connect_lock);
	while ( !disconnected && connecting )
	{
		if ( !kthread_cond_wait_signal(&connectable_cond, &connect_lock) )
		{
			CurrentThread()->yield_to_tid = 0;
			delete channel;
			return errno = EINTR, (Channel*) NULL;
		}
	}
	CurrentThread()->yield_to_tid = 0;
	if ( disconnected )
		return delete channel, errno = ECONNREFUSED, (Channel*) NULL;
	channel->InformSystemTids(CurrentThread()->system_tid, listener_system_tid);
	connecting = channel;
	kthread_cond_signal(&connecting_cond);
	return channel;
}

Channel* Server::Accept(ioctx_t* ctx)
{
	ScopedLock lock(&connect_lock);
	listener_system_tid = CurrentThread()->system_tid;
	while ( !connecting && !unmounted )
	{
		if ( ctx->dflags & O_NONBLOCK )
			return errno = EWOULDBLOCK, (Channel*) NULL;
		if ( !kthread_cond_wait_signal(&connecting_cond, &connect_lock) )
			return errno = EINTR, (Channel*) NULL;
	}
	if ( unmounted )
		return errno = ECONNRESET, (Channel*) NULL;
	Channel* result = connecting;
	connecting = NULL;
	kthread_cond_signal(&connectable_cond);
	return result;
}

Ref<Inode> Server::BootstrapNode(ino_t ino, mode_t type)
{
	return Ref<Inode>(new Unode(Ref<Server>(this), ino, type));
}

Ref<Inode> Server::OpenNode(ino_t ino, mode_t type)
{
	return BootstrapNode(ino, type);
}

//
// Implementation of ServerNode.
//

ServerNode::ServerNode(Ref<Server> server)
{
	inode_type = INODE_TYPE_UNKNOWN;
	this->server = server;
	this->type = S_IFSOCK;
	this->dev = (dev_t) this;
	this->ino = 0;
	// TODO: Set uid, gid, mode.
}

ServerNode::~ServerNode()
{
	server->Disconnect();
}

Ref<Inode> ServerNode::accept4(ioctx_t* ctx, uint8_t* addr, size_t* addrlen,
                               int flags)
{
	(void) addr;
	if ( flags & ~(0) )
		return errno = EINVAL, Ref<Inode>(NULL);
	size_t out_addrlen = 0;
	if ( addrlen && !ctx->copy_to_dest(addrlen, &out_addrlen, sizeof(out_addrlen)) )
		return Ref<Inode>(NULL);
	Ref<ChannelNode> node(new ChannelNode);
	if ( !node )
		return Ref<Inode>(NULL);
	Channel* channel = server->Accept(ctx);
	if ( !channel )
		return Ref<Inode>(NULL);
	node->Construct(channel);
	return node;
}

//
// Implementation of Unode.
//

Unode::Unode(Ref<Server> server, ino_t ino, mode_t type)
{
	SetupKernelIOCtx(&kctx);
	this->server = server;
	this->ino = ino;
	this->dev = (dev_t) server.Get();
	this->type = type;

	// Let the remote know that the kernel is using this inode.
	Thread* thread = CurrentThread();
	bool saved = thread->force_no_signals;
	thread->force_no_signals = true;
	thread->DoUpdatePendingSignal();
	if ( Channel* channel = server->Connect(NULL) )
	{
		struct fsm_req_refer msg;
		msg.ino = ino;
		SendMessage(channel, FSM_REQ_REFER, &msg, sizeof(msg));
		channel->KernelClose();
	}
	thread->force_no_signals = saved;
	thread->DoUpdatePendingSignal();
}

Unode::~Unode()
{
	// Let the remote know that the kernel is no longer using this inode.
	Thread* thread = CurrentThread();
	bool saved = thread->force_no_signals;
	thread->force_no_signals = true;
	thread->DoUpdatePendingSignal();
	if ( Channel* channel = server->Connect(NULL) )
	{
		struct fsm_req_unref msg;
		msg.ino = ino;
		SendMessage(channel, FSM_REQ_UNREF, &msg, sizeof(msg));
		channel->KernelClose();
	}
	thread->force_no_signals = saved;
	thread->DoUpdatePendingSignal();
}

bool Unode::SendMessage(Channel* channel, size_t type, void* ptr, size_t size,
                        size_t extra)
{
	struct fsm_msg_header hdr;
	hdr.msgtype = type;
	hdr.msgsize = size + extra;
	hdr.uid = channel->uid;
	hdr.gid = channel->gid;
	if ( !channel->KernelSend(&kctx, &hdr, sizeof(hdr)) )
		return false;
	if ( !channel->KernelSend(&kctx, ptr, size) )
		return false;
	return true;
}

bool Unode::RecvMessage(Channel* channel, size_t type, void* ptr, size_t size)
{
	struct fsm_msg_header resp_hdr;
	if ( !channel->KernelRecv(&kctx, &resp_hdr, sizeof(resp_hdr)) )
		return false;
	if ( resp_hdr.msgtype != type )
	{
		UnexpectedResponse(channel, &resp_hdr);
		return false;
	}
	return  !ptr || !size || channel->KernelRecv(&kctx, ptr, size);
}

void Unode::RecvError(Channel* channel)
{
	SetupKernelIOCtx(&kctx);
	struct fsm_resp_error resp;
	if ( channel->KernelRecv(&kctx, &resp, sizeof(resp)) )
		errno = resp.errnum;
	// In case of error, errno is set to that error.
}

bool Unode::RecvBoolean(Channel* channel)
{
	struct fsm_msg_header resp_hdr;
	if ( !channel->KernelRecv(&kctx, &resp_hdr, sizeof(resp_hdr)) )
		return false;
	if ( resp_hdr.msgtype == FSM_RESP_SUCCESS )
		return true;
	UnexpectedResponse(channel, &resp_hdr);
	return false;
}

void Unode::UnexpectedResponse(Channel* channel, struct fsm_msg_header* hdr)
{
	if ( hdr->msgtype == FSM_RESP_ERROR )
		RecvError(channel);
	else
		errno = EIO;
}

bool Unode::pass()
{
	return true;
}

void Unode::unpass()
{
}

void Unode::linked()
{
}

void Unode::unlinked()
{
}

int Unode::sync(ioctx_t* ctx)
{
	Channel* channel = server->Connect(ctx);
	if ( !channel )
		return -1;
	int ret = -1;
	struct fsm_req_sync msg;
	msg.ino = ino;
	if ( SendMessage(channel, FSM_REQ_SYNC, &msg, sizeof(msg)) &&
	     RecvMessage(channel, FSM_RESP_SUCCESS, NULL, 0) )
		ret = 0;
	channel->KernelClose();
	return ret;
}

int Unode::stat(ioctx_t* ctx, struct stat* st)
{
	// stat(2) isn't allowed to fail with EINTR.
	sigset_t set, oldset;
	sigfillset(&set);
	Signal::UpdateMask(SIG_SETMASK, &set, &oldset);
	int ret = -1;
	Channel* channel = server->Connect(ctx);
	if ( channel )
	{
		struct fsm_req_stat msg;
		struct fsm_resp_stat resp;
		msg.ino = ino;
		if ( SendMessage(channel, FSM_REQ_STAT, &msg, sizeof(msg)) &&
			 RecvMessage(channel, FSM_RESP_STAT, &resp, sizeof(resp)) &&
			 (resp.st.st_dev = (dev_t) server.Get(), true) &&
			 ctx->copy_to_dest(st, &resp.st, sizeof(*st)) )
			ret = 0;
		channel->KernelClose();
	}
	Signal::UpdateMask(SIG_SETMASK, &oldset, NULL);
	return ret;
}

int Unode::statvfs(ioctx_t* ctx, struct statvfs* stvfs)
{
	Channel* channel = server->Connect(ctx);
	if ( !channel )
		return -1;
	int ret = -1;
	struct fsm_req_statvfs msg;
	struct fsm_resp_statvfs resp;
	msg.ino = ino;
	if ( SendMessage(channel, FSM_REQ_STATVFS, &msg, sizeof(msg)) &&
	     RecvMessage(channel, FSM_RESP_STATVFS, &resp, sizeof(resp)) &&
	     (resp.stvfs.f_fsid = (dev_t) server.Get(), true) &&
	     (resp.stvfs.f_flag |= ST_NOSUID, true) &&
	     ctx->copy_to_dest(stvfs, &resp.stvfs, sizeof(*stvfs)) )
		ret = 0;
	channel->KernelClose();
	return ret;
}

int Unode::chmod(ioctx_t* ctx, mode_t mode)
{
	Channel* channel = server->Connect(ctx);
	if ( !channel )
		return -1;
	int ret = -1;
	struct fsm_req_chmod msg;
	msg.ino = ino;
	msg.mode = mode;
	if ( SendMessage(channel, FSM_REQ_CHMOD, &msg, sizeof(msg)) &&
	     RecvMessage(channel, FSM_RESP_SUCCESS, NULL, 0) )
		ret = 0;
	channel->KernelClose();
	return ret;
}

int Unode::chown(ioctx_t* ctx, uid_t owner, gid_t group)
{
	Channel* channel = server->Connect(ctx);
	if ( !channel )
		return -1;
	int ret = -1;
	struct fsm_req_chown msg;
	msg.ino = ino;
	msg.uid = owner;
	msg.gid = group;
	if ( SendMessage(channel, FSM_REQ_CHOWN, &msg, sizeof(msg)) &&
	     RecvMessage(channel, FSM_RESP_SUCCESS, NULL, 0) )
		ret = 0;
	channel->KernelClose();
	return ret;
}

int Unode::truncate(ioctx_t* ctx, off_t length)
{
	// truncate(2) is allowed to EINTR but may be used by open(2) O_TRUNC which
	// should not fail with EINTR.
	sigset_t set, oldset;
	sigfillset(&set);
	Signal::UpdateMask(SIG_SETMASK, &set, &oldset);
	int ret = -1;
	Channel* channel = server->Connect(ctx);
	if ( channel )
	{
		struct fsm_req_truncate msg;
		msg.ino = ino;
		msg.size = length;
		if ( SendMessage(channel, FSM_REQ_TRUNCATE, &msg, sizeof(msg)) &&
			 RecvMessage(channel, FSM_RESP_SUCCESS, NULL, 0) )
			ret = 0;
		channel->KernelClose();
	}
	Signal::UpdateMask(SIG_SETMASK, &oldset, NULL);
	return ret;
}

off_t Unode::lseek(ioctx_t* ctx, off_t offset, int whence)
{
	if ( whence != SEEK_END && offset != 0 )
		return errno = EINVAL, -1;
	Channel* channel = server->Connect(ctx);
	if ( !channel )
		return -1;
	off_t ret = -1;
	struct fsm_req_lseek msg;
	struct fsm_resp_lseek resp;
	msg.ino = ino;
	msg.offset = offset;
	msg.whence = whence;
	if ( SendMessage(channel, FSM_REQ_LSEEK, &msg, sizeof(msg)) &&
	     RecvMessage(channel, FSM_RESP_LSEEK, &resp, sizeof(resp)) )
		ret = resp.offset;
	channel->KernelClose();
	return ret;
}

ssize_t Unode::read(ioctx_t* ctx, uint8_t* buf, size_t count)
{
	Channel* channel = server->Connect(ctx);
	if ( !channel )
		return -1;
	ssize_t ret = -1;
	struct fsm_req_read msg;
	struct fsm_resp_read resp;
	msg.ino = ino;
	msg.count = count;
	if ( SendMessage(channel, FSM_REQ_READ, &msg, sizeof(msg)) &&
	     RecvMessage(channel, FSM_RESP_READ, &resp, sizeof(resp)) )
	{
		if ( resp.count < count )
			count = resp.count;
		if ( channel->KernelRecv(ctx, buf, count) )
			ret = (ssize_t) count;
	}
	channel->KernelClose();
	return ret;
}

ssize_t Unode::readv(ioctx_t* ctx, const struct iovec* iov, int iovcnt)
{
	ssize_t sofar = 0;
	for ( int i = 0; i < iovcnt && sofar < SSIZE_MAX; i++ )
	{
		size_t maxcount = SSIZE_MAX - sofar;
		uint8_t* buf = (uint8_t*) iov[i].iov_base;
		size_t count = iov[i].iov_len;
		if ( maxcount < count )
			count = maxcount;
		int old_dflags = ctx->dflags;
		if ( sofar )
			ctx->dflags |= O_NONBLOCK;
		ssize_t amount = read(ctx, buf, count);
		ctx->dflags = old_dflags;
		if ( amount < 0 )
			return sofar ? sofar : -1;
		if ( amount == 0 )
			break;
		sofar += amount;
		if ( (size_t) amount < count )
			break;
	}
	return sofar;
}

ssize_t Unode::pread(ioctx_t* ctx, uint8_t* buf, size_t count, off_t off)
{
	Channel* channel = server->Connect(ctx);
	if ( !channel )
		return -1;
	ssize_t ret = -1;
	struct fsm_req_pread msg;
	struct fsm_resp_read resp;
	msg.ino = ino;
	msg.count = count;
	msg.offset = off;
	if ( SendMessage(channel, FSM_REQ_PREAD, &msg, sizeof(msg)) &&
	     RecvMessage(channel, FSM_RESP_READ, &resp, sizeof(resp)) )
	{
		if ( resp.count < count )
			count = resp.count;
		if ( channel->KernelRecv(ctx, buf, count) )
			ret = (ssize_t) count;
	}
	channel->KernelClose();
	return ret;
}

ssize_t Unode::preadv(ioctx_t* ctx, const struct iovec* iov, int iovcnt,
	                  off_t off)
{
	ssize_t sofar = 0;
	for ( int i = 0; i < iovcnt && sofar < SSIZE_MAX; i++ )
	{
		size_t maxcount = SSIZE_MAX - sofar;
		uint8_t* buf = (uint8_t*) iov[i].iov_base;
		size_t count = iov[i].iov_len;
		if ( maxcount < count )
			count = maxcount;
		off_t offset;
		if ( __builtin_add_overflow(off, sofar, &offset) )
			return sofar ? sofar : (errno = EOVERFLOW, -1);
		int old_dflags = ctx->dflags;
		if ( sofar )
			ctx->dflags |= O_NONBLOCK;
		ssize_t amount = pread(ctx, buf, count, offset);
		ctx->dflags = old_dflags;
		if ( amount < 0 )
			return sofar ? sofar : -1;
		if ( amount == 0 )
			break;
		sofar += amount;
		if ( (size_t) amount < count )
			break;
	}
	return sofar;
}

ssize_t Unode::write(ioctx_t* ctx, const uint8_t* buf, size_t count)
{
	Channel* channel = server->Connect(ctx);
	if ( !channel )
		return -1;
	int ret = -1;
	struct fsm_req_write msg;
	msg.ino = ino;
	msg.count = count;
	struct fsm_msg_header hdr;
	hdr.msgtype = FSM_REQ_WRITE;
	hdr.msgsize = sizeof(msg) + count;
	hdr.uid = ctx->uid;
	hdr.gid = ctx->gid;
	struct fsm_resp_write resp;
	if ( channel->KernelSend(&kctx, &hdr, sizeof(hdr)) &&
	     channel->KernelSend(&kctx, &msg, sizeof(msg)) &&
	     channel->KernelSend(ctx, buf, count) &&
	     RecvMessage(channel, FSM_RESP_WRITE, &resp, sizeof(resp)) )
		ret = (ssize_t) resp.count;
	channel->KernelClose();
	return ret;
}

ssize_t Unode::writev(ioctx_t* ctx, const struct iovec* iov, int iovcnt)
{
	ssize_t sofar = 0;
	for ( int i = 0; i < iovcnt && sofar < SSIZE_MAX; i++ )
	{
		size_t maxcount = SSIZE_MAX - sofar;
		const uint8_t* buf = (uint8_t*) iov[i].iov_base;
		size_t count = iov[i].iov_len;
		if ( maxcount < count )
			count = maxcount;
		int old_dflags = ctx->dflags;
		if ( sofar )
			ctx->dflags |= O_NONBLOCK;
		ssize_t amount = write(ctx, buf, count);
		ctx->dflags = old_dflags;
		if ( amount < 0 )
			return sofar ? sofar : -1;
		if ( amount == 0 )
			break;
		sofar += amount;
		if ( (size_t) amount < count )
			break;
	}
	return sofar;
}

ssize_t Unode::pwrite(ioctx_t* ctx, const uint8_t* buf, size_t count, off_t off)
{
	Channel* channel = server->Connect(ctx);
	if ( !channel )
		return -1;
	ssize_t ret = -1;
	struct fsm_req_pwrite msg;
	msg.ino = ino;
	msg.count = count;
	msg.offset = off;
	struct fsm_msg_header hdr;
	hdr.msgtype = FSM_REQ_PWRITE;
	hdr.msgsize = sizeof(msg) + count;
	hdr.uid = ctx->uid;
	hdr.gid = ctx->gid;
	struct fsm_resp_write resp;
	if ( channel->KernelSend(&kctx, &hdr, sizeof(hdr)) &&
	     channel->KernelSend(&kctx, &msg, sizeof(msg)) &&
	     channel->KernelSend(ctx, buf, count) &&
	     RecvMessage(channel, FSM_RESP_WRITE, &resp, sizeof(resp)) )
		ret = (ssize_t) resp.count;
	channel->KernelClose();
	return ret;
}

ssize_t Unode::pwritev(ioctx_t* ctx, const struct iovec* iov, int iovcnt,
                       off_t off)
{
	ssize_t sofar = 0;
	for ( int i = 0; i < iovcnt && sofar < SSIZE_MAX; i++ )
	{
		size_t maxcount = SSIZE_MAX - sofar;
		uint8_t* buf = (uint8_t*) iov[i].iov_base;
		size_t count = iov[i].iov_len;
		if ( maxcount < count )
			count = maxcount;
		off_t offset;
		if ( __builtin_add_overflow(off, sofar, &offset) )
			return sofar ? sofar : (errno = EOVERFLOW, -1);
		int old_dflags = ctx->dflags;
		if ( sofar )
			ctx->dflags |= O_NONBLOCK;
		ssize_t amount = pwrite(ctx, buf, count, offset);
		ctx->dflags = old_dflags;
		if ( amount < 0 )
			return sofar ? sofar : -1;
		if ( amount == 0 )
			break;
		sofar += amount;
		if ( (size_t) amount < count )
			break;
	}
	return sofar;
}

int Unode::utimens(ioctx_t* ctx, const struct timespec* times)
{
	Channel* channel = server->Connect(ctx);
	if ( !channel )
		return -1;
	int ret = -1;
	struct fsm_req_utimens msg;
	msg.ino = ino;
	msg.times[0] = times[0];
	msg.times[1] = times[1];
	if ( SendMessage(channel, FSM_REQ_UTIMENS, &msg, sizeof(msg)) &&
	     RecvMessage(channel, FSM_RESP_SUCCESS, NULL, 0) )
		ret = 0;
	channel->KernelClose();
	return ret;
}

int Unode::isatty(ioctx_t* ctx)
{
	Channel* channel = server->Connect(ctx);
	if ( !channel )
		return 0;
	int ret = 0;
	struct fsm_req_isatty msg;
	msg.ino = ino;
	if ( SendMessage(channel, FSM_REQ_ISATTY, &msg, sizeof(msg)) &&
	     RecvMessage(channel, FSM_RESP_SUCCESS, NULL, 0) )
		ret = 1;
	channel->KernelClose();
	return ret;
}

ssize_t Unode::readdirents(ioctx_t* ctx, struct dirent* dirent, size_t size,
                           off_t start)
{
	Channel* channel = server->Connect(ctx);
	if ( !channel )
		return -1;
	ssize_t ret = -1;
	struct fsm_req_readdirents msg;
	struct fsm_resp_readdirents resp;
	msg.ino = ino;
	msg.rec_num = start;
	errno = 0;
	if ( SendMessage(channel, FSM_REQ_READDIRENTS, &msg, sizeof(msg)) &&
	     RecvMessage(channel, FSM_RESP_READDIRENTS, &resp, sizeof(resp)) )
	{
		if ( !resp.namelen )
		{
			ret = 0;
			goto break_if;
		}

		struct dirent entry;
		memset(&entry, 0, sizeof(entry));
		entry.d_reclen = sizeof(entry) + resp.namelen + 1;
		entry.d_namlen = resp.namelen;
		entry.d_dev = (dev_t) server.Get();
		entry.d_ino = resp.ino;
		entry.d_type = resp.type;

		if ( !ctx->copy_to_dest(dirent, &entry, sizeof(entry)) )
			goto break_if;

		if ( size < entry.d_reclen && (errno = ERANGE) )
			goto break_if;

		char nul = '\0';
		if ( channel->KernelRecv(ctx, dirent->d_name, resp.namelen) &&
		     ctx->copy_to_dest(&dirent->d_name[resp.namelen], &nul, 1) )
			ret = (ssize_t) entry.d_reclen;
	} break_if:
	channel->KernelClose();
	return ret;
}

Ref<Inode> Unode::open(ioctx_t* ctx, const char* filename, int flags,
                       mode_t mode)
{
	// open(2) may EINTR on slow devices but is used by stat(2) and readlink(2)
	// wnich aren't allowed to EINTR.
	sigset_t set, oldset;
	sigfillset(&set);
	Signal::UpdateMask(SIG_SETMASK, &set, &oldset);
	Ref<Inode> ret;
	Channel* channel = server->Connect(ctx);
	if ( channel )
	{
		size_t filenamelen = strlen(filename);
		struct fsm_req_open msg;
		msg.dirino = ino;
		msg.flags = flags;
		msg.mode = mode;
		msg.namelen = filenamelen;
		struct fsm_resp_open resp;
		if ( SendMessage(channel, FSM_REQ_OPEN, &msg, sizeof(msg), filenamelen) &&
			 channel->KernelSend(&kctx, filename, filenamelen) &&
			 RecvMessage(channel, FSM_RESP_OPEN, &resp, sizeof(resp)) )
			ret = server->OpenNode(resp.ino, resp.type);
		channel->KernelClose();
	}
	Signal::UpdateMask(SIG_SETMASK, &oldset, NULL);
	return ret;
}

Ref<Inode> Unode::factory(ioctx_t* /*ctx*/, const char* /*filename*/,
                          int /*flags*/, mode_t /*mode*/)
{
	return errno = EBADF, Ref<Inode>(NULL);
}

int Unode::mkdir(ioctx_t* ctx, const char* filename, mode_t mode)
{
	Channel* channel = server->Connect(ctx);
	if ( !channel )
		return -1;
	size_t filenamelen = strlen(filename);
	int ret = -1;
	struct fsm_req_mkdir msg;
	msg.dirino = ino;
	msg.mode = mode;
	msg.namelen = filenamelen;
	struct fsm_resp_mkdir resp;
	if ( SendMessage(channel, FSM_REQ_MKDIR, &msg, sizeof(msg), filenamelen) &&
	     channel->KernelSend(&kctx, filename, filenamelen) &&
	     RecvMessage(channel, FSM_RESP_MKDIR, &resp, sizeof(resp)) )
		ret = 0;
	channel->KernelClose();
	return ret;
}

int Unode::link(ioctx_t* ctx, const char* filename, Ref<Inode> node)
{
	if ( node->dev != this->dev )
		return errno = EXDEV, -1;
	Channel* channel = server->Connect(ctx);
	if ( !channel )
		return -1;
	size_t filenamelen = strlen(filename);
	int ret = -1;
	struct fsm_req_link msg;
	msg.dirino = ino;
	msg.linkino = node->ino;
	msg.namelen = filenamelen;
	if ( SendMessage(channel, FSM_REQ_LINK, &msg, sizeof(msg), filenamelen) &&
	     channel->KernelSend(&kctx, filename, filenamelen) &&
	     RecvMessage(channel, FSM_RESP_SUCCESS, NULL, 0) )
		ret = 0;
	channel->KernelClose();
	return ret;
}

int Unode::link_raw(ioctx_t* /*ctx*/, const char* /*filename*/, Ref<Inode> /*node*/)
{
	return errno = EPERM, -1;
}

int Unode::unlink(ioctx_t* ctx, const char* filename)
{
	// TODO: Make sure the target is no longer used!
	Channel* channel = server->Connect(ctx);
	if ( !channel )
		return -1;
	size_t filenamelen = strlen(filename);
	int ret = -1;
	struct fsm_req_unlink msg;
	msg.dirino = ino;
	msg.namelen = filenamelen;
	if ( SendMessage(channel, FSM_REQ_UNLINK, &msg, sizeof(msg), filenamelen) &&
	     channel->KernelSend(&kctx, filename, filenamelen) &&
	     RecvMessage(channel, FSM_RESP_SUCCESS, NULL, 0) )
		ret = 0;
	channel->KernelClose();
	return ret;
}

int Unode::unlink_raw(ioctx_t* /*ctx*/, const char* /*filename*/)
{
	return errno = EPERM, -1;
}

int Unode::rmdir(ioctx_t* ctx, const char* filename)
{
	// TODO: Make sure the target is no longer used!
	Channel* channel = server->Connect(ctx);
	if ( !channel )
		return -1;
	size_t filenamelen = strlen(filename);
	int ret = -1;
	struct fsm_req_rmdir msg;
	msg.dirino = ino;
	msg.namelen = filenamelen;
	if ( SendMessage(channel, FSM_REQ_RMDIR, &msg, sizeof(msg), filenamelen) &&
	     channel->KernelSend(&kctx, filename, filenamelen) &&
	     RecvMessage(channel, FSM_RESP_SUCCESS, NULL, 0) )
		ret = 0;
	channel->KernelClose();
	return ret;
}

int Unode::rmdir_me(ioctx_t* /*ctx*/)
{
	return errno = EPERM, -1;
}

int Unode::symlink(ioctx_t* ctx, const char* oldname, const char* filename)
{
	Channel* channel = server->Connect(ctx);
	if ( !channel )
		return -1;
	size_t oldnamelen = strlen(oldname);
	size_t filenamelen = strlen(filename);
	int ret = -1;
	struct fsm_req_symlink msg;
	msg.dirino = ino;
	msg.targetlen = oldnamelen;
	msg.namelen = filenamelen;
	size_t extra = msg.targetlen + msg.namelen;
	if ( SendMessage(channel, FSM_REQ_SYMLINK, &msg, sizeof(msg), extra) &&
	     channel->KernelSend(&kctx, oldname, oldnamelen) &&
	     channel->KernelSend(&kctx, filename, filenamelen) &&
	     RecvMessage(channel, FSM_RESP_SUCCESS, NULL, 0) )
		ret = 0;
	channel->KernelClose();
	return ret;
}

ssize_t Unode::readlink(ioctx_t* ctx, char* buf, size_t bufsiz)
{
	// readlink(2) isn't allowed to fail with EINTR.
	sigset_t set, oldset;
	sigfillset(&set);
	Signal::UpdateMask(SIG_SETMASK, &set, &oldset);
	ssize_t ret = -1;
	Channel* channel = server->Connect(ctx);
	if ( channel )
	{
		struct fsm_req_readlink msg;
		struct fsm_resp_readlink resp;
		msg.ino = ino;
		if ( SendMessage(channel, FSM_REQ_READLINK, &msg, sizeof(msg)) &&
			 RecvMessage(channel, FSM_RESP_READLINK, &resp, sizeof(resp)) )
		{
			if ( resp.targetlen < bufsiz )
				bufsiz = resp.targetlen;
			if ( channel->KernelRecv(ctx, buf, bufsiz) )
				ret = (ssize_t) bufsiz;
		}
		channel->KernelClose();
	}
	Signal::UpdateMask(SIG_SETMASK, &oldset, NULL);
	return ret;
}

int Unode::tcgetwincurpos(ioctx_t* ctx, struct wincurpos* wcp)
{
	Channel* channel = server->Connect(ctx);
	if ( !channel )
		return -1;
	int ret = -1;
	struct fsm_req_tcgetwincurpos msg;
	struct fsm_resp_tcgetwincurpos resp;
	msg.ino = ino;
	if ( SendMessage(channel, FSM_REQ_TCGETWINCURPOS, &msg, sizeof(msg)) &&
	     RecvMessage(channel, FSM_RESP_TCGETWINCURPOS, &resp, sizeof(resp)) &&
	     ctx->copy_to_dest(wcp, &resp.pos, sizeof(*wcp)) )
		ret = 0;
	channel->KernelClose();
	return ret;
}

int Unode::ioctl(ioctx_t* ctx, int cmd, uintptr_t arg)
{
	if ( cmd == TIOCGWINSZ )
	{
		struct winsize* ws = (struct winsize*) arg;
		Channel* channel = server->Connect(ctx);
		if ( !channel )
			return -1;
		int ret = -1;
		struct fsm_req_tcgetwinsize msg;
		struct fsm_resp_tcgetwinsize resp;
		msg.ino = ino;
		if ( SendMessage(channel, FSM_REQ_TCGETWINSIZE, &msg, sizeof(msg)) &&
		     RecvMessage(channel, FSM_RESP_TCGETWINSIZE, &resp, sizeof(resp)) &&
		     ctx->copy_to_dest(ws, &resp.size, sizeof(*ws)) )
			ret = 0;
		channel->KernelClose();
		return ret;
	}
	return errno = ENOTTY, -1;
}
int Unode::tcsetpgrp(ioctx_t* ctx, pid_t pgid)
{
	Channel* channel = server->Connect(ctx);
	if ( !channel )
		return -1;
	int ret = -1;
	struct fsm_req_tcsetpgrp msg;
	msg.ino = ino;
	msg.pgid = pgid;
	if ( SendMessage(channel, FSM_REQ_TCSETPGRP, &msg, sizeof(msg)) &&
	     RecvMessage(channel, FSM_RESP_SUCCESS, NULL, 0) )
		ret = 0;
	channel->KernelClose();
	return ret;
}

pid_t Unode::tcgetpgrp(ioctx_t* ctx)
{
	Channel* channel = server->Connect(ctx);
	if ( !channel )
		return -1;
	pid_t ret = -1;
	struct fsm_req_tcgetpgrp msg;
	struct fsm_resp_tcgetpgrp resp;
	msg.ino = ino;
	if ( SendMessage(channel, FSM_REQ_TCGETPGRP, &msg, sizeof(msg)) &&
	     RecvMessage(channel, FSM_RESP_TCGETPGRP, &resp, sizeof(resp)) )
		ret = resp.pgid;
	channel->KernelClose();
	return ret;
}

int Unode::settermmode(ioctx_t* ctx, unsigned mode)
{
	Channel* channel = server->Connect(ctx);
	if ( !channel )
		return -1;
	int ret = -1;
	struct fsm_req_settermmode msg;
	msg.ino = ino;
	msg.termmode = mode;
	if ( SendMessage(channel, FSM_REQ_SETTERMMODE, &msg, sizeof(msg)) &&
	     RecvMessage(channel, FSM_RESP_SUCCESS, NULL, 0) )
		ret = 0;
	channel->KernelClose();
	return ret;
}

int Unode::gettermmode(ioctx_t* ctx, unsigned* mode)
{
	Channel* channel = server->Connect(ctx);
	if ( !channel )
		return -1;
	int ret = -1;
	struct fsm_req_gettermmode msg;
	struct fsm_resp_gettermmode resp;
	msg.ino = ino;
	if ( SendMessage(channel, FSM_REQ_GETTERMMODE, &msg, sizeof(msg)) &&
	     RecvMessage(channel, FSM_RESP_GETTERMMODE, &resp, sizeof(resp)) &&
	     ctx->copy_to_dest(mode, &resp.termmode, sizeof(*mode)) )
		ret = 0;
	channel->KernelClose();
	return ret;
}

int Unode::poll(ioctx_t* /*ctx*/, PollNode* node)
{
	short status = POLLIN | POLLOUT | POLLRDNORM | POLLWRNORM;
	if ( !(status & node->events) )
		return errno = EAGAIN, -1;
	node->master->revents |= status & node->events;
	return 0;
}

int Unode::rename_here(ioctx_t* ctx, Ref<Inode> from, const char* oldname,
                       const char* newname)
{
	Channel* channel = server->Connect(ctx);
	if ( !channel )
		return -1;
	int ret = -1;
	struct fsm_req_rename msg;
	msg.olddirino = from->ino;
	msg.newdirino = this->ino;
	msg.oldnamelen = strlen(oldname);
	msg.newnamelen = strlen(newname);
	size_t extra = msg.oldnamelen + msg.newnamelen;
	if ( SendMessage(channel, FSM_REQ_RENAME, &msg, sizeof(msg), extra) &&
	     channel->KernelSend(&kctx, oldname, msg.oldnamelen) &&
	     channel->KernelSend(&kctx, newname, msg.newnamelen) &&
	     RecvMessage(channel, FSM_RESP_SUCCESS, NULL, 0) )
		ret = 0;
	channel->KernelClose();
	return ret;
}

Ref<Inode> Unode::accept4(ioctx_t* /*ctx*/, uint8_t* /*addr*/,
                          size_t* /*addrlen*/, int /*flags*/)
{
	return errno = ENOTSOCK, Ref<Inode>();
}

int Unode::bind(ioctx_t* /*ctx*/, const uint8_t* /*addr*/, size_t /*addrlen*/)
{
	return errno = ENOTSOCK, -1;
}

int Unode::connect(ioctx_t* /*ctx*/, const uint8_t* /*addr*/, size_t /*addrlen*/)
{
	return errno = ENOTSOCK, -1;
}

int Unode::listen(ioctx_t* /*ctx*/, int /*backlog*/)
{
	return errno = ENOTSOCK, -1;
}

ssize_t Unode::recv(ioctx_t* /*ctx*/, uint8_t* /*buf*/, size_t /*count*/,
                    int /*flags*/)
{
	return errno = ENOTSOCK, -1;
}

ssize_t Unode::recvmsg(ioctx_t* /*ctx*/, struct msghdr* /*msg*/, int /*flags*/)
{
	return errno = ENOTSOCK, -1;
}

ssize_t Unode::send(ioctx_t* /*ctx*/, const uint8_t* /*buf*/, size_t /*count*/,
                    int /*flags*/)
{
	return errno = ENOTSOCK, -1;
}

ssize_t Unode::sendmsg(ioctx_t* /*ctx*/, const struct msghdr* /*msg*/,
                       int /*flags*/)
{
	return errno = ENOTSOCK, -1;
}

int Unode::getsockopt(ioctx_t* ctx, int level, int option_name,
                      void* option_value, size_t* option_size_ptr)
{
	size_t option_size;
	if ( !ctx->copy_from_src(&option_size, option_size_ptr, sizeof(option_size)) )
		return -1;
	Channel* channel = server->Connect(ctx);
	if ( !channel )
		return -1;
	int ret = -1;
	struct fsm_req_getsockopt msg;
	struct fsm_resp_getsockopt resp;
	msg.ino = ino;
	msg.level = level;
	msg.option_name = option_name;
	msg.max_option_size = option_size;
	if ( SendMessage(channel, FSM_REQ_GETSOCKOPT, &msg, sizeof(msg)) &&
	     RecvMessage(channel, FSM_RESP_GETSOCKOPT, &resp, sizeof(resp)) )
	{
		if ( resp.option_size < option_size )
			option_size = resp.option_size;
		if ( channel->KernelRecv(ctx, option_value, option_size) )
			ret = 0;
		if ( !ctx->copy_to_dest(option_size_ptr, &option_size, sizeof(option_size)) )
			ret = -1;
	}
	channel->KernelClose();
	return ret;
}

int Unode::setsockopt(ioctx_t* ctx, int level, int option_name,
                      const void* option_value, size_t option_size)
{
	Channel* channel = server->Connect(ctx);
	if ( !channel )
		return -1;
	int ret = -1;
	struct fsm_req_setsockopt msg;
	msg.ino = ino;
	msg.level = level;
	msg.option_name = option_name;
	msg.option_size = option_size;
	if ( SendMessage(channel, FSM_REQ_SETSOCKOPT, &msg, sizeof(msg)) &&
	     channel->KernelSend(ctx, option_value, option_size) &&
	     RecvMessage(channel, FSM_RESP_SUCCESS, NULL, 0) )
		ret = 0;
	channel->KernelClose();
	return ret;
}

ssize_t Unode::tcgetblob(ioctx_t* ctx, const char* name, void* buffer, size_t count)
{
	Channel* channel = server->Connect(ctx);
	if ( !channel )
		return -1;
	if ( !buffer )
		count = SSIZE_MAX;
	ssize_t ret = -1;
	size_t namelen = name ? strlen(name) : 0;
	struct fsm_req_tcgetblob msg;
	struct fsm_resp_tcgetblob resp;
	msg.ino = ino;
	msg.namelen = namelen;
	if ( SendMessage(channel, FSM_REQ_TCGETBLOB, &msg, sizeof(msg), namelen) &&
	     channel->KernelSend(&kctx, name, namelen) &&
	     RecvMessage(channel, FSM_RESP_TCGETBLOB, &resp, sizeof(resp)) )
	{
		if ( resp.count < count )
			count = resp.count;
		if ( count < resp.count )
			errno = ERANGE;
		else if ( !buffer || channel->KernelRecv(ctx, buffer, count) )
			ret = (ssize_t) count;
	}
	channel->KernelClose();
	return ret;
}

ssize_t Unode::tcsetblob(ioctx_t* ctx, const char* name, const void* buffer, size_t count)
{
	Channel* channel = server->Connect(ctx);
	if ( !channel )
		return -1;
	ssize_t ret = -1;
	size_t namelen = name ? strlen(name) : 0;
	if ( SIZE_MAX - count < namelen )
		return errno = EOVERFLOW, -1;
	struct fsm_req_tcsetblob msg;
	struct fsm_resp_tcsetblob resp;
	msg.ino = ino;
	msg.namelen = namelen;
	msg.count = count;
	if ( SendMessage(channel, FSM_REQ_TCSETBLOB, &msg, sizeof(msg), namelen + count) &&
	     channel->KernelSend(&kctx, name, namelen) &&
	     channel->KernelSend(ctx, buffer, count) &&
	     RecvMessage(channel, FSM_RESP_TCSETBLOB, &resp, sizeof(resp)) )
		ret = (ssize_t) resp.count;
	channel->KernelClose();
	return ret;
}

int Unode::unmounted(ioctx_t* /*ctx*/)
{
	server->Unmount();
	return 0;
}

int Unode::tcdrain(ioctx_t* ctx)
{
	Channel* channel = server->Connect(ctx);
	if ( !channel )
		return -1;
	int ret = -1;
	struct fsm_req_tcdrain msg;
	msg.ino = ino;
	if ( SendMessage(channel, FSM_REQ_TCDRAIN, &msg, sizeof(msg)) &&
	     RecvMessage(channel, FSM_RESP_SUCCESS, NULL, 0) )
		ret = 0;
	channel->KernelClose();
	return ret;
}

int Unode::tcflow(ioctx_t* ctx, int action)
{
	Channel* channel = server->Connect(ctx);
	if ( !channel )
		return -1;
	int ret = -1;
	struct fsm_req_tcflow msg;
	msg.ino = ino;
	msg.action = action;
	if ( SendMessage(channel, FSM_REQ_TCFLOW, &msg, sizeof(msg)) &&
	     RecvMessage(channel, FSM_RESP_SUCCESS, NULL, 0) )
		ret = 0;
	channel->KernelClose();
	return ret;
}

int Unode::tcflush(ioctx_t* ctx, int queue_selector)
{
	Channel* channel = server->Connect(ctx);
	if ( !channel )
		return -1;
	int ret = -1;
	struct fsm_req_tcflush msg;
	msg.ino = ino;
	msg.queue_selector = queue_selector;
	if ( SendMessage(channel, FSM_REQ_TCFLUSH, &msg, sizeof(msg)) &&
	     RecvMessage(channel, FSM_RESP_SUCCESS, NULL, 0) )
		ret = 0;
	channel->KernelClose();
	return ret;
}

int Unode::tcgetattr(ioctx_t* ctx, struct termios* io_tio)
{
	Channel* channel = server->Connect(ctx);
	if ( !channel )
		return -1;
	int ret = -1;
	struct fsm_req_tcgetattr msg;
	struct fsm_resp_tcgetattr resp;
	msg.ino = ino;
	if ( SendMessage(channel, FSM_REQ_TCGETATTR, &msg, sizeof(msg)) &&
	     RecvMessage(channel, FSM_RESP_TCGETATTR, &resp, sizeof(resp)) &&
	     ctx->copy_to_dest(io_tio, &resp.tio, sizeof(resp.tio)) )
		ret = 0;
	channel->KernelClose();
	return ret;
}

pid_t Unode::tcgetsid(ioctx_t* ctx)
{
	Channel* channel = server->Connect(ctx);
	if ( !channel )
		return -1;
	pid_t ret = -1;
	struct fsm_req_tcgetsid msg;
	struct fsm_resp_tcgetsid resp;
	msg.ino = ino;
	if ( SendMessage(channel, FSM_REQ_TCGETSID, &msg, sizeof(msg)) &&
	     RecvMessage(channel, FSM_RESP_TCGETSID, &resp, sizeof(resp)) )
		ret = resp.sid;
	channel->KernelClose();
	return ret;
}

int Unode::tcsendbreak(ioctx_t* ctx, int duration)
{
	Channel* channel = server->Connect(ctx);
	if ( !channel )
		return -1;
	int ret = -1;
	struct fsm_req_tcsendbreak msg;
	msg.ino = ino;
	msg.duration = duration;
	if ( SendMessage(channel, FSM_REQ_TCSENDBREAK, &msg, sizeof(msg)) &&
	     RecvMessage(channel, FSM_RESP_SUCCESS, NULL, 0) )
		ret = 0;
	channel->KernelClose();
	return ret;
}

int Unode::tcsetattr(ioctx_t* ctx, int actions, const struct termios* user_tio)
{
	struct fsm_req_tcsetattr msg;
	if ( !ctx->copy_from_src(&msg.tio, user_tio, sizeof(msg.tio)) )
		return -1;
	Channel* channel = server->Connect(ctx);
	if ( !channel )
		return -1;
	int ret = -1;
	msg.ino = ino;
	msg.actions = actions;
	if ( SendMessage(channel, FSM_REQ_TCSETATTR, &msg, sizeof(msg)) &&
	     RecvMessage(channel, FSM_RESP_SUCCESS, NULL, 0) )
		ret = 0;
	channel->KernelClose();
	return ret;
}

int Unode::shutdown(ioctx_t* ctx, int how)
{
	struct fsm_req_shutdown msg;
	Channel* channel = server->Connect(ctx);
	if ( !channel )
		return -1;
	int ret = -1;
	msg.ino = ino;
	msg.how = how;
	if ( SendMessage(channel, FSM_REQ_SHUTDOWN, &msg, sizeof(msg)) &&
	     RecvMessage(channel, FSM_RESP_SUCCESS, NULL, 0) )
		ret = 0;
	channel->KernelClose();
	return ret;
}

int Unode::getpeername(ioctx_t* /*ctx*/, uint8_t* /*addr*/,
                       size_t* /*addrsize*/)
{
	return errno = ENOTSOCK, -1;
}

int Unode::getsockname(ioctx_t* /*ctx*/, uint8_t* /*addr*/,
                       size_t* /*addrsize*/)
{
	return errno = ENOTSOCK, -1;
}

bool Bootstrap(Ref<Inode>* out_root,
               Ref<Inode>* out_server,
               const struct stat* rootst)
{
	Ref<Server> server(new Server());
	if ( !server )
		return false;

	ino_t root_inode_ino = rootst->st_ino;
	mode_t root_inode_type = rootst->st_mode & S_IFMT;
	Ref<Inode> root_inode = server->OpenNode(root_inode_ino, root_inode_type);
	if ( !root_inode )
		return false;

	Ref<ServerNode> server_inode(new ServerNode(server));
	if ( !server_inode )
		return false;

	return *out_root = root_inode, *out_server = server_inode, true;
}

} // namespace UserFS
} // namespace Sortix
