628 lines
14 KiB
C
628 lines
14 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
|
|
* 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, SIGCHLD);
|
|
if (sigaction(SIGCHLD, &act, NULL) < 0) {
|
|
tst_resm(TFAIL, "SIGCHLD signal setup failed, errno: %d", errno);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void cleanup(void)
|
|
{
|
|
if (child_pid1 > 0)
|
|
kill(child_pid1, 9);
|
|
|
|
if (child_pid2 > 0)
|
|
kill(child_pid2, 9);
|
|
|
|
if (child_pid3 > 0)
|
|
kill(child_pid3, 9);
|
|
|
|
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(SIGCHLD, SIG_DFL);
|
|
arg = STOP;
|
|
child_free(child_pipe1[1], arg);
|
|
child_free(child_pipe2[1], arg);
|
|
child_free(child_pipe3[1], arg);
|
|
waitpid(child_pid1, &child_stat, 0);
|
|
child_pid1 = 0;
|
|
waitpid(child_pid2, &child_stat, 0);
|
|
child_pid2 = 0;
|
|
waitpid(child_pid3, &child_stat, 0);
|
|
child_pid3 = 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;
|
|
int fail = 0;
|
|
|
|
tst_parse_opts(ac, av, NULL, NULL);
|
|
#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)
|
|
tst_brkm(TBROK|TERRNO, cleanup, "Fork failed: child 1");
|
|
|
|
/* 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) {
|
|
tst_brkm(TBROK|TERRNO, cleanup, "Fork failed: child 2");
|
|
}
|
|
|
|
/* 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) {
|
|
tst_brkm(TBROK|TERRNO, cleanup, "Fork failed: child 3");
|
|
}
|
|
/* 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(TFAIL, "Block 1 FAILED");
|
|
} else {
|
|
tst_resm(TPASS, "Block 1 PASSED");
|
|
}
|
|
tst_resm(TINFO, "Exit block 1");
|
|
}
|
|
cleanup();
|
|
tst_exit();
|
|
}
|