565 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
	
			
		
		
	
	
			565 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
	
| /*
 | |
|  *
 | |
|  *   Copyright (c) International Business Machines  Corp., 2001
 | |
|  *
 | |
|  *   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
 | |
|  */
 | |
| 
 | |
| /*
 | |
|  * NAME
 | |
|  *	fcntl15.c
 | |
|  *
 | |
|  * DESCRIPTION
 | |
|  *	Check that file locks are removed when file closed
 | |
|  *
 | |
|  * ALGORITHM
 | |
|  *	Use three testcases to check removal of locks when a file is closed.
 | |
|  *
 | |
|  *	Case 1: Parent opens a file and duplicates it, places locks using
 | |
|  *	both file descriptors then closes one descriptor, all locks should
 | |
|  *	be removed.
 | |
|  *
 | |
|  *	Case 2: Open same file twice using(open), place locks using both
 | |
|  *	descriptors then close on descriptor, locks on the file should be
 | |
|  *	lost
 | |
|  *
 | |
|  *	Case 3: Open file twice, one by each process, set the locks and have
 | |
|  *	a child check the locks. Remove the first file and have the child
 | |
|  *	check the locks. Remove the first file and have child check locks
 | |
|  *	again. Only locks set on first file should have been removed
 | |
|  *
 | |
|  * USAGE
 | |
|  *	fcntl15
 | |
|  *
 | |
|  * HISTORY
 | |
|  *	07/2001 Ported by Wayne Boyer
 | |
|  * MODIFIED: - mridge@us.ibm.com -- changed getpid to syscall(get thread ID) for unique ID on NPTL threading
 | |
|  *
 | |
|  * RESTRICTIONS
 | |
|  *	None
 | |
|  */
 | |
| 
 | |
| #include <signal.h>
 | |
| #include <fcntl.h>
 | |
| #include "test.h"
 | |
| #include <sys/types.h>
 | |
| #include <sys/wait.h>
 | |
| #include <sys/types.h>
 | |
| #include <sys/syscall.h>
 | |
| #include <linux/unistd.h>
 | |
| 
 | |
| #define	DATA	"ABCDEFGHIJ"
 | |
| #define	DUP	0
 | |
| #define	OPEN	1
 | |
| #define	FORK_	2
 | |
| 
 | |
| char *TCID = "fcntl15";
 | |
| int TST_TOTAL = 1;
 | |
| 
 | |
| static int parent, child1, child2, status;
 | |
| static volatile sig_atomic_t parent_flag, child_flag, alarm_flag;
 | |
| static char tmpname[40];
 | |
| struct flock flock;
 | |
| 
 | |
| #ifdef UCLINUX
 | |
| static char *argv0;		/* set by main, passed to self_exec */
 | |
| #endif
 | |
| 
 | |
| 
 | |
| void alarm_sig(int sig)
 | |
| {
 | |
| 	signal(SIGALRM, alarm_sig);
 | |
| 	alarm_flag = 1;
 | |
| 	if ((syscall(__NR_gettid)) == parent) {
 | |
| 		tst_resm(TINFO, "Alarm caught by parent");
 | |
| 	} else {
 | |
| 		tst_resm(TINFO, "Alarm caught by child");
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void child_sig(int sig)
 | |
| {
 | |
| 	signal(SIGUSR1, child_sig);
 | |
| 	child_flag++;
 | |
| }
 | |
| 
 | |
| void parent_sig(int sig)
 | |
| {
 | |
| 	signal(SIGUSR2, parent_sig);
 | |
| 	parent_flag++;
 | |
| }
 | |
| 
 | |
| int dochild1(int file_flag, int file_mode)
 | |
| {
 | |
| 	int fd_B;
 | |
| 	sigset_t newmask, zeromask, oldmask;
 | |
| 
 | |
| 	if ((fd_B = open(tmpname, file_flag, file_mode)) < 0) {
 | |
| 		perror("open on child1 file failed");
 | |
| 		exit(1);
 | |
| 	}
 | |
| 
 | |
| 	/* initialize lock structure for second 5 bytes of file */
 | |
| 	flock.l_type = F_WRLCK;
 | |
| 	flock.l_whence = 0;
 | |
| 	flock.l_start = 5L;
 | |
| 	flock.l_len = 5L;
 | |
| 
 | |
| 	/* set lock on child file descriptor */
 | |
| 	if ((fcntl(fd_B, F_SETLK, &flock)) < 0) {
 | |
| 		perror("child lock failed should have succeeded");
 | |
| 		exit(1);
 | |
| 	}
 | |
| 
 | |
| 	sigemptyset(&zeromask);
 | |
| 	sigemptyset(&newmask);
 | |
| 	sigaddset(&newmask, SIGUSR1);
 | |
| 	sigaddset(&newmask, SIGUSR2);
 | |
| 	sigaddset(&newmask, SIGALRM);
 | |
| 	if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) {
 | |
| 		perror("child1 sigprocmask SIG_BLOCK fail");
 | |
| 		exit(1);
 | |
| 	}
 | |
| 	/*
 | |
| 	 * send signal to parent here to tell parent we have locked the
 | |
| 	 * file, thus allowing parent to proceed
 | |
| 	 */
 | |
| 	if ((kill(parent, SIGUSR1)) < 0) {
 | |
| 		perror("child1 signal to parent failed");
 | |
| 		exit(1);
 | |
| 	}
 | |
| 
 | |
| 	/*
 | |
| 	 * set alarm to break pause if parent fails to signal then spin till
 | |
| 	 * parent ready
 | |
| 	 */
 | |
| 	alarm(60);
 | |
| 	while (parent_flag == 0 && alarm_flag == 0)
 | |
| 		sigsuspend(&zeromask);
 | |
| 	alarm((unsigned)0);
 | |
| 	if (parent_flag != 1) {
 | |
| 		perror("pause in child1 terminated without "
 | |
| 		       "SIGUSR2 signal from parent");
 | |
| 		exit(1);
 | |
| 	}
 | |
| 	parent_flag = 0;
 | |
| 	alarm_flag = 0;
 | |
| 	if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) {
 | |
| 		perror("child1 sigprocmask SIG_SETMASK fail");
 | |
| 		exit(1);
 | |
| 	}
 | |
| 
 | |
| 	/* wait for child2 to complete then cleanup */
 | |
| 	sleep(10);
 | |
| 	close(fd_B);
 | |
| 	exit(0);
 | |
| }
 | |
| 
 | |
| #ifdef UCLINUX
 | |
| int uc_file_flag, uc_file_mode, uc_dup_flag;
 | |
| 
 | |
| void dochild1_uc(void)
 | |
| {
 | |
| 	dochild1(uc_file_flag, uc_file_mode);
 | |
| }
 | |
| 
 | |
| void dochild2_uc(void)
 | |
| {
 | |
| 	dochild2(uc_file_flag, uc_dup_flag);
 | |
| }
 | |
| #endif
 | |
| 
 | |
| int dofork(int file_flag, int file_mode)
 | |
| {
 | |
| 	/* create child process */
 | |
| 	if ((child1 = FORK_OR_VFORK()) < 0) {
 | |
| 		perror("Fork failure");
 | |
| 		return 1;
 | |
| 	}
 | |
| 
 | |
| 	/* child1 */
 | |
| 	if (child1 == 0) {
 | |
| #ifdef UCLINUX
 | |
| 		if (self_exec(argv0, "nddds", 1, file_flag, file_mode,
 | |
| 			      parent, tmpname) < 0) {
 | |
| 			perror("self_exec failure");
 | |
| 			return 1;
 | |
| 		}
 | |
| #else
 | |
| 		dochild1(file_flag, file_mode);
 | |
| #endif
 | |
| 	} else {
 | |
| 		/*
 | |
| 		 * need to wait for child1 to open, and lock the area of the
 | |
| 		 * file prior to continuing on from here
 | |
| 		 */
 | |
| 		sigset_t newmask, zeromask, oldmask;
 | |
| 		sigemptyset(&zeromask);
 | |
| 		sigemptyset(&newmask);
 | |
| 		sigaddset(&newmask, SIGUSR1);
 | |
| 		sigaddset(&newmask, SIGUSR2);
 | |
| 		sigaddset(&newmask, SIGALRM);
 | |
| 		if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) {
 | |
| 			perror("parent sigprocmask SIG_BLOCK fail");
 | |
| 			exit(1);
 | |
| 		}
 | |
| 
 | |
| 		/*
 | |
| 		 * set alarm to break pause if parent fails to signal then spin till
 | |
| 		 * parent ready
 | |
| 		 */
 | |
| 		alarm(60);
 | |
| 		while (child_flag == 0 && alarm_flag == 0)
 | |
| 			sigsuspend(&zeromask);
 | |
| 		alarm((unsigned)0);
 | |
| 		if (child_flag != 1) {
 | |
| 			perror("parent paused without SIGUSR1 " "from child");
 | |
| 			exit(1);
 | |
| 		}
 | |
| 		child_flag = 0;
 | |
| 		alarm_flag = 0;
 | |
| 		if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) {
 | |
| 			perror("parent sigprocmask SIG_SETMASK fail");
 | |
| 			exit(1);
 | |
| 		}
 | |
| 	}
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| int dochild2(int file_flag, int file_mode, int dup_flag)
 | |
| {
 | |
| 	int fd_C;
 | |
| 	sigset_t newmask, zeromask, oldmask;
 | |
| 
 | |
| 	if ((fd_C = open(tmpname, file_flag, file_mode)) < 0) {
 | |
| 		perror("open on child2 file failed");
 | |
| 		exit(1);
 | |
| 	}
 | |
| 
 | |
| 	/* initialize lock structure for first 5 bytes of file */
 | |
| 	flock.l_type = F_WRLCK;
 | |
| 	flock.l_whence = 0;
 | |
| 	flock.l_start = 0L;
 | |
| 	flock.l_len = 5L;
 | |
| 
 | |
| 	/* Set lock on child file descriptor */
 | |
| 	if ((fcntl(fd_C, F_SETLK, &flock)) >= 0) {
 | |
| 		tst_resm(TFAIL, "First child2 lock succeeded should "
 | |
| 			 "have failed");
 | |
| 		exit(1);
 | |
| 	}
 | |
| 
 | |
| 	/* initialize lock structure for second 5 bytes of file */
 | |
| 	flock.l_type = F_WRLCK;
 | |
| 	flock.l_whence = 0;
 | |
| 	flock.l_start = 5L;
 | |
| 	flock.l_len = 5L;
 | |
| 
 | |
| 	/* set lock on child file descriptor */
 | |
| 	if ((fcntl(fd_C, F_SETLK, &flock)) >= 0) {
 | |
| 		tst_resm(TFAIL, "second child2 lock succeeded should have "
 | |
| 			 "failed");
 | |
| 		exit(1);
 | |
| 	}
 | |
| 
 | |
| 	sigemptyset(&zeromask);
 | |
| 	sigemptyset(&newmask);
 | |
| 	sigaddset(&newmask, SIGUSR1);
 | |
| 	sigaddset(&newmask, SIGUSR2);
 | |
| 	sigaddset(&newmask, SIGALRM);
 | |
| 	if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) {
 | |
| 		perror("child2 sigprocmask SIG_BLOCK fail");
 | |
| 		exit(1);
 | |
| 	}
 | |
| 	/*
 | |
| 	 * send signal to parent here to tell parent we have locked the
 | |
| 	 * file, thus allowing parent to proceed
 | |
| 	 */
 | |
| 	if ((kill(parent, SIGUSR1)) < 0) {
 | |
| 		perror("child2 signal to parent failed");
 | |
| 		exit(1);
 | |
| 	}
 | |
| 
 | |
| 	/*
 | |
| 	 * set alarm to break pause if parent fails to signal then spin till
 | |
| 	 * parent ready
 | |
| 	 */
 | |
| 	alarm(60);
 | |
| 	while (parent_flag == 0 && alarm_flag == 0)
 | |
| 		sigsuspend(&zeromask);
 | |
| 	alarm((unsigned)0);
 | |
| 	if (parent_flag != 1) {
 | |
| 		perror("pause in child2 terminated without "
 | |
| 		       "SIGUSR2 signal from parent");
 | |
| 		exit(1);
 | |
| 	}
 | |
| 	parent_flag = 0;
 | |
| 	alarm_flag = 0;
 | |
| 	if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) {
 | |
| 		perror("child2 sigprocmask SIG_SETMASK fail");
 | |
| 		exit(1);
 | |
| 	}
 | |
| 
 | |
| 	/* initialize lock structure for first 5 bytes of file */
 | |
| 	flock.l_type = F_WRLCK;
 | |
| 	flock.l_whence = 0;
 | |
| 	flock.l_start = 0L;
 | |
| 	flock.l_len = 5L;
 | |
| 
 | |
| 	/* set lock on child file descriptor */
 | |
| 	if ((fcntl(fd_C, F_SETLK, &flock)) < 0) {
 | |
| 		tst_resm(TFAIL, "third child2 lock failed should have "
 | |
| 			 "succeeded");
 | |
| 		exit(1);
 | |
| 	}
 | |
| 
 | |
| 	/* Initialize lock structure for second 5 bytes of file */
 | |
| 	flock.l_type = F_WRLCK;
 | |
| 	flock.l_whence = 0;
 | |
| 	flock.l_start = 5L;
 | |
| 	flock.l_len = 5L;
 | |
| 
 | |
| 	/* set lock on child file descriptor */
 | |
| 	if (dup_flag == FORK_) {
 | |
| 		if ((fcntl(fd_C, F_SETLK, &flock)) >= 0) {
 | |
| 			tst_resm(TFAIL, "fourth child2 lock succeeded "
 | |
| 				 "should have failed");
 | |
| 			exit(1);
 | |
| 		}
 | |
| 	} else {
 | |
| 		if ((fcntl(fd_C, F_SETLK, &flock)) < 0) {
 | |
| 			tst_resm(TFAIL, "fourth child2 lock failed "
 | |
| 				 "should have succeeded");
 | |
| 			exit(1);
 | |
| 		}
 | |
| 	}
 | |
| 	close(fd_C);
 | |
| 	exit(0);
 | |
| }
 | |
| 
 | |
| void setup(void)
 | |
| {
 | |
| 	tst_sig(FORK, DEF_HANDLER, NULL);
 | |
| 
 | |
| 	TEST_PAUSE;
 | |
| }
 | |
| 
 | |
| int run_test(int file_flag, int file_mode, int dup_flag)
 | |
| {
 | |
| 	int fd_A, fd_B;
 | |
| 	fd_B = -1;
 | |
| 	sigset_t newmask, zeromask, oldmask;
 | |
| 
 | |
| 	/* setup to catch SIGUSR1 signal from child process */
 | |
| 	if ((signal(SIGUSR1, child_sig)) == SIG_ERR) {
 | |
| 		perror("Signal setup for SIGUSR1 failed");
 | |
| 	}
 | |
| 
 | |
| 	/* setup to catch SIGUSR2 signal from parent */
 | |
| 	if ((signal(SIGUSR2, parent_sig)) == SIG_ERR) {
 | |
| 		perror("Signal setup for SIGUSR1 failed");
 | |
| 	}
 | |
| 
 | |
| 	parent = syscall(__NR_gettid);
 | |
| 
 | |
| 	tst_tmpdir();
 | |
| 	/* setup temporary file name */
 | |
| 	sprintf(tmpname, "fcntl15.%d", parent);
 | |
| 
 | |
| 	/* initialize signal flags */
 | |
| 	child_flag = parent_flag = alarm_flag = 0;
 | |
| 
 | |
| 	if ((fd_A = open(tmpname, file_flag, file_mode)) < 0) {
 | |
| 		perror("open first parent file failed");
 | |
| 		tst_rmdir();
 | |
| 		return 1;
 | |
| 	}
 | |
| 
 | |
| 	/* write some data to the file */
 | |
| 	(void)write(fd_A, DATA, 10);
 | |
| 
 | |
| 	if (dup_flag) {
 | |
| 		if (dup_flag == FORK_) {
 | |
| 			dofork(file_flag, file_mode);
 | |
| 		} else {
 | |
| 			if ((fd_B = open(tmpname, file_flag, file_mode)) < 0) {
 | |
| 				perror("open second parent file failed");
 | |
| 				tst_rmdir();
 | |
| 				return 1;
 | |
| 			}
 | |
| 		}
 | |
| 	} else {
 | |
| 		/* create a second file descriptor from first file */
 | |
| 		if ((fd_B = fcntl(fd_A, F_DUPFD, 0)) < 0) {
 | |
| 			perror("dup of second parent file failed");
 | |
| 			tst_rmdir();
 | |
| 			return 1;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/*
 | |
| 	 * initialize lock structure for first lock on first
 | |
| 	 * 5 bytes of file
 | |
| 	 */
 | |
| 	flock.l_type = F_WRLCK;
 | |
| 	flock.l_whence = 0;
 | |
| 	flock.l_start = 0L;
 | |
| 	flock.l_len = 5L;
 | |
| 
 | |
| 	/* set lock on first file descriptor */
 | |
| 	if ((fcntl(fd_A, F_SETLK, &flock)) < 0) {
 | |
| 		perror("Attempt to set first parent lock failed");
 | |
| 		tst_rmdir();
 | |
| 		return 1;
 | |
| 	}
 | |
| 
 | |
| 	if (dup_flag != FORK_) {
 | |
| 		/* initialize lock structure for last 5 bytes of file */
 | |
| 		flock.l_type = F_WRLCK;
 | |
| 		flock.l_whence = 0;
 | |
| 		flock.l_start = 5L;
 | |
| 		flock.l_len = 5L;
 | |
| 
 | |
| 		/* set lock on second file descriptor */
 | |
| 		if ((fcntl(fd_B, F_SETLK, &flock)) < 0) {
 | |
| 			perror("Attempt to set second parent lock failed");
 | |
| 			tst_rmdir();
 | |
| 			return 1;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/* create child process */
 | |
| 	if ((child2 = FORK_OR_VFORK()) < 0) {
 | |
| 		perror("Fork failure");
 | |
| 		tst_rmdir();
 | |
| 		return 1;
 | |
| 	} else if (child2 == 0) {	/* child */
 | |
| #ifdef UCLINUX
 | |
| 		if (self_exec(argv0, "ndddds", 2, file_flag, file_mode,
 | |
| 			      dup_flag, parent, tmpname) < 0)
 | |
| 			tst_brkm(TBROK | TERRNO, NULL, "self_exec failed");
 | |
| #else
 | |
| 		dochild2(file_flag, file_mode, dup_flag);
 | |
| #endif
 | |
| 	}
 | |
| 
 | |
| 	/* parent */
 | |
| 
 | |
| 	/*
 | |
| 	 * Set alarm to break pause if child fails to signal then spin till
 | |
| 	 * child is ready
 | |
| 	 */
 | |
| 
 | |
| 	sigemptyset(&zeromask);
 | |
| 	sigemptyset(&newmask);
 | |
| 	sigaddset(&newmask, SIGUSR1);
 | |
| 	sigaddset(&newmask, SIGUSR2);
 | |
| 	sigaddset(&newmask, SIGALRM);
 | |
| 	if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) {
 | |
| 		perror("parent sigprocmask SIG_BLOCK fail");
 | |
| 		exit(1);
 | |
| 	}
 | |
| 
 | |
| 	/*
 | |
| 	 * set alarm to break pause if parent fails to signal then spin till
 | |
| 	 * parent ready
 | |
| 	 */
 | |
| 	alarm(60);
 | |
| 	while (child_flag == 0 && alarm_flag == 0)
 | |
| 		sigsuspend(&zeromask);
 | |
| 	alarm((unsigned)0);
 | |
| 	if (child_flag != 1) {
 | |
| 		perror("parent paused without SIGUSR1 " "from child");
 | |
| 		exit(1);
 | |
| 	}
 | |
| 	child_flag = 0;
 | |
| 	alarm_flag = 0;
 | |
| 	if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) {
 | |
| 		perror("parent sigprocmask SIG_SETMASK fail");
 | |
| 		exit(1);
 | |
| 	}
 | |
| 
 | |
| 	/* close the first file then signal child to test locks */
 | |
| 	close(fd_A);
 | |
| 	if ((kill(child2, SIGUSR2)) < 0) {
 | |
| 		perror("Signal to child2 failed");
 | |
| 		tst_rmdir();
 | |
| 		return 1;
 | |
| 	}
 | |
| 
 | |
| 	if (dup_flag == FORK_) {
 | |
| 		if ((kill(child1, SIGUSR2)) < 0) {
 | |
| 			perror("Signal to child1 failed");
 | |
| 			tst_rmdir();
 | |
| 			return 1;
 | |
| 		}
 | |
| 	}
 | |
| 	/* wait for child to complete then cleanup */
 | |
| 	while ((wait(&status)) > 0) {
 | |
| 		if (status >> 8 != 0) {
 | |
| 			tst_resm(TFAIL, "Expected 0 got %d", status >> 8);
 | |
| 			tst_rmdir();
 | |
| 			return 1;
 | |
| 		}
 | |
| 	}
 | |
| 	if (dup_flag != FORK_) {
 | |
| 		close(fd_B);
 | |
| 	}
 | |
| 	unlink(tmpname);
 | |
| 	tst_rmdir();
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| int main(int ac, char **av)
 | |
| {
 | |
| 	int lc;
 | |
| 
 | |
| 	tst_parse_opts(ac, av, NULL, NULL);
 | |
| #ifdef UCLINUX
 | |
| 	maybe_run_child(&dochild1_uc, "nddds", 1, &uc_file_flag,
 | |
| 			&uc_file_mode, &parent, tmpname);
 | |
| 	maybe_run_child(&dochild2_uc, "nddds", 1, &uc_file_flag,
 | |
| 			&uc_file_mode, &uc_dup_flag, &parent, tmpname);
 | |
| 	argv0 = av[0];
 | |
| #endif
 | |
| 
 | |
| 	setup();
 | |
| 
 | |
| 	for (lc = 0; TEST_LOOPING(lc); lc++) {
 | |
| 		tst_count = 0;
 | |
| 
 | |
| 		if ((signal(SIGALRM, alarm_sig)) == SIG_ERR) {
 | |
| 			perror("SIGALRM signal set up failed");
 | |
| 			exit(1);
 | |
| 		}
 | |
| 
 | |
| 		if (run_test(O_CREAT | O_RDWR | O_TRUNC, 0777, DUP))
 | |
| 			tst_resm(TFAIL, "Test 1: test with \"dup\" FAILED");
 | |
| 		else
 | |
| 			tst_resm(TPASS, "Test 1: test with \"dup\" PASSED");
 | |
| 
 | |
| 		if (run_test(O_CREAT | O_RDWR | O_TRUNC, 0777, OPEN))
 | |
| 			tst_resm(TFAIL, "Test 2: test with \"open\" FAILED");
 | |
| 		else
 | |
| 			tst_resm(TPASS, "Test 2: test with \"open\" PASSED");
 | |
| 
 | |
| 		if (run_test(O_CREAT | O_RDWR | O_TRUNC, 0777, FORK_))
 | |
| 			tst_resm(TFAIL, "Test 3: test with \"fork\" FAILED");
 | |
| 		else
 | |
| 			tst_resm(TPASS, "Test 3: test with \"fork\" PASSED");
 | |
| 	}
 | |
| 	tst_exit();
 | |
| }
 |