blob: 73b781dd9958b67a9dc6afd756699b2c1c8ee38a [file] [log] [blame]
/*
*
* 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
* fcntl17.c
*
* DESCRIPTION
* Check deadlock detection for file locking
*
* ALGORITHM
* The parent forks off 3 children. The parent controls the children
* with messages via pipes to create a delayed deadlock between the
* second and third child.
*
* USAGE
* fcntl17
*
* HISTORY
* 07/2001 Ported by Wayne Boyer
* 04/2002 Minor fixes by William Jay Huie (testcase name
fcntl05 => fcntl17, check signal return for SIG_ERR)
*
* RESTRICTIONS
* None
*/
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <inttypes.h>
#include "test.h"
char *TCID = "fcntl17";
int TST_TOTAL = 1;
#define STRINGSIZE 27
#define STRING "abcdefghijklmnopqrstuvwxyz\n"
#define STOP 0xFFF0
#define TIME_OUT 10
/* global variables */
int parent_pipe[2];
int child_pipe1[2];
int child_pipe2[2];
int child_pipe3[2];
int file_fd;
pid_t parent_pid, child_pid1, child_pid2, child_pid3;
int child_stat;
struct flock lock1 = { (short)F_WRLCK, (short)0, 2, 5, (short)0 };
struct flock lock2 = { (short)F_WRLCK, (short)0, 9, 5, (short)0 };
struct flock lock3 = { (short)F_WRLCK, (short)0, 17, 5, (short)0 };
struct flock lock4 = { (short)F_WRLCK, (short)0, 17, 5, (short)0 };
struct flock lock5 = { (short)F_WRLCK, (short)0, 2, 14, (short)0 };
struct flock unlock = { (short)F_UNLCK, (short)0, 0, 0, (short)0 };
/* prototype declarations */
int setup();
void cleanup();
int parent_wait();
void parent_free();
void child_wait();
void child_free();
void do_child1();
void do_child2();
void do_child3();
int do_test(struct flock *, pid_t);
void stop_children();
void catch_child();
void catch_alarm();
char *str_type();
int setup(void)
{
char *buf = STRING;
char template[PATH_MAX];
struct sigaction act;
tst_sig(FORK, DEF_HANDLER, NULL);
umask(0);
TEST_PAUSE;
tst_tmpdir(); /* make temp dir and cd to it */
if (pipe(parent_pipe) < 0) {
tst_resm(TFAIL, "Couldn't create parent_pipe! errno = %d",
errno);
return 1;
}
if (pipe(child_pipe1) < 0) {
tst_resm(TFAIL, "Couldn't create child_pipe1! errno = %d",
errno);
return 1;
}
if (pipe(child_pipe2) < 0) {
tst_resm(TFAIL, "Couldn't create child_pipe2! errno = %d",
errno);
return 1;
}
if (pipe(child_pipe3) < 0) {
tst_resm(TFAIL, "Couldn't create child_pipe3! errno = %d",
errno);
return 1;
}
parent_pid = getpid();
snprintf(template, PATH_MAX, "fcntl17XXXXXX");
if ((file_fd = mkstemp(template)) < 0) {
tst_resm(TFAIL, "Couldn't open temp file! errno = %d", errno);
}
if (write(file_fd, buf, STRINGSIZE) < 0) {
tst_resm(TFAIL, "Couldn't write to temp file! errno = %d",
errno);
}
memset(&act, 0, sizeof(act));
act.sa_handler = catch_alarm;
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask, SIGALRM);
if (sigaction(SIGALRM, &act, NULL) < 0) {
tst_resm(TFAIL, "SIGALRM signal setup failed, errno: %d",
errno);
return 1;
}
memset(&act, 0, sizeof(act));
act.sa_handler = catch_child;
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask, SIGCLD);
if (sigaction(SIGCLD, &act, NULL) < 0) {
tst_resm(TFAIL, "SIGCLD signal setup failed, errno: %d", errno);
return 1;
}
return 0;
}
void cleanup(void)
{
close(file_fd);
tst_rmdir();
}
void do_child1(void)
{
int err;
close(parent_pipe[0]);
close(child_pipe1[1]);
close(child_pipe2[0]);
close(child_pipe2[1]);
close(child_pipe3[0]);
close(child_pipe3[1]);
child_wait(child_pipe1[0]);
tst_resm(TINFO, "child 1 starting");
if (fcntl(file_fd, F_SETLK, &lock1) < 0) {
err = errno;
tst_resm(TINFO, "child 1 lock err %d", err);
parent_free(err);
} else {
tst_resm(TINFO, "child 1 pid %d locked", getpid());
parent_free(0);
}
child_wait(child_pipe1[0]);
tst_resm(TINFO, "child 1 resuming");
fcntl(file_fd, F_SETLK, &unlock);
tst_resm(TINFO, "child 1 unlocked");
child_wait(child_pipe1[0]);
tst_resm(TINFO, "child 1 exiting");
exit(1);
}
void do_child2(void)
{
int err;
close(parent_pipe[0]);
close(child_pipe1[0]);
close(child_pipe1[1]);
close(child_pipe2[1]);
close(child_pipe3[0]);
close(child_pipe3[1]);
child_wait(child_pipe2[0]);
tst_resm(TINFO, "child 2 starting");
if (fcntl(file_fd, F_SETLK, &lock2) < 0) {
err = errno;
tst_resm(TINFO, "child 2 lock err %d", err);
parent_free(err);
} else {
tst_resm(TINFO, "child 2 pid %d locked", getpid());
parent_free(0);
}
child_wait(child_pipe2[0]);
tst_resm(TINFO, "child 2 resuming");
if (fcntl(file_fd, F_SETLKW, &lock4) < 0) {
err = errno;
tst_resm(TINFO, "child 2 lockw err %d", err);
parent_free(err);
} else {
tst_resm(TINFO, "child 2 lockw locked");
parent_free(0);
}
child_wait(child_pipe2[0]);
tst_resm(TINFO, "child 2 exiting");
exit(1);
}
void do_child3(void)
{
int err;
close(parent_pipe[0]);
close(child_pipe1[0]);
close(child_pipe1[1]);
close(child_pipe2[0]);
close(child_pipe2[1]);
close(child_pipe3[1]);
child_wait(child_pipe3[0]);
tst_resm(TINFO, "child 3 starting");
if (fcntl(file_fd, F_SETLK, &lock3) < 0) {
err = errno;
tst_resm(TINFO, "child 3 lock err %d", err);
parent_free(err);
} else {
tst_resm(TINFO, "child 3 pid %d locked", getpid());
parent_free(0);
}
child_wait(child_pipe3[0]);
tst_resm(TINFO, "child 3 resuming");
if (fcntl(file_fd, F_SETLKW, &lock5) < 0) {
err = errno;
tst_resm(TINFO, "child 3 lockw err %d", err);
parent_free(err);
} else {
tst_resm(TINFO, "child 3 lockw locked");
parent_free(0);
}
child_wait(child_pipe3[0]);
tst_resm(TINFO, "child 3 exiting");
exit(1);
}
int do_test(struct flock *lock, pid_t pid)
{
struct flock fl;
fl.l_type = /* lock->l_type */ F_RDLCK;
fl.l_whence = lock->l_whence;
fl.l_start = lock->l_start;
fl.l_len = lock->l_len;
fl.l_pid = (short)0;
if (fcntl(file_fd, F_GETLK, &fl) < 0) {
tst_resm(TFAIL, "fcntl on file failed, errno =%d", errno);
return 1;
}
if (fl.l_type != lock->l_type) {
tst_resm(TFAIL, "lock type is wrong should be %s is %s",
str_type(lock->l_type), str_type(fl.l_type));
return 1;
}
if (fl.l_whence != lock->l_whence) {
tst_resm(TFAIL, "lock whence is wrong should be %d is %d",
lock->l_whence, fl.l_whence);
return 1;
}
if (fl.l_start != lock->l_start) {
tst_resm(TFAIL, "region starts in wrong place, "
"should be %" PRId64 " is %" PRId64,
(int64_t) lock->l_start, (int64_t) fl.l_start);
return 1;
}
if (fl.l_len != lock->l_len) {
tst_resm(TFAIL,
"region length is wrong, should be %" PRId64 " is %"
PRId64, (int64_t) lock->l_len, (int64_t) fl.l_len);
return 1;
}
if (fl.l_pid != pid) {
tst_resm(TFAIL, "locking pid is wrong, should be %d is %d",
pid, fl.l_pid);
return 1;
}
return 0;
}
char *str_type(int type)
{
static char buf[20];
switch (type) {
case F_RDLCK:
return ("F_RDLCK");
case F_WRLCK:
return ("F_WRLCK");
case F_UNLCK:
return ("F_UNLCK");
default:
sprintf(buf, "BAD VALUE: %d", type);
return (buf);
}
}
void parent_free(int arg)
{
if (write(parent_pipe[1], &arg, sizeof(arg)) != sizeof(arg)) {
tst_resm(TFAIL, "couldn't send message to parent");
exit(1);
}
}
int parent_wait(void)
{
int arg;
if (read(parent_pipe[0], &arg, sizeof(arg)) != sizeof(arg)) {
tst_resm(TFAIL, "parent_wait() failed");
return (errno);
}
return (arg);
}
void child_free(int fd, int arg)
{
if (write(fd, &arg, sizeof(arg)) != sizeof(arg)) {
tst_resm(TFAIL, "couldn't send message to child");
exit(1);
}
}
void child_wait(int fd)
{
int arg;
if (read(fd, &arg, sizeof(arg)) != sizeof(arg)) {
tst_resm(TFAIL, "couldn't get message from parent");
exit(1);
} else if (arg == (short)STOP) {
exit(0);
}
}
void stop_children(void)
{
int arg;
signal(SIGCLD, SIG_DFL);
arg = STOP;
child_free(child_pipe1[1], arg);
child_free(child_pipe2[1], arg);
child_free(child_pipe3[1], arg);
wait(0);
}
void catch_child(void)
{
tst_resm(TFAIL, "Unexpected death of child process");
cleanup();
}
void catch_alarm(void)
{
sighold(SIGCHLD);
/*
* Timer has runout and the children have not detected the deadlock.
* Need to kill the kids and exit
*/
if (child_pid1 != 0 && (kill(child_pid1, SIGKILL)) < 0) {
tst_resm(TFAIL, "Attempt to signal child 1 failed.");
}
if (child_pid2 != 0 && (kill(child_pid2, SIGKILL)) < 0) {
tst_resm(TFAIL, "Attempt to signal child 2 failed.");
}
if (child_pid3 != 0 && (kill(child_pid3, SIGKILL)) < 0) {
tst_resm(TFAIL, "Attempt to signal child 2 failed.");
}
tst_resm(TFAIL, "Alarm expired, deadlock not detected");
tst_resm(TWARN, "You may need to kill child processes by hand");
cleanup();
}
int main(int ac, char **av)
{
int ans;
int lc;
const char *msg;
int fail = 0;
if ((msg = parse_opts(ac, av, NULL, NULL)) != NULL) {
tst_brkm(TBROK, NULL, "OPTION PARSING ERROR - %s", msg);
}
#ifdef UCLINUX
maybe_run_child(&do_child1, "nddddddddd", 1, &file_fd,
&parent_pipe[0], &parent_pipe[1],
&child_pipe1[0], &child_pipe1[1],
&child_pipe2[0], &child_pipe2[1],
&child_pipe3[0], &child_pipe3[1]);
maybe_run_child(&do_child2, "nddddddddd", 2, &file_fd,
&parent_pipe[0], &parent_pipe[1],
&child_pipe1[0], &child_pipe1[1],
&child_pipe2[0], &child_pipe2[1],
&child_pipe3[0], &child_pipe3[1]);
maybe_run_child(&do_child3, "nddddddddd", 3, &file_fd,
&parent_pipe[0], &parent_pipe[1],
&child_pipe1[0], &child_pipe1[1],
&child_pipe2[0], &child_pipe2[1],
&child_pipe3[0], &child_pipe3[1]);
#endif
if (setup()) { /* global testup */
tst_resm(TINFO, "setup failed");
cleanup();
}
/* check for looping state if -i option is given */
for (lc = 0; TEST_LOOPING(lc); lc++) {
/* reset tst_count in case we are looping */
tst_count = 0;
tst_resm(TINFO, "Enter preparation phase");
if ((child_pid1 = FORK_OR_VFORK()) == 0) { /* first child */
#ifdef UCLINUX
if (self_exec(av[0], "nddddddddd", 1, file_fd,
parent_pipe[0], parent_pipe[1],
child_pipe1[0], child_pipe1[1],
child_pipe2[0], child_pipe2[1],
child_pipe3[0], child_pipe3[1]) < 0) {
perror("self_exec failed, child 1");
cleanup();
}
#else
do_child1();
#endif
} else if (child_pid1 < 0) {
perror("Fork failed: child 1");
cleanup();
}
/* parent */
if ((child_pid2 = fork()) == 0) { /* second child */
#ifdef UCLINUX
if (self_exec(av[0], "nddddddddd", 2, file_fd,
parent_pipe[0], parent_pipe[1],
child_pipe1[0], child_pipe1[1],
child_pipe2[0], child_pipe2[1],
child_pipe3[0], child_pipe3[1]) < 0) {
perror("self_exec failed, child 2");
cleanup();
}
#else
do_child2();
#endif
} else if (child_pid2 < 0) {
perror("Fork failed: child 2");
if ((kill(child_pid1, SIGKILL)) < 0) {
tst_resm(TFAIL, "Attempt to signal child "
"1 failed");
}
cleanup();
}
/* parent */
if ((child_pid3 = fork()) == 0) { /* third child */
#ifdef UCLINUX
if (self_exec(av[0], "nddddddddd", 3, file_fd,
parent_pipe[0], parent_pipe[1],
child_pipe1[0], child_pipe1[1],
child_pipe2[0], child_pipe2[1],
child_pipe3[0], child_pipe3[1]) < 0) {
perror("self_exec failed, child 3");
cleanup();
}
#else
do_child3();
#endif
do_child3();
} else if (child_pid3 < 0) {
perror("Fork failed: child 3");
if ((kill(child_pid1, SIGKILL)) < 0) {
tst_resm(TFAIL, "Attempt to signal child "
"1 failed");
}
if ((kill(child_pid2, SIGKILL)) < 0) {
tst_resm(TFAIL, "Attempt to signal child 2 "
"failed");
}
cleanup();
}
/* parent */
close(parent_pipe[1]);
close(child_pipe1[0]);
close(child_pipe2[0]);
close(child_pipe3[0]);
tst_resm(TINFO, "Exit preparation phase");
/* //block1: */
tst_resm(TINFO, "Enter block 1");
fail = 0;
/*
* child 1 puts first lock (bytes 2-7)
*/
child_free(child_pipe1[1], 0);
if (parent_wait()) {
tst_resm(TFAIL, "didn't set first child's lock, "
"errno: %d", errno);
}
if (do_test(&lock1, child_pid1)) {
tst_resm(TINFO, "do_test failed child 1");
fail = 1;
}
/*
* child 2 puts second lock (bytes 9-14)
*/
child_free(child_pipe2[1], 0);
if (parent_wait()) {
tst_resm(TINFO, "didn't set second child's lock, "
"errno: %d", errno);
fail = 1;
}
if (do_test(&lock2, child_pid2)) {
tst_resm(TINFO, "do_test failed child 2");
fail = 1;
}
/*
* child 3 puts third lock (bytes 17-22)
*/
child_free(child_pipe3[1], 0);
if (parent_wait()) {
tst_resm(TFAIL, "didn't set third child's lock, "
"errno: %d", errno);
fail = 1;
}
if (do_test(&lock3, child_pid3)) {
tst_resm(TINFO, "do_test failed child 3");
fail = 1;
}
/*
* child 2 tries to lock same range as
* child 3's first lock.
*/
child_free(child_pipe2[1], 0);
/*
* child 3 tries to lock same range as
* child 1 and child 2's first locks.
*/
child_free(child_pipe3[1], 0);
/*
* Tell child 1 to release its lock. This should cause a
* delayed deadlock between child 2 and child 3.
*/
child_free(child_pipe1[1], 0);
/*
* Setup an alarm to go off in case the deadlock is not
* detected
*/
alarm(TIME_OUT);
/*
* should get a message from child 3 telling that its
* second lock EDEADLOCK
*/
if ((ans = parent_wait()) != EDEADLK) {
tst_resm(TFAIL, "child 2 didn't deadlock, "
"returned: %d", ans);
fail = 1;
}
/*
* Double check that lock 2 and lock 3 are still right
*/
do_test(&lock2, child_pid2);
do_test(&lock3, child_pid3);
stop_children();
if (fail) {
tst_resm(TINFO, "Block 1 FAILED");
} else {
tst_resm(TINFO, "Block 1 PASSED");
}
tst_resm(TINFO, "Exit block 1");
}
waitpid(child_pid1, &child_stat, 0);
waitpid(child_pid2, &child_stat, 0);
waitpid(child_pid3, &child_stat, 0);
cleanup();
tst_exit();
}