#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <fcntl.h>
#include <errno.h>
#include "ptmx_fake.h"
#include "ptmx_fake_internal.h"

/* Allocate a pty using the BSD style scheme - try to open each master
   until you succeed */
static int ptmx_master_alloc(void)
{
	const char *I="pqrstuvwxyzabcde";
	const char *J="0123456789abcdef";
	char pty[11]="/dev/pty??";
	const char *i, *j;
	int fd;

	for(i=I; *i; i++) for(j=J; *j; j++)
	{
		pty[8]=*i, pty[9]=*j;
		if ((fd=open(pty, O_RDWR))!=-1)
			return fd;
	}
	return -1;
}

/* If a pty hasn't been allocated yet, allocate one */
static int ptmx_master(int fd)
{
	static int ptmx_init=0;
	static struct stat devptmx;
	struct stat statbuf;
	int newfd;

	/* Keep a copy of stat(/dev/ptmx) so we can detect when
	   the fake multiplexor is passed to ptsname/grantpt/unlockpt */
	if (!ptmx_init)
	{
		if (stat(PTMX, &devptmx)==-1) return 1;
		ptmx_init=1;
	}

	/* Compare dev/ino of fd with /dev/ptmx - if they don't match then
	   a pty has already been allocated */
	if (fstat(fd, &statbuf)==-1) return 1;
	if (statbuf.st_dev!=devptmx.st_dev 
		|| statbuf.st_ino!=devptmx.st_ino)
		return 0;
       
	/* Allocate a real pty and use dup2 to make it look as if we
           had really been given the pty by the multiplexor device */
	if ((newfd=ptmx_master_alloc())==-1) return 1;
	if (dup2(newfd, fd)==-1) return 1;
	if (close(newfd)==-1) return 1;
	return 0;
}

/* Call the setuid helper to change the ownership of the pty slave.
   Authentication is done by passing the filedescriptor of the pty master 
   (The master can only be opened once so we must be the owner.) */
static int ptmx_chmod(int fd)
{
	int ret=0, status;
	pid_t pid;
	struct sigaction sa, intr, quit;
	sigset_t block, omask;

	sa.sa_handler = SIG_IGN;
	sa.sa_flags = 0;
	sigemptyset(&sa.sa_mask);
	sigemptyset(&block);
	sigaddset(&block, SIGCHLD);
	if (sigaction(SIGINT, &sa, &intr)==-1) {ret=errno; goto out0;}
	if (sigaction(SIGQUIT, &sa, &quit)==-1) {ret=errno; goto out1;}
	if (sigprocmask(SIG_BLOCK, &block, &omask)==-1) {ret=errno; goto out2;}
	if ((pid=fork())==-1) {ret=errno; goto out3;}
	if (pid==0)
	{
		if (dup2(fd, 0)==-1) exit(1);
		execl(PTMX_CHMOD, PTMX_CHMOD, NULL); 
		exit(1);
	}
	while (waitpid(pid, &status, 0)==-1)
		if (errno!=EINTR) {ret=errno; goto out3;}
	if (sigprocmask(SIG_SETMASK, &omask, NULL)==-1) {ret=errno; goto out2;}
	if (sigaction(SIGQUIT, &quit, NULL)==-1) {ret=errno; goto out1;}
	if (sigaction(SIGINT, &intr, NULL)==-1) {ret=errno; goto out0;}
	return !WIFEXITED(status) || WEXITSTATUS(status)!=0;

 out3:	sigprocmask(SIG_SETMASK, &omask, NULL);
 out2:	sigaction(SIGQUIT, &quit, NULL);
 out1:	sigaction(SIGINT, &intr, NULL);
 out0:	errno=ret;
	return -1;
}
	
/* Return the name of the slave device */
char *ptsname(int fd)
{
	struct stat statbuf;
	static char tty[11]="/dev/tty??";
	const char *I="pqrstuvwxyzabcde";
	const char *J="0123456789abcdef";
	unsigned int minor;

	if (ptmx_master(fd)) return NULL;
	if (fstat(fd, &statbuf)==-1) return NULL;
	if (!S_ISCHR(statbuf.st_mode)) return NULL;
	if (MAJOR(statbuf.st_rdev)!=PTY_MASTER_MAJOR) return NULL;
	minor=MINOR(statbuf.st_rdev);
	tty[8]=I[minor/16];
	tty[9]=J[minor%16];
	if (stat(tty, &statbuf)==-1) return NULL;
	if (!S_ISCHR(statbuf.st_mode)) return NULL;
	if (MAJOR(statbuf.st_rdev)!=PTY_SLAVE_MAJOR) return NULL;
	if ((unsigned int )MINOR(statbuf.st_rdev)!=minor) return NULL;
	return tty;
}

/* Change the ownership and throw other processes off the slave */
int grantpt(int fd)
{
	struct stat statbuf;

	if (ptmx_master(fd)) return -1;
	if (fstat(fd, &statbuf)==-1) return -1;
	if (!S_ISCHR(statbuf.st_mode)) return -1;
	if (MAJOR(statbuf.st_rdev)!=PTY_MASTER_MAJOR) return -1;
	if (ptmx_chmod(fd)) return -1;
	return 0;

}

/* Unlock the pty (a no-op in this implementation) */
int unlockpt(int fd)
{
	struct stat statbuf;

	if (ptmx_master(fd)) return -1;
	if (fstat(fd, &statbuf)==-1) return -1;
	if (!S_ISCHR(statbuf.st_mode)) return -1;
	if (MAJOR(statbuf.st_rdev)!=PTY_MASTER_MAJOR) return -1;
	return 0;
}

