/*-------------------------------------------------------------------------
 *
 * open.c
 *	   Win32 open() replacement
 *
 *
 * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
 *
 * src/port/open.c
 *
 *-------------------------------------------------------------------------
 */

#ifdef WIN32

#ifndef FRONTEND
#include "postgres.h"
#else
#include "postgres_fe.h"
#endif

#include "port/win32ntdll.h"

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

static int
openFlagsToCreateFileFlags(int openFlags)
{
	switch (openFlags & (O_CREAT | O_TRUNC | O_EXCL))
	{
			/* O_EXCL is meaningless without O_CREAT */
		case 0:
		case O_EXCL:
			return OPEN_EXISTING;

		case O_CREAT:
			return OPEN_ALWAYS;

			/* O_EXCL is meaningless without O_CREAT */
		case O_TRUNC:
		case O_TRUNC | O_EXCL:
			return TRUNCATE_EXISTING;

		case O_CREAT | O_TRUNC:
			return CREATE_ALWAYS;

			/* O_TRUNC is meaningless with O_CREAT */
		case O_CREAT | O_EXCL:
		case O_CREAT | O_TRUNC | O_EXCL:
			return CREATE_NEW;
	}

	/* will never get here */
	return 0;
}

/*
 * Internal function used by pgwin32_open() and _pgstat64().  When
 * backup_semantics is true, directories may be opened (for limited uses).  On
 * failure, INVALID_HANDLE_VALUE is returned and errno is set.
 */
HANDLE
pgwin32_open_handle(const char *fileName, int fileFlags, bool backup_semantics)
{
	HANDLE		h;
	SECURITY_ATTRIBUTES sa;
	int			loops = 0;

	if (initialize_ntdll() < 0)
		return INVALID_HANDLE_VALUE;

	/* Check that we can handle the request */
	assert((fileFlags & ((O_RDONLY | O_WRONLY | O_RDWR) | O_APPEND |
						 (O_RANDOM | O_SEQUENTIAL | O_TEMPORARY) |
						 _O_SHORT_LIVED | O_DSYNC | O_DIRECT | O_CLOEXEC |
						 (O_CREAT | O_TRUNC | O_EXCL) | (O_TEXT | O_BINARY))) == fileFlags);

	sa.nLength = sizeof(sa);
	sa.lpSecurityDescriptor = NULL;

	/*
	 * If O_CLOEXEC is specified, create a non-inheritable handle.  Otherwise,
	 * create an inheritable handle (the default Windows behavior).
	 *
	 * Note: We could instead use SetHandleInformation() after CreateFile() to
	 * clear HANDLE_FLAG_INHERIT, but this way avoids rare leaks in
	 * multi-threaded programs that create processes, just like POSIX
	 * O_CLOEXEC.
	 */
	sa.bInheritHandle = !(fileFlags & O_CLOEXEC);

	while ((h = CreateFile(fileName,
	/* cannot use O_RDONLY, as it == 0 */
						   (fileFlags & O_RDWR) ? (GENERIC_WRITE | GENERIC_READ) :
						   ((fileFlags & O_WRONLY) ? GENERIC_WRITE : GENERIC_READ),
	/* These flags allow concurrent rename/unlink */
						   (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE),
						   &sa,
						   openFlagsToCreateFileFlags(fileFlags),
						   FILE_ATTRIBUTE_NORMAL |
						   (backup_semantics ? FILE_FLAG_BACKUP_SEMANTICS : 0) |
						   ((fileFlags & O_RANDOM) ? FILE_FLAG_RANDOM_ACCESS : 0) |
						   ((fileFlags & O_SEQUENTIAL) ? FILE_FLAG_SEQUENTIAL_SCAN : 0) |
						   ((fileFlags & _O_SHORT_LIVED) ? FILE_ATTRIBUTE_TEMPORARY : 0) |
						   ((fileFlags & O_TEMPORARY) ? FILE_FLAG_DELETE_ON_CLOSE : 0) |
						   ((fileFlags & O_DIRECT) ? FILE_FLAG_NO_BUFFERING : 0) |
						   ((fileFlags & O_DSYNC) ? FILE_FLAG_WRITE_THROUGH : 0),
						   NULL)) == INVALID_HANDLE_VALUE)
	{
		/*
		 * Sharing violation or locking error can indicate antivirus, backup
		 * or similar software that's locking the file.  Wait a bit and try
		 * again, giving up after 30 seconds.
		 */
		DWORD		err = GetLastError();

		if (err == ERROR_SHARING_VIOLATION ||
			err == ERROR_LOCK_VIOLATION)
		{
#ifndef FRONTEND
			if (loops == 50)
				ereport(LOG,
						(errmsg("could not open file \"%s\": %s", fileName,
								(err == ERROR_SHARING_VIOLATION) ? _("sharing violation") : _("lock violation")),
						 errdetail("Continuing to retry for 30 seconds."),
						 errhint("You might have antivirus, backup, or similar software interfering with the database system.")));
#endif

			if (loops < 300)
			{
				pg_usleep(100000);
				loops++;
				continue;
			}
		}

		/*
		 * ERROR_ACCESS_DENIED is returned if the file is deleted but not yet
		 * gone (Windows NT status code is STATUS_DELETE_PENDING).  In that
		 * case, we'd better ask for the NT status too so we can translate it
		 * to a more Unix-like error.  We hope that nothing clobbers the NT
		 * status in between the internal NtCreateFile() call and CreateFile()
		 * returning.
		 *
		 * If there's no O_CREAT flag, then we'll pretend the file is
		 * invisible.  With O_CREAT, we have no choice but to report that
		 * there's a file in the way (which wouldn't happen on Unix).
		 */
		if (err == ERROR_ACCESS_DENIED &&
			pg_RtlGetLastNtStatus() == STATUS_DELETE_PENDING)
		{
			if (fileFlags & O_CREAT)
				err = ERROR_FILE_EXISTS;
			else
				err = ERROR_FILE_NOT_FOUND;
		}

		_dosmaperr(err);
		return INVALID_HANDLE_VALUE;
	}

	return h;
}

int
pgwin32_open(const char *fileName, int fileFlags,...)
{
	HANDLE		h;
	int			fd;

	h = pgwin32_open_handle(fileName, fileFlags, false);
	if (h == INVALID_HANDLE_VALUE)
		return -1;

#ifdef FRONTEND

	/*
	 * Since PostgreSQL 12, those concurrent-safe versions of open() and
	 * fopen() can be used by frontends, having as side-effect to switch the
	 * file-translation mode from O_TEXT to O_BINARY if none is specified.
	 * Caller may want to enforce the binary or text mode, but if nothing is
	 * defined make sure that the default mode maps with what versions older
	 * than 12 have been doing.
	 */
	if ((fileFlags & O_BINARY) == 0)
		fileFlags |= O_TEXT;
#endif

	/* _open_osfhandle will, on error, set errno accordingly */
	if ((fd = _open_osfhandle((intptr_t) h, fileFlags & O_APPEND)) < 0)
		CloseHandle(h);			/* will not affect errno */
	else if (fileFlags & (O_TEXT | O_BINARY) &&
			 _setmode(fd, fileFlags & (O_TEXT | O_BINARY)) < 0)
	{
		_close(fd);
		return -1;
	}

	return fd;
}

FILE *
pgwin32_fopen(const char *fileName, const char *mode)
{
	int			openmode = 0;
	int			fd;

	if (strstr(mode, "r+"))
		openmode |= O_RDWR;
	else if (strchr(mode, 'r'))
		openmode |= O_RDONLY;
	if (strstr(mode, "w+"))
		openmode |= O_RDWR | O_CREAT | O_TRUNC;
	else if (strchr(mode, 'w'))
		openmode |= O_WRONLY | O_CREAT | O_TRUNC;
	if (strchr(mode, 'a'))
		openmode |= O_WRONLY | O_CREAT | O_APPEND;

	if (strchr(mode, 'b'))
		openmode |= O_BINARY;
	if (strchr(mode, 't'))
		openmode |= O_TEXT;

	fd = pgwin32_open(fileName, openmode);
	if (fd == -1)
		return NULL;
	return _fdopen(fd, mode);
}

#endif
