/*
 *
 *   Copyright (c) International Business Machines  Corp., 2002
 *
 *   This program is free software;  you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY;  without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
 *   the GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program;  if not, write to the Free Software
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

/* 12/23/2002	Port to LTP	robbiew@us.ibm.com */
/* 06/30/2001	Port to Linux	nsharoff@us.ibm.com */

#ifndef _GNU_SOURCE
#define _GNU_SOURCE 1
#endif

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termio.h>
#include <unistd.h>

/** LTP Port **/
#include "test.h"

char *TCID = "pty01";		/* Test program identifier.    */
int TST_TOTAL = 5;		/* Total number of test cases. */
/**************/

/*
 * pty master clone device
 */
#define MASTERCLONE "/dev/ptmx"

/*
 * string for testing read/write on ptys
 */
#define STRING "Linux Test Project\n"

/*
 * test buffer size
 */
#define TESTSIZE 1024

/*
 * mode we expect grantpt() to leave pty as
 */
#define PTY_MODE 020622

/*
 * number of procs for parallel test
 */
#define NUMPROCS 15

/*
 * test slave locking
 */
static int test1(void)
{
	int masterfd;		/* master pty fd */
	int slavefd;		/* slave pty fd */
	char *slavename;
	struct stat st;
	char buf[TESTSIZE];

	masterfd = open(MASTERCLONE, O_RDWR);
	if (masterfd < 0) {
		tst_brkm(TBROK | TERRNO, NULL, MASTERCLONE);
	}

	slavename = ptsname(masterfd);
	if (slavename == NULL) {
		tst_brkm(TBROK | TERRNO, NULL, "ptsname() call failed");
	}

	if (grantpt(masterfd) != 0) {
		tst_brkm(TBROK | TERRNO, NULL, "grantpt() call failed");
	}

	if (stat(slavename, &st) != 0) {
		tst_brkm(TBROK | TERRNO, NULL, "stat(%s) failed", slavename);
	}
	if (st.st_uid != getuid()) {
		tst_brkm(TBROK, NULL, "uid mismatch");
	}

	if (st.st_mode != (S_IFCHR | S_IRUSR | S_IWUSR | S_IWGRP)) {
		tst_brkm(TBROK, NULL, "mode mismatch (mode=%o)", st.st_mode);
	}

	slavefd = open(slavename, O_RDWR);
	if (slavefd >= 0) {
		tst_brkm(TBROK, NULL, "open didn't fail as expected!");
	}

	if (unlockpt(masterfd) != 0) {
		tst_brkm(TBROK | TERRNO, NULL, "unlockpt() failed");
	}

	slavefd = open(slavename, O_RDWR);
	if (slavefd < 0) {
		tst_brkm(TBROK, NULL, "Could not open %s", slavename);
	}

	/*
	 * test writing to the master / reading from the slave
	 */
	if (write(masterfd, STRING, strlen(STRING)) != strlen(STRING)) {
		/*
		 * XXX: the errno printout might be garbage, but better to be
		 * safe than sorry..
		 */
		tst_brkm(TFAIL | TERRNO, NULL, "write to master");
	}

	if (read(slavefd, buf, strlen(STRING)) != strlen(STRING)) {
		/* XXX: Same as write above.. */
		tst_brkm(TFAIL | TERRNO, NULL, "read from slave");
	}
	if (strncmp(STRING, buf, strlen(STRING) - 1) != 0) {
		tst_brkm(TFAIL, NULL,
			 "strings are different (STRING = '%s' != buf = '%s')",
			 STRING, buf);
	}

	/*
	 * test writing to the slave / reading from the master
	 */
	if (write(slavefd, STRING, strlen(STRING)) != strlen(STRING)) {
		/* XXX: Same as write above.. */
		tst_brkm(TFAIL | TERRNO, NULL, "write to slave");
	}

	if (read(masterfd, buf, strlen(STRING)) != strlen(STRING)) {
		/* XXX: Same as write above.. */
		tst_brkm(TFAIL | TERRNO, NULL, "read from master");
	}
	if (strncmp(STRING, buf, strlen(STRING) - 1) != 0) {
		tst_brkm(TFAIL, NULL,
			 "strings are different (STRING = '%s' != buf = '%s').",
			 STRING, buf);
	}

	/*
	 * try an invalid ioctl on the slave...
	 */
	if (ioctl(slavefd, TIOCGWINSZ, NULL) == 0) {
		tst_brkm(TFAIL, NULL,
			 "invalid slave TIOCGWINSZ ioctl succeeded.. it should "
			 "have failed");
	}

	/*
	 * try an invalid ioctl on the master...
	 */
	if (ioctl(masterfd, TIOCGWINSZ, NULL) == 0) {
		tst_brkm(TFAIL, NULL,
			 "invalid master TIOCGWINSZ ioctl succeeded.. it should "
			 "have failed");
	}

	/*
	 * close pty fds
	 */
	if (close(slavefd) != 0) {
		tst_brkm(TBROK | TERRNO, NULL, "close of slave");
	}
	if (close(masterfd) != 0) {
		tst_brkm(TBROK | TERRNO, NULL, "close of master");
	}
	tst_resm(TPASS, "test1");
	/** NOTREACHED **/
	return 0;
}

/*
 * test slave operations with closed master
 */
static void test2(void)
{
	int masterfd;		/* master pty fd */
	int slavefd;		/* slave pty fd */
	int i;
	char *slavename;
	char c;

	masterfd = open(MASTERCLONE, O_RDWR);
	if (masterfd < 0) {
		tst_brkm(TBROK | TERRNO, NULL, MASTERCLONE);
	}

	slavename = ptsname(masterfd);
	if (slavename == NULL) {
		tst_brkm(TBROK | TERRNO, NULL, "ptsname() call failed");
	}

	if (grantpt(masterfd) != 0) {
		tst_brkm(TBROK | TERRNO, NULL, "grantpt() call failed");
	}

	if (unlockpt(masterfd) != 0) {
		tst_brkm(TBROK | TERRNO, NULL, "unlockpt() call failed");
	}

	slavefd = open(slavename, O_RDWR);
	if (slavefd < 0) {
		tst_brkm(TBROK | TERRNO, NULL, "Could not open %s", slavename);
	}

	/*
	 * close pty fds.  See what happens when we close the master
	 * first.
	 */
	if (close(masterfd) != 0) {
		tst_brkm(TBROK | TERRNO, NULL, "close()");
	}

	errno = 0;
	if ((i = read(slavefd, &c, 1)) == 1) {
		tst_brkm(TFAIL, NULL,
			 "reading from slave fd should have failed, but didn't"
			 "(read '%c')", c);
	}

	if ((i = write(slavefd, &c, 1)) == 1) {
		tst_brkm(TFAIL, NULL,
			 "writing to slave fd should have failed, but didn't");
	}

	if (ioctl(slavefd, TIOCGWINSZ, NULL) == 0) {
		tst_brkm(TFAIL, NULL,
			 "trying TIOCGWINSZ on slave fd should have failed, "
			 "but didn't");
	}

	if (close(slavefd) != 0) {
		tst_brkm(TBROK, NULL, "close");
	}
	tst_resm(TPASS, "test2");
}

/*
 * test operations on master with closed slave
 */
static void test3(void)
{
	int masterfd;		/* master pty fd */

	masterfd = open(MASTERCLONE, O_RDWR);
	if (masterfd < 0) {
		tst_brkm(TBROK, NULL, MASTERCLONE);
	}

	if (ioctl(masterfd, TIOCGWINSZ, NULL) == 0) {
		tst_brkm(TFAIL | TERRNO, NULL,
			 "trying TIOCGWINSZ on master with no open slave "
			 "succeeded unexpectedly");
	}
	tst_resm(TPASS, "test3");
}

/*
 * test multiple opens on slave side of pty
 */
static void test4(void)
{
	int masterfd;		/* master pty fd */
	int slavefd;		/* slave pty fd */
	int slavefd2;
	int slavefd3;
	char *slavename;

	masterfd = open(MASTERCLONE, O_RDWR);
	if (masterfd < 0) {
		tst_brkm(TBROK, NULL, "%s", MASTERCLONE);
	}

	slavename = ptsname(masterfd);
	if (slavename == NULL) {
		tst_brkm(TBROK, NULL, "ptsname() call failed");
	}

	if (grantpt(masterfd) != 0) {
		tst_brkm(TBROK, NULL, "grantpt() call failed");
	}

	if (unlockpt(masterfd) != 0) {
		tst_brkm(TBROK | TERRNO, NULL, "unlockpt() call failed");
	}

	slavefd = open(slavename, O_RDWR);
	if (slavefd < 0) {
		tst_brkm(TBROK | TERRNO, NULL, "Could not open %s", slavename);
	}

	slavefd2 = open(slavename, O_RDWR);
	if (slavefd < 0) {
		tst_brkm(TFAIL | TERRNO, NULL, "Could not open %s (again)",
			 slavename);
	}

	slavefd3 = open(slavename, O_RDWR);
	if (slavefd < 0) {
		tst_brkm(TFAIL | TERRNO, NULL, "Could not open %s (once more)",
			 slavename);
	}

	/*
	 * close pty fds.
	 */
	if (close(slavefd) != 0) {
		tst_brkm(TBROK | TERRNO, NULL, "close slave");
	}

	if (close(slavefd2) != 0) {
		tst_brkm(TBROK, NULL, "close slave again");
	}

	if (close(slavefd3) != 0) {
		tst_brkm(TBROK, NULL, "close slave once more");
	}

	if (close(masterfd) != 0) {
		tst_brkm(TBROK, NULL, "close master");
	}
	tst_resm(TPASS, "test4");
}

/*
 * test opening/closing lots of ptys in parallel.  We may run out
 * of ptys for this test depending on how the system is configured,
 * but that's not a fatal error.
 */
static void test5(void)
{
	int masterfd;		/* master pty fd */
	char *slavename;
	int status;
	int i;

	for (i = 0; i < NUMPROCS; ++i) {
		switch (fork()) {
		case -1:
			tst_brkm(TBROK, NULL, "fork()");
			break;
		case 0:
			masterfd = open(MASTERCLONE, O_RDWR);
			if (masterfd < 0) {
				printf("proc %d: opening %s failed: %s",
				       i, MASTERCLONE, strerror(errno));
				exit(1);
			}
			if (grantpt(masterfd) != 0) {
				printf("proc %d: grantpt() call failed: %s",
				       i, strerror(errno));
				exit(1);
			}
			slavename = ptsname(masterfd);
			if (slavename == NULL) {
				printf("proc %d: ptsname() call failed: %s",
				       i, strerror(errno));
				exit(1);
			}
			sleep(10);
			if (close(masterfd) != 0) {
				printf("proc %d: close failed: %s",
				       i, strerror(errno));
				exit(1);
			}
			exit(0);
		default:
			break;
		}
	}
	while (wait(&status) > 0) {
		if (status) {
			tst_brkm(TFAIL, NULL,
				 "child exited with non-zero status %d",
				 status);
		}
	}
	tst_resm(TPASS, "test5");
}

/*
 * main test driver
 */
int main(int argc, char **argv)
{
	test1();
	test2();
	test3();
	test4();
	test5();

	/*
	 * all done
	 */
	tst_exit();
}
