// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (c) Linux Test Project, 2021
 * Copyright (c) International Business Machines  Corp., 2001
 * 07/2001 Ported by Wayne Boyer
 * MODIFIED: - mridge@us.ibm.com -- changed getpid to syscall(get thread ID)
 * for unique ID on NPTL threading
 */

/*\
 * [Description]
 *
 * Check that file locks are removed when a file descriptor is closed, three
 * different tests are implemented.
 *
 * Parent opens a file and duplicates the file descriptor, places locks using
 * both file descriptors then closes one descriptor, all locks should be
 * removed.
 *
 * Open same file twice using open, place locks using both descriptors then
 * close one descriptor, all lock should be removed.
 *
 * Open file twice, each in a different process, set the locks and the child
 * check the locks. Close the first file descriptor and have child check locks
 * again. Only locks set on first file descriptor should have been removed.
 */

#include <signal.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/syscall.h>
#include <stdlib.h>
#include <stdio.h>
#include "tst_test.h"
#include "lapi/syscalls.h"

#define DATA    "ABCDEFGHIJ"
#define DUP     0
#define OPEN    1
#define FORK_   2

static char tmpname[10] = "testfile";
static int fd[2];

static const struct flock lock_one = {
	.l_type = F_WRLCK,
	.l_whence = 0,
	.l_start = 0L,
	.l_len = 5L,
};

static const struct flock lock_two = {
	.l_type = F_WRLCK,
	.l_whence = 0,
	.l_start = 5L,
	.l_len = 5L,
};

static struct tcase {
	int dup_flag;
	int test_num;
	char *dup_flag_name;
} tcases[] = {
	{DUP, 1, "dup"},
	{OPEN, 2, "open"},
	{FORK_, 3, "fork"}
};

static void lock_region_two(int file_flag, int file_mode)
{
	int fd;

	fd = SAFE_OPEN(tmpname, file_flag, file_mode);

	SAFE_FCNTL(fd, F_SETLK, &lock_two);

	TST_CHECKPOINT_WAKE_AND_WAIT(1);

	SAFE_CLOSE(fd);
}

static void do_test(int file_flag, int file_mode, int dup_flag)
{
	int ret, fd;

	fd = SAFE_OPEN(tmpname, file_flag, file_mode);

	if (!fcntl(fd, F_SETLK, &lock_one))
		tst_res(TFAIL, "Succeeded to lock already locked region one");
	else
		tst_res(TPASS, "Failed to lock already locked region one");

	if (!fcntl(fd, F_SETLK, &lock_two))
		tst_res(TFAIL, "Succeeded to lock already locked region two");
	else
		tst_res(TPASS, "Failed to lock already locked region two");

	TST_CHECKPOINT_WAKE_AND_WAIT(0);

	if (fcntl(fd, F_SETLK, &lock_one))
		tst_res(TFAIL | TERRNO, "Failed to lock now unlocked region one");
	else
		tst_res(TPASS, "Succeeded to lock now ulocked region two");

	ret = fcntl(fd, F_SETLK, &lock_two);

	if (dup_flag == FORK_) {
		if (ret)
			tst_res(TPASS, "Failed to lock already locked region two");
		else
			tst_res(TFAIL, "Succeeded to lock already locked region two");
	} else {
		if (ret)
			tst_res(TFAIL, "Failed to lock now ulocked region two");
		else
			tst_res(TPASS, "Succeeded to lock now ulocked region two");
	}

	SAFE_CLOSE(fd);
	TST_CHECKPOINT_WAKE(0);
}

static int run_test(int file_flag, int file_mode, int dup_flag)
{
	fd[0] = SAFE_OPEN(tmpname, file_flag, file_mode);
	SAFE_WRITE(SAFE_WRITE_ALL, fd[0], DATA, 10);

	switch (dup_flag) {
	case FORK_:
		if (!SAFE_FORK()) {
			lock_region_two(file_flag, file_mode);
			exit(0);
		}
	break;
	case OPEN:
		fd[1] = SAFE_OPEN(tmpname, file_flag, file_mode);
	break;
	case DUP:
		fd[1] = SAFE_FCNTL(fd[0], F_DUPFD, 0);
	break;
	}

	SAFE_FCNTL(fd[0], F_SETLK, &lock_one);

	// Lock region two or wait until the child locked it
	if (dup_flag != FORK_)
		SAFE_FCNTL(fd[1], F_SETLK, &lock_two);
	else
		TST_CHECKPOINT_WAIT(1);

	if (!SAFE_FORK()) {
		do_test(file_flag, file_mode, dup_flag);
		exit(0);
	}

	TST_CHECKPOINT_WAIT(0);

	tst_res(TINFO, "Closing a file descriptor in parent");

	SAFE_CLOSE(fd[0]);

	TST_CHECKPOINT_WAKE_AND_WAIT(0);

	if (dup_flag != FORK_)
		SAFE_CLOSE(fd[1]);
	else
		TST_CHECKPOINT_WAKE(1);

	SAFE_UNLINK(tmpname);
	return 0;
}

static void verify_fcntl(unsigned int n)
{
	struct tcase *tc = &tcases[n];

	tst_res(TINFO, "Running test with %s", tc->dup_flag_name);

	run_test(O_CREAT | O_RDWR | O_TRUNC, 0777, tc->dup_flag);
}

static void cleanup(void)
{
	size_t i;

	for (i = 0; i < ARRAY_SIZE(fd); i++) {
		if (fd[i] > 0)
			SAFE_CLOSE(fd[i]);
	}
}

static struct tst_test test = {
	.tcnt = ARRAY_SIZE(tcases),
	.forks_child = 1,
	.test = verify_fcntl,
	.needs_checkpoints = 1,
	.cleanup = cleanup,
};
