/**
 * This file has no copyright assigned and is placed in the Public Domain.
 * This file is part of the mingw-w64 runtime package.
 * No warranty is given; refer to the file DISCLAIMER.PD within this package.
 */

#include <io.h>
#include <errno.h>
#include <limits.h>
#include <windows.h>

/* Define 64-bit _lseeki64() function via 32-bit CRT _lseek() function and 64-bit WinAPI SetFilePointer() function */
__int64 __cdecl _lseeki64(int fd, __int64 offset, int whence)
{
  BOOL offset_overflowed = offset < LONG_MIN || offset > LONG_MAX;

  if (!offset_overflowed)
  {
    /* _lseek() takes only 32-bit offset value but can seek to file position
     * which does not fit into 32-bit integer via SEEK_CUR / SEEK_END.
     * Just the return value of new offset is truncated to 32 bits.
     * _lseek() does not signal truncation, so check here just for offset
     * overflow for SEEK_SET. For all other cases call SetFilePointer()
     * to retrieve new 64-bit file offset. On recent Windows versions
     * when the current file position does not fit into the 32 bits then
     * the _lseek() function returns error without setting new position. */
    errno = 0;
    long ret = _lseek(fd, offset, whence);
    offset_overflowed = (ret == -1 && errno);
    if (whence == SEEK_SET && !offset_overflowed)
      return ret;
  }

  HANDLE handle = (HANDLE)_get_osfhandle(fd);
  if (handle == INVALID_HANDLE_VALUE)
  {
    errno = EBADF;
    return -1;
  }

  /* If offset did not overflow then _lseek() was called and it already
   * set new file position, so just read that new file position.
   * If offset overflowed then _lseek() was not called at all,
   * so move in the file and set new position.
   * Note that FILE_* method and SEEK_* whence constants are same. */
  _Static_assert(FILE_BEGIN == SEEK_SET, "FILE_BEGIN == SEEK_SET");
  _Static_assert(FILE_CURRENT == SEEK_CUR, "FILE_CURRENT == SEEK_CUR");
  _Static_assert(FILE_END == SEEK_END, "FILE_END == SEEK_END");
  LARGE_INTEGER li = { .QuadPart = offset_overflowed ? offset : 0 };
  DWORD method = offset_overflowed ? whence : FILE_CURRENT;
  li.LowPart = SetFilePointer(handle, li.LowPart, &li.HighPart, method);
  if (li.LowPart == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR)
  {
    errno = EINVAL;
    return -1;
  }

  /* At this stage, the file position was already changed by SetFilePointer().
   * For any file position change it is needed to clear EOF flag on fd, because
   * otherwise _read() would not work. The easiest way to clear EOF flag is to
   * call _lseek() function which at success path clears the EOF flag. If the
   * offset did not overflow then the _lseek() was already called. So when offset
   * overflowed call it with 0/SEEK_CUR which does nothing and clears EOF flag. */
  if (offset_overflowed)
  {
    (void)_lseek(fd, 0, SEEK_CUR);
  }

  return li.QuadPart;
}
__int64 (__cdecl *__MINGW_IMP_SYMBOL(_lseeki64))(int, __int64, int) = _lseeki64;

__int64 __attribute__ ((alias ("_lseeki64"))) __cdecl lseek64(int, __int64, int);
extern __int64 (__cdecl * __attribute__((alias (__MINGW64_STRINGIFY(__MINGW_IMP_SYMBOL(_lseeki64))))) __MINGW_IMP_SYMBOL(lseek64))(int, __int64, int);
