/**
 * 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 <stdio.h>
#include <errno.h>
#include <limits.h>
#include <io.h>

/* Define 64-bit fgetpos() function via 32-bit fgetpos32() function */
_CRTIMP int __cdecl fgetpos32(FILE *__restrict__ _File, long *__restrict__ _Pos);
int __cdecl fgetpos(FILE *__restrict__ _File, fpos_t *__restrict__ _Pos)
{
  __int64 raw_off64;
  long pos32;
  int ret;
  int fd;
  int old_errno;

  old_errno = errno;

  /* Function fgetpos32() on success returns 0 and sets pos32 to [30:0] bits of
   * 64-bit position offset. If the [31] bit of 64-bit position offset is set
   * then fgetpos32() returns -1, sets pos32 to -1 and do not change errno.
   * Non-zero value in [63:32] bits of 64-bit position offset does not trigger
   * any error condition. In other error cases fgetpos32() returns -1,
   * sets pos32 to -1 and errno to error value.
   * Recent Windows versions have different behavior and returns -1 with errno
   * set to EINVAL when position does not fit into 32 bits.
   */

  errno = 0;
  ret = fgetpos32(_File, &pos32);
  if (ret != 0 || pos32 < 0)
  {
    if (errno == 0) /* This detects overflow when [31] bit is set. */
      errno = EOVERFLOW;
    else if (errno == EINVAL) /* Value does not fit into 32 bits. */
      errno = EOVERFLOW;
    *_Pos = pos32;
    return ret;
  }

  /* Now try to detect overflow when [63:32] bits of offset are set.
   * Do it by comparing the raw fd-based 64-bit offset returned by
   * _lseeki64() that is not further than the half of the maximal
   * possible 31-bit position offset returned by fgetpos32().
   * Because FILE* stream use read/write buffers and translation modes,
   * the FILE* stream position and raw fd position can be different
   * and raw fd position be used directly for FILE* stream position.
   */
  fd = fileno(_File);
  if (fd >= 0)
  {
    raw_off64 = _lseeki64(fd, 0, SEEK_CUR);
    if (raw_off64 > LONG_MAX && raw_off64 - pos32 > LONG_MAX/2)
    {
      errno = EOVERFLOW;
      *_Pos = -1;
      return -1;
    }
  }

  errno = old_errno;

  *_Pos = pos32;
  return 0;
}
int (__cdecl *__MINGW_IMP_SYMBOL(fgetpos))(FILE *__restrict__ _File, fpos_t *__restrict__ _Pos) = fgetpos;

int __attribute__ ((alias ("fgetpos"))) __cdecl fgetpos64 (FILE *__restrict__ _File, fpos_t *__restrict__ _Pos);
extern int (__cdecl * __attribute__((alias (__MINGW64_STRINGIFY(__MINGW_IMP_SYMBOL(fgetpos))))) __MINGW_IMP_SYMBOL(fgetpos64))(FILE *__restrict__ _File, fpos_t *__restrict__ _Pos);
