/***********************************************************************
*                                                                      *
*               This software is part of the ast package               *
*          Copyright (c) 1985-2012 AT&T Intellectual Property          *
*          Copyright (c) 2020-2023 Contributors to ksh 93u+m           *
*                      and is licensed under the                       *
*                 Eclipse Public License, Version 2.0                  *
*                                                                      *
*                A copy of the License is available at                 *
*      https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html      *
*         (with md5 checksum 84283fa8859daf213bdda5a9f8d1be1d)         *
*                                                                      *
*                 Glenn Fowler <gsf@research.att.com>                  *
*                  David Korn <dgk@research.att.com>                   *
*                   Phong Vo <kpv@research.att.com>                    *
*                  Martijn Dekker <martijn@inlv.org>                   *
*                                                                      *
***********************************************************************/

#include "asohdr.h"

#if !_aso_fcntl

NoN(aso_meth_fcntl)

#else

#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

typedef struct APL_s
{
	int		fd;
	size_t		size;
	char		path[1];
} APL_t;

static void*
aso_init_fcntl(void* data, const char* details)
{
	APL_t*		apl = (APL_t*)data;
	char*		path;
	char*		opt;
	size_t		size;
	size_t		references;
	int		n;
	int		fd;
	int		drop;
	int		perm;
	struct flock	lock;
	char		buf[PATH_MAX];
	char		tmp[64];

	if (apl)
	{
		lock.l_type = F_WRLCK;
		lock.l_whence = SEEK_SET;
		lock.l_start = apl->size;
		lock.l_len = sizeof(references);
		if (fcntl(apl->fd, F_SETLKW, &lock) >= 0)
		{
			if (lseek(apl->fd, apl->size, SEEK_SET) != apl->size)
				references = 0;
			else if (read(apl->fd, &references, sizeof(references)) != sizeof(references))
				references = 0;
			else if (references > 0)
			{
				references--;
				if (lseek(apl->fd, apl->size, SEEK_SET) != apl->size)
					references = 0;
				else if (write(apl->fd, &references, sizeof(references)) != sizeof(references))
					references = 0;
			}
			lock.l_type = F_UNLCK;
			fcntl(apl->fd, F_SETLK, &lock);
			if (!references)
				remove(apl->path);
		}
		close(apl->fd);
		free(apl);
		return NULL;
	}
	fd = -1;
	perm = S_IRUSR|S_IWUSR;
	drop = 0;
	size = 32 * 1024 - sizeof(references);
	if (path = (char*)details)
		while (opt = strchr(path, ','))
		{
			if (strneq(path, "perm=", 5))
			{
				if ((n = opt - (path + 5)) >= sizeof(tmp))
					n = sizeof(tmp) - 1;
				memcpy(tmp, path + 5, n);
				tmp[n] = 0;
				perm = strperm(tmp, NULL, perm);
			}
			else if (strneq(path, "size=", 5))
			{
				size = strtoul(path + 5, NULL, 0);
				if (size <= sizeof(references))
					goto bad;
				size -= sizeof(references);
			}
			path = opt + 1;
		}
	if (!path || !*path)
	{
		if (!(path = pathtemp(buf, sizeof(buf), NULL, "aso", &fd)))
			return NULL;
		drop = 1;
	}
	if (!(apl = newof(0, APL_t, 1, strlen(path))))
		goto bad;
	if (fd >= 0 || (fd = open(path, O_RDWR|O_cloexec)) < 0 && (fd = open(path, O_CREAT|O_RDWR|O_cloexec, perm)) >= 0)
	{
		if (lseek(fd, size, SEEK_SET) != size)
			goto bad;
		references = 1;
		if (write(fd, &references, sizeof(references)) != sizeof(references))
			goto bad;
	}
	else
	{
		if ((size = lseek(fd, 0, SEEK_END)) <= sizeof(references))
			goto bad;
		size -= sizeof(references);
		lock.l_type = F_WRLCK;
		lock.l_whence = SEEK_SET;
		lock.l_start = 0;
		lock.l_len = sizeof(references);
		if (fcntl(fd, F_SETLKW, &lock) < 0)
			goto bad;
		if (lseek(fd, size, SEEK_SET) != size)
			goto bad;
		if (read(fd, &references, sizeof(references)) != sizeof(references))
			goto bad;
		references++;
		if (lseek(fd, size, SEEK_SET) != size)
			goto bad;
		if (write(fd, &references, sizeof(references)) != sizeof(references))
			goto bad;
		lock.l_type = F_UNLCK;
		fcntl(fd, F_SETLK, &lock);
	}
	apl->fd = fd;
	apl->size = size;
	strcpy(apl->path, path);
	return apl;
 bad:
	if (apl)
		free(apl);
	if (fd >= 0)
		close(fd);
	if (drop)
		remove(path);
	return NULL;
}

static ssize_t
aso_lock_fcntl(void* data, ssize_t k, void volatile* p)
{
	APL_t*		apl = (APL_t*)data;
	struct flock	lock;

	if (!apl)
		return -1;
	if (k > 0)
		lock.l_type = F_UNLCK;
	else
	{
		lock.l_type = F_WRLCK;
		k = HASH(p, apl->size) + 1;
	}
	lock.l_whence = SEEK_SET;
	lock.l_start = k - 1;
	lock.l_len = 1;
	return fcntl(apl->fd, F_SETLKW, &lock) < 0 ? -1 : k;
}

Asometh_t	_aso_meth_fcntl = { "fcntl", ASO_PROCESS, aso_init_fcntl, aso_lock_fcntl };

#endif
