// SPDX-License-Identifier: GPL-2.0-only

/*
 * Copyright 2012 Xyratex Technology Limited
 */

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/file.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <errno.h>

#define LOCK_LEN 100

struct flock flocks[4] = {
	/* 1st region */
	{
		.l_type         = F_WRLCK,
		.l_whence       = SEEK_SET,
		.l_start        = 0,
		.l_len          = LOCK_LEN,
		.l_pid          = 0,
	},
	/* 2nd region */
	{
		.l_type         = F_WRLCK,
		.l_whence       = SEEK_SET,
		.l_start        = LOCK_LEN,
		.l_len          = LOCK_LEN,
		.l_pid          = 0,
	},
	/* 3rd region */
	{
		.l_type         = F_WRLCK,
		.l_whence       = SEEK_SET,
		.l_start        = 2 * LOCK_LEN,
		.l_len          = LOCK_LEN,
		.l_pid          = 0,
	},
	/* 2nd & 3rd regions */
	{
		.l_type         = F_WRLCK,
		.l_whence       = SEEK_SET,
		.l_start        = LOCK_LEN,
		.l_len          = 2 * LOCK_LEN,
		.l_pid          = 0,
	},
};

enum {
	FLOCK_GET       = 0,
	FLOCK_PUT       = 1,
};

#define flock_call(fd, num, get, label)					      \
	flocks[num].l_type = get == FLOCK_GET ? F_WRLCK : F_UNLCK;	      \
	printf("%d: %s lock%d [%llu, %llu]\n", pid,			      \
		get == FLOCK_GET ? "taking" : "putting",		      \
		num, (unsigned long long)flocks[num].l_start,		      \
		(unsigned long long)flocks[num].l_start + flocks[num].l_len); \
	rc = fcntl(fd, F_SETLKW, &flocks[num]);				      \
	if (rc < 0) {							      \
		rc = errno;						      \
		fprintf(stderr, "%d: failed to %s lock%d, %s\n",	      \
			pid, get == FLOCK_GET ? "take" : "put",		      \
			num, strerror(errno));				      \
		goto label;						      \
	} else {							      \
		printf("%d: done\n", pid);				      \
	}

static void catch_alarm(int i)
{
	fprintf(stderr, "lock timeout\n");
	exit(124);
}

int main(int argc, char *argv[])
{
	struct sigaction act;
	int status;
	pid_t wpid = 0;
	int fd, i, pid, num = 0, rc = 0;

	if (argc != 2) {
		fprintf(stderr, "usage: %s <file>\n", argv[0]);
		return EXIT_FAILURE;
	}
	fd = open(argv[1], O_RDWR|O_CREAT, (mode_t)0666);
	if (fd < 0) {
		fprintf(stderr, "error open file %s\n", argv[1]);
		return EXIT_FAILURE;
	}

	for (i = 0; i < 2; i++) {
		fflush(stdout);
		pid = fork();
		if (pid && i == 0)
			wpid = pid;
		if (pid == 0)
			wpid = 0;
		if (pid == 0 && i == 0) {
			pid = getpid();

			flock_call(fd, num, FLOCK_GET, err_lock0);

			printf("%d sleeping 1\n", pid);
			sleep(1);

			/* First of all, it should get blocked on flocks[1]
			 * 2nd child. Later, should deadlock with flocks[2]
			 * parent, after cancelling flocks[1] 2nd child.
			 */
			printf("%d: taking lock3 [%llu, %llu]\n", pid,
				(unsigned long long)flocks[3].l_start,
				(unsigned long long)flocks[3].l_start +
				flocks[3].l_len);
			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) {
				fprintf(stderr,
					"SIGALRM signal setup failed, errno: %d",
					errno);
				rc = 3;
				goto err_lock1;
			}
			alarm(5);
			rc = fcntl(fd, F_SETLKW, &flocks[3]);
			if (rc >= 0) {
				fprintf(stderr,
					"%d: should not succeed to take lock3\n",
					pid);

				flock_call(fd, 3, FLOCK_PUT, err_lock1);
				rc = EINVAL;
				goto err_lock1;
			}
			if (errno != EDEADLK) {
				rc = errno;
				fprintf(stderr,
					"%d: failed to take lock3: %s\n", pid,
					strerror(errno));
				goto err_lock1;
			}

			printf("%d: expected deadlock\n", pid);

			flock_call(fd, num, FLOCK_PUT, err_lock0);
			break;
		} else if (pid == 0 && i == 1) {
			pid = getpid();

			flock_call(fd, 1, FLOCK_GET, err_lock0);

			/* Let flocks[2] 2nd child get granted and
			 * flocks[3] 1st child, flocks[0] parent get blocked.
			 */
			printf("%d sleeping 2\n", pid);
			sleep(2);

			flock_call(fd, 1, FLOCK_PUT, err_lock0);
			break;
		} else if (pid && i == 1) {
			pid = getpid();
			num = 2;

			/* Let flocks[1] 2nd child get granted first */
			printf("%d: sleeping 1\n", pid);
			sleep(1);

			flock_call(fd, num, FLOCK_GET, err_lock0);

			/* Should get blocked on flocks[0], 1st child
			 * and succeed later.
			 */
			flock_call(fd, 0, FLOCK_GET, err_lock1);

			flock_call(fd, 0, FLOCK_PUT, err_lock1);
			flock_call(fd, num, FLOCK_PUT, err_lock0);
			break;
		}
	}

	if (pid == 0)
		sleep(2);
	if (wpid) {
		waitpid(wpid, &status, 0);
		rc = WEXITSTATUS(status);
	}
	printf("%d Exit\n", pid);
	close(fd);
	return rc;

err_lock1:
	flocks[num].l_type = F_UNLCK;
	fcntl(fd, F_SETLKW, &flocks[num]);
err_lock0:
	close(fd);
	return rc;
}
