/*
 *  linux/fs/pipe.c
 *
 *  Copyright (C) 1991, 1992  Linus Torvalds
 */

#include <asm/segment.h>

#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/signal.h>
#include <linux/fcntl.h>
#include <linux/termios.h>

static int pipe_read(struct inode * inode, struct file * filp, char * buf, int count)
{
	int chars, size, read = 0;

	if (!(filp->f_flags & O_NONBLOCK))
		while (!PIPE_SIZE(*inode)) {
			wake_up(& PIPE_WRITE_WAIT(*inode));
			if (!PIPE_WRITERS(*inode)) /* are there any writers? */
				return 0;
			if (current->signal & ~current->blocked)
				return -ERESTARTSYS;
			interruptible_sleep_on(& PIPE_READ_WAIT(*inode));
		}
	while (count>0 && (size = PIPE_SIZE(*inode))) {
		chars = PAGE_SIZE-PIPE_TAIL(*inode);
		if (chars > count)
			chars = count;
		if (chars > size)
			chars = size;
		memcpy_tofs(buf, PIPE_BASE(*inode)+PIPE_TAIL(*inode), chars );
		read += chars;
		PIPE_TAIL(*inode) += chars;
		PIPE_TAIL(*inode) &= (PAGE_SIZE-1);
		count -= chars;
		buf += chars;
	}
	wake_up(& PIPE_WRITE_WAIT(*inode));
	if (read)
		return read;
	if (PIPE_WRITERS(*inode))
		return -EAGAIN;
	return 0;
}
	
static int pipe_write(struct inode * inode, struct file * filp, char * buf, int count)
{
	int chars, size, written = 0;

	if (!PIPE_READERS(*inode)) { /* no readers */
		send_sig(SIGPIPE,current,0);
		return -EPIPE;
	}
/* if count < PAGE_SIZE, we have to make it atomic */
	if (count < PAGE_SIZE)
		size = PAGE_SIZE-count;
	else
		size = PAGE_SIZE-1;
	while (count>0) {
		while (PIPE_SIZE(*inode) >= size) {
			if (!PIPE_READERS(*inode)) { /* no readers */
				send_sig(SIGPIPE,current,0);
				return written?written:-EPIPE;
			}
			if (current->signal & ~current->blocked)
				return written?written:-ERESTARTSYS;
			if (filp->f_flags & O_NONBLOCK)
				return written?written:-EAGAIN;
			else
				interruptible_sleep_on(&PIPE_WRITE_WAIT(*inode));
		}
		while (count>0 && (size = (PAGE_SIZE-1)-PIPE_SIZE(*inode))) {
			chars = PAGE_SIZE-PIPE_HEAD(*inode);
			if (chars > count)
				chars = count;
			if (chars > size)
				chars = size;
			memcpy_fromfs(PIPE_BASE(*inode)+PIPE_HEAD(*inode), buf, chars );
			written += chars;
			PIPE_HEAD(*inode) += chars;
			PIPE_HEAD(*inode) &= (PAGE_SIZE-1);
			count -= chars;
			buf += chars;
		}
		wake_up(& PIPE_READ_WAIT(*inode));
		size = PAGE_SIZE-1;
	}
	return written;
}

static int pipe_lseek(struct inode * inode, struct file * file, off_t offset, int orig)
{
	return -ESPIPE;
}

static int pipe_readdir(struct inode * inode, struct file * file, struct dirent * de, int count)
{
	return -ENOTDIR;
}

static int bad_pipe_rw(struct inode * inode, struct file * filp, char * buf, int count)
{
	return -EBADF;
}

static int pipe_ioctl(struct inode *pino, struct file * filp,
	unsigned int cmd, unsigned long arg)
{
	int error;

	switch (cmd) {
		case FIONREAD:
			error = verify_area(VERIFY_WRITE, (void *) arg,4);
			if (!error)
				put_fs_long(PIPE_SIZE(*pino),(unsigned long *) arg);
			return error;
		default:
			return -EINVAL;
	}
}

static int pipe_select(struct inode * inode, struct file * filp, int sel_type, select_table * wait)
{
	switch (sel_type) {
		case SEL_IN:
			if (!PIPE_EMPTY(*inode) || !PIPE_WRITERS(*inode))
				return 1;
			select_wait(&PIPE_READ_WAIT(*inode), wait);
			return 0;
		case SEL_OUT:
			if (!PIPE_FULL(*inode) || !PIPE_READERS(*inode))
				return 1;
			select_wait(&PIPE_WRITE_WAIT(*inode), wait);
			return 0;
		case SEL_EX:
			if (!PIPE_READERS(*inode) || !PIPE_WRITERS(*inode))
				return 1;
			select_wait(&inode->i_wait,wait);
			return 0;
	}
	return 0;
}

/*
 * The 'connect_xxx()' functions are needed for named pipes when
 * the open() code hasn't guaranteed a connection (O_NONBLOCK),
 * and we need to act differently until we do get a writer..
 */
static int connect_read(struct inode * inode, struct file * filp, char * buf, int count)
{
	while (!PIPE_SIZE(*inode)) {
		if (PIPE_WRITERS(*inode))
			break;
		if (filp->f_flags & O_NONBLOCK)
			return -EAGAIN;
		wake_up(& PIPE_WRITE_WAIT(*inode));
		if (current->signal & ~current->blocked)
			return -ERESTARTSYS;
		interruptible_sleep_on(& PIPE_READ_WAIT(*inode));
	}
	filp->f_op = &read_pipe_fops;
	return pipe_read(inode,filp,buf,count);
}

static int connect_select(struct inode * inode, struct file * filp, int sel_type, select_table * wait)
{
	switch (sel_type) {
		case SEL_IN:
			if (!PIPE_EMPTY(*inode)) {
				filp->f_op = &read_pipe_fops;
				return 1;
			}
			select_wait(&PIPE_READ_WAIT(*inode), wait);
			return 0;
		case SEL_OUT:
			if (!PIPE_FULL(*inode))
				return 1;
			select_wait(&PIPE_WRITE_WAIT(*inode), wait);
			return 0;
		case SEL_EX:
			if (!PIPE_READERS(*inode) || !PIPE_WRITERS(*inode))
				return 1;
			select_wait(&inode->i_wait,wait);
			return 0;
	}
	return 0;
}

/*
 * Ok, these three routines NOW keep track of readers/writers,
 * Linus previously did it with inode->i_count checking.
 */
static void pipe_read_release(struct inode * inode, struct file * filp)
{
	PIPE_READERS(*inode)--;
	wake_up(&PIPE_WRITE_WAIT(*inode));
}

static void pipe_write_release(struct inode * inode, struct file * filp)
{
	PIPE_WRITERS(*inode)--;
	wake_up(&PIPE_READ_WAIT(*inode));
}

static void pipe_rdwr_release(struct inode * inode, struct file * filp)
{
	PIPE_READERS(*inode)--;
	PIPE_WRITERS(*inode)--;
	wake_up(&PIPE_READ_WAIT(*inode));
	wake_up(&PIPE_WRITE_WAIT(*inode));
}

/*
 * The file_operations structs are not static because they
 * are also used in linux/fs/fifo.c to do operations on fifo's.
 */
struct file_operations connecting_pipe_fops = {
	pipe_lseek,
	connect_read,
	bad_pipe_rw,
	pipe_readdir,
	connect_select,
	pipe_ioctl,
	NULL,		/* no mmap on pipes.. surprise */
	NULL,		/* no special open code */
	pipe_read_release,
	NULL
};

struct file_operations read_pipe_fops = {
	pipe_lseek,
	pipe_read,
	bad_pipe_rw,
	pipe_readdir,
	pipe_select,
	pipe_ioctl,
	NULL,		/* no mmap on pipes.. surprise */
	NULL,		/* no special open code */
	pipe_read_release,
	NULL
};

struct file_operations write_pipe_fops = {
	pipe_lseek,
	bad_pipe_rw,
	pipe_write,
	pipe_readdir,
	pipe_select,
	pipe_ioctl,
	NULL,		/* mmap */
	NULL,		/* no special open code */
	pipe_write_release,
	NULL
};

struct file_operations rdwr_pipe_fops = {
	pipe_lseek,
	pipe_read,
	pipe_write,
	pipe_readdir,
	pipe_select,
	pipe_ioctl,
	NULL,		/* mmap */
	NULL,		/* no special open code */
	pipe_rdwr_release,
	NULL
};

struct inode_operations pipe_inode_operations = {
	&rdwr_pipe_fops,
	NULL,			/* create */
	NULL,			/* lookup */
	NULL,			/* link */
	NULL,			/* unlink */
	NULL,			/* symlink */
	NULL,			/* mkdir */
	NULL,			/* rmdir */
	NULL,			/* mknod */
	NULL,			/* rename */
	NULL,			/* readlink */
	NULL,			/* follow_link */
	NULL,			/* bmap */
	NULL,			/* truncate */
	NULL			/* permission */
};

extern "C" int sys_pipe(unsigned long * fildes)
{
	struct inode * inode;
	struct file * f[2];
	int fd[2];
	int i,j;

	j = verify_area(VERIFY_WRITE,fildes,8);
	if (j)
		return j;
	for(j=0 ; j<2 ; j++)
		if (!(f[j] = get_empty_filp()))
			break;
	if (j==1)
		f[0]->f_count--;
	if (j<2)
		return -ENFILE;
	j=0;
	for(i=0;j<2 && i<NR_OPEN;i++)
		if (!current->filp[i]) {
			current->filp[ fd[j]=i ] = f[j];
			j++;
		}
	if (j==1)
		current->filp[fd[0]]=NULL;
	if (j<2) {
		f[0]->f_count--;
		f[1]->f_count--;
		return -EMFILE;
	}
	if (!(inode=get_pipe_inode())) {
		current->filp[fd[0]] = NULL;
		current->filp[fd[1]] = NULL;
		f[0]->f_count--;
		f[1]->f_count--;
		return -ENFILE;
	}
	f[0]->f_inode = f[1]->f_inode = inode;
	f[0]->f_pos = f[1]->f_pos = 0;
	f[0]->f_flags = O_RDONLY;
	f[0]->f_op = &read_pipe_fops;
	f[0]->f_mode = 1;		/* read */
	f[1]->f_flags = O_WRONLY;
	f[1]->f_op = &write_pipe_fops;
	f[1]->f_mode = 2;		/* write */
	put_fs_long(fd[0],0+fildes);
	put_fs_long(fd[1],1+fildes);
	return 0;
}
