/****************************************************************************
**  SIONLIB     http://www.fz-juelich.de/jsc/sionlib                       **
*****************************************************************************
**  Copyright (c) 2008-2021                                                **
**  Forschungszentrum Juelich, Juelich Supercomputing Centre               **
**                                                                         **
**  Copyright (c) 2019                                                     **
**  DataDirect Networks                                                    **
**                                                                         **
**  See the file COPYRIGHT in the package base directory for details       **
****************************************************************************/

#define _XOPEN_SOURCE 700

#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>

#include "sion_config.h"
#include "sion_const.h"
#include "sion_debug.h"
#include "sion_error_handler.h"
#include "sion_file.h"

#if defined(_SION_SIONFWD)
#include <sionfwd/client.h>
#endif

#ifdef _SION_IME_NATIVE
#include "ime_native.h"
#endif

_sion_fileptr *_lsion_file_open(const char *fname, unsigned int flags, unsigned int addflags)
{
  if (flags & SION_FILE_FLAG_ANSI) {
    _sion_fileptr *sion_fileptr = _lsion_file_alloc_and_init_sion_fileptr();
    sion_fileptr->flags |= SION_FILE_FLAG_ANSI;

    if (flags & SION_FILE_FLAG_WRITE) {
      sion_fileptr->flags |= SION_FILE_FLAG_WRITE;

      if (flags & SION_FILE_FLAG_CREATE) {
        sion_fileptr->flags |= SION_FILE_FLAG_CREATE;
        sion_fileptr->fileptr = _lsion_file_open_ansi_write_create(fname, addflags);
      } else {
        sion_fileptr->fileptr = _lsion_file_open_ansi_write_existing(fname, addflags);
      }
    } else {
      sion_fileptr->flags |= SION_FILE_FLAG_READ;
      sion_fileptr->fileptr = _lsion_file_open_ansi_read(fname, addflags);
    }

    /* could not open ANSI file */
    if (!sion_fileptr->fileptr) {
      free(sion_fileptr);
      _lsion_errorprint(SION_NOT_SUCCESS, _SION_ERROR_WARN, "error: could not open file (ANSI), %s %d %d, aborting ...\n", fname,
        flags, addflags);
      return NULL;
    }

    return sion_fileptr;
  } else if (flags & SION_FILE_FLAG_POSIX) {
    _sion_fileptr *sion_fileptr = _lsion_file_alloc_and_init_sion_fileptr();
    sion_fileptr->flags |= SION_FILE_FLAG_POSIX;

    if (flags & SION_FILE_FLAG_WRITE) {
      sion_fileptr->flags |= SION_FILE_FLAG_WRITE;
      if (flags & SION_FILE_FLAG_CREATE) {
        sion_fileptr->flags |= SION_FILE_FLAG_CREATE;
        sion_fileptr->fd = _lsion_file_open_posix_write_create(fname, addflags);
      } else {
        sion_fileptr->fd = _lsion_file_open_posix_write_existing(fname, addflags);
      }
    } else {
      sion_fileptr->flags |= SION_FILE_FLAG_READ;
      sion_fileptr->fd = _lsion_file_open_posix_read(fname, addflags);
    }

    /* could not open POSIX file */
    if (sion_fileptr->fd < 0) {
      free(sion_fileptr);
      _lsion_errorprint(SION_NOT_SUCCESS, _SION_ERROR_WARN, "error: could not open file (POSIX), %s %d %d, aborting ...\n", fname,
        flags, addflags);
      return NULL;
    }

    return sion_fileptr;
  }
#if defined(_SION_SIONFWD)
  else if (flags & SION_FILE_FLAG_SIONFWD) {
    _sion_fileptr *sion_fileptr = _lsion_file_alloc_and_init_sion_fileptr();
    sion_fileptr->flags |= SION_FILE_FLAG_SIONFWD;

    if (flags & SION_FILE_FLAG_WRITE) {
      sion_fileptr->flags |= SION_FILE_FLAG_WRITE;
      if (flags & SION_FILE_FLAG_CREATE) {
        sion_fileptr->flags |= SION_FILE_FLAG_CREATE;
        sion_fileptr->fd = _sion_file_open_sionfwd_write_create(fname, addflags);
      } else {
        sion_fileptr->fd = _sion_file_open_sionfwd_write_existing(fname, addflags);
      }
    } else {
      sion_fileptr->flags |= SION_FILE_FLAG_READ;
      sion_fileptr->fd = _sion_file_open_sionfwd_read(fname, addflags);
    }

    /* could not open SIONfwd file */
    if (sion_fileptr->fd == -1) {
      free(sion_fileptr);
      _lsion_errorprint(SION_NOT_SUCCESS, _SION_ERROR_WARN, "error: could not open file (SIONFWD), %s %d %d, aborting ...\n",
        fname, flags, addflags);
      return (NULL);
    }

    return (sion_fileptr);
  }
#endif
#ifdef _SION_IME_NATIVE
  else if (flags & SION_FILE_FLAG_IME_NATIVE) {
    _sion_fileptr *sion_fileptr = _lsion_file_alloc_and_init_sion_fileptr();
    sion_fileptr->flags |= SION_FILE_FLAG_IME_NATIVE;

    if (flags & SION_FILE_FLAG_WRITE) {
      sion_fileptr->flags |= SION_FILE_FLAG_WRITE;
      if (flags & SION_FILE_FLAG_CREATE) {
        sion_fileptr->flags |= SION_FILE_FLAG_CREATE;
        sion_fileptr->fd = _sion_file_open_ime_native_write_create(fname, addflags);
      } else {
        sion_fileptr->fd = _sion_file_open_ime_native_write_existing(fname, addflags);
      }
    } else {
      sion_fileptr->flags |= SION_FILE_FLAG_READ;
      sion_fileptr->fd = _sion_file_open_ime_native_read(fname, addflags);
    }

    /* could not open IME native file */
    if (sion_fileptr->fd < 0) {
      free(sion_fileptr);
      _lsion_errorprint(SION_NOT_SUCCESS, _SION_ERROR_WARN, "error: could not open file (IME native), %s %d %d, aborting ...\n",
        fname, flags, addflags);
      return NULL;
    }

    return sion_fileptr;
  }
#endif
  else {
    /* unknown mode? */
    _lsion_errorprint(
      SION_NOT_SUCCESS, _SION_ERROR_WARN, "internal error: unknown open type, %s %d %d, aborting ...\n", fname, flags, addflags);
    return NULL;
  }
}

#define DFUNCTION "_lsion_file_close"
int _lsion_file_close(_sion_fileptr *sion_fileptr)
{
  if (NULL == sion_fileptr) {
    return SION_NOT_SUCCESS;
  }

  int rc;
  if (sion_fileptr->flags & SION_FILE_FLAG_ANSI) {
    rc = _lsion_file_close_ansi(sion_fileptr->fileptr);
    sion_fileptr->fileptr = NULL;
  } else if (sion_fileptr->flags & SION_FILE_FLAG_POSIX) {
    rc = _lsion_file_close_posix(sion_fileptr->fd);
  }
#if defined(_SION_SIONFWD)
  else if (sion_fileptr->flags & SION_FILE_FLAG_SIONFWD) {
    rc = _sion_file_close_sionfwd(sion_fileptr->fd);
  }
#endif
#ifdef _SION_IME_NATIVE
  else if (sion_fileptr->flags & SION_FILE_FLAG_IME_NATIVE) {
    rc = _sion_file_close_ime_native(sion_fileptr->fd);
  }
#endif
  else {
    rc = SION_NOT_SUCCESS;
  }

  DPRINTFP((32, DFUNCTION, -1, "free now fileptr=%x %d rc=%d\n", sion_fileptr, sion_fileptr->flags, rc));
  free(sion_fileptr);

  return rc;
}
#undef DFUNCTION

int64_t _lsion_file_write(const void *data, int64_t bytes, _sion_fileptr *sion_fileptr)
{
  if (NULL == sion_fileptr) {
    return SION_SIZE_NOT_VALID;
  }

  if (sion_fileptr->flags & SION_FILE_FLAG_ANSI) {
    return _lsion_file_write_ansi(data, bytes, sion_fileptr->fileptr);
  } else if (sion_fileptr->flags & SION_FILE_FLAG_POSIX) {
    return _lsion_file_write_posix(data, bytes, sion_fileptr->fd);
  }
#if defined(_SION_SIONFWD)
  else if (sion_fileptr->flags & SION_FILE_FLAG_SIONFWD) {
    return _sion_file_write_sionfwd(data, bytes, sion_fileptr->fd, &sion_fileptr->position);
  }
#endif
#ifdef _SION_IME_NATIVE
  else if (sion_fileptr->flags & SION_FILE_FLAG_IME_NATIVE) {
    return _sion_file_write_ime_native(data, bytes, sion_fileptr->fd);
  }
#endif
  else {
    return _lsion_errorprint(
      SION_SIZE_NOT_VALID, _SION_ERROR_RETURN, "_lsion_file_write: cannot find valid file flag (flags=%d)\n", sion_fileptr->flags);
  }
}

int64_t _lsion_file_read(void *data, int64_t bytes, _sion_fileptr *sion_fileptr)
{
  if (NULL == sion_fileptr) {
    return SION_SIZE_NOT_VALID;
  }

  if (sion_fileptr->flags & SION_FILE_FLAG_ANSI) {
    return _lsion_file_read_ansi(data, bytes, sion_fileptr->fileptr);
  } else if (sion_fileptr->flags & SION_FILE_FLAG_POSIX) {
    return _lsion_file_read_posix(data, bytes, sion_fileptr->fd);
  }
#if defined(_SION_SIONFWD)
  else if (sion_fileptr->flags & SION_FILE_FLAG_SIONFWD) {
    return _sion_file_read_sionfwd(data, bytes, sion_fileptr->fd, &sion_fileptr->position);
  }
#endif
#ifdef _SION_IME_NATIVE
  else if (sion_fileptr->flags & SION_FILE_FLAG_IME_NATIVE) {
    return _sion_file_read_ime_native(data, bytes, sion_fileptr->fd);
  }
#endif
  else {
    return _lsion_errorprint(
      SION_SIZE_NOT_VALID, _SION_ERROR_RETURN, "_lsion_file_read: cannot find valid file flag (flags=%d)\n", sion_fileptr->flags);
  }
}

long _lsion_file_get_opt_blksize(_sion_fileptr *sion_fileptr)
{
  if (NULL == sion_fileptr) {
    return SION_SIZE_NOT_VALID;
  }

  if (sion_fileptr->flags & SION_FILE_FLAG_ANSI) {
    return _lsion_file_get_opt_blksize_ansi(sion_fileptr->fileptr);
  } else if (sion_fileptr->flags & SION_FILE_FLAG_POSIX) {
    return _lsion_file_get_opt_blksize_posix(sion_fileptr->fd);
  }
#if defined(_SION_SIONFWD)
  else if (sion_fileptr->flags & SION_FILE_FLAG_SIONFWD) {
    return _sion_file_get_opt_blksize_sionfwd(sion_fileptr->fd);
  }
#endif
#ifdef _SION_IME_NATIVE
  else if (sion_fileptr->flags & SION_FILE_FLAG_IME_NATIVE) {
    return _sion_file_get_opt_blksize_ime_native(sion_fileptr->fd);
  }
#endif
  else {
    return _lsion_errorprint(SION_SIZE_NOT_VALID, _SION_ERROR_RETURN,
      "_lsion_file_get_opt_blksize: cannot find valid file flag (flags=%d)\n", sion_fileptr->flags);
  }
}

int _lsion_file_stat_file(const char *fname, unsigned int apiflag)
{
  if ((apiflag & SION_FILE_FLAG_ANSI) || (apiflag & SION_FILE_FLAG_POSIX)) {
    struct stat sbuf;
    return 0 == stat(fname, &sbuf);
  }
#if defined(_SION_SIONFWD)
  else if (apiflag & SION_FILE_FLAG_SIONFWD) {
    return sionfwd_stat_file(fname);
  }
#endif
#ifdef _SION_IME_NATIVE
  else if (apiflag & SION_FILE_FLAG_IME_NATIVE) {
    struct stat sbuf;
    return 0 == ime_native_stat(fname, &sbuf);
  }
#endif
  else {
    return _lsion_errorprint(0, _SION_ERROR_RETURN, "_lsion_file_stat_file: cannot find valid api flag (flags=%d)\n", apiflag);
  }
}

int64_t _lsion_file_set_position(_sion_fileptr *sion_fileptr, int64_t startpointer)
{
  if (NULL == sion_fileptr) {
    return SION_SIZE_NOT_VALID;
  }

  if (sion_fileptr->flags & SION_FILE_FLAG_ANSI) {
    return _lsion_file_set_position_ansi(sion_fileptr->fileptr, startpointer);
  } else if (sion_fileptr->flags & SION_FILE_FLAG_POSIX) {
    return _lsion_file_set_position_posix(sion_fileptr->fd, startpointer);
  }
#if defined(_SION_SIONFWD)
  else if (sion_fileptr->flags & SION_FILE_FLAG_SIONFWD) {
    sion_fileptr->position = startpointer;
    return sion_fileptr->position;
  }
#endif
#ifdef _SION_IME_NATIVE
  else if (sion_fileptr->flags & SION_FILE_FLAG_IME_NATIVE) {
    return _sion_file_set_position_ime_native(sion_fileptr->fd, startpointer);
  }
#endif
  else {
    return _lsion_errorprint(SION_SIZE_NOT_VALID, _SION_ERROR_RETURN,
      "_lsion_file_set_position: cannot find valid file flag (flags=%d)\n", sion_fileptr->flags);
  }
}

int64_t _lsion_file_get_position(_sion_fileptr *sion_fileptr)
{
  if (NULL == sion_fileptr) {
    return SION_SIZE_NOT_VALID;
  }

  if (sion_fileptr->flags & SION_FILE_FLAG_ANSI) {
    return _lsion_file_get_position_ansi(sion_fileptr->fileptr);
  } else if (sion_fileptr->flags & SION_FILE_FLAG_POSIX) {
    return _lsion_file_get_position_posix(sion_fileptr->fd);
  }
#if defined(_SION_SIONFWD)
  else if (sion_fileptr->flags & SION_FILE_FLAG_SIONFWD) {
    return sion_fileptr->position;
  }
#endif
#ifdef _SION_IME_NATIVE
  else if (sion_fileptr->flags & SION_FILE_FLAG_IME_NATIVE) {
    return _sion_file_get_position_ime_native(sion_fileptr->fd);
  }
#endif
  else {
    return _lsion_errorprint(SION_SIZE_NOT_VALID, _SION_ERROR_RETURN,
      "_lsion_file_get_position: cannot find valid file flag (flags=%d)\n", sion_fileptr->flags);
  }
}

int _lsion_file_flush(_sion_fileptr *sion_fileptr)
{
  if (NULL == sion_fileptr) {
    return SION_NOT_SUCCESS;
  }

  if (sion_fileptr->flags & SION_FILE_FLAG_ANSI) {
    return _lsion_file_flush_ansi(sion_fileptr->fileptr);
  } else if (sion_fileptr->flags & SION_FILE_FLAG_POSIX) {
    return _lsion_file_flush_posix(sion_fileptr->fd);
  }
#if defined(_SION_SIONFWD)
  else if (sion_fileptr->flags & SION_FILE_FLAG_SIONFWD) {
    return _sion_file_flush_sionfwd(sion_fileptr->fd);
  }
#endif
#ifdef _SION_IME_NATIVE
  else if (sion_fileptr->flags & SION_FILE_FLAG_IME_NATIVE) {
    return _sion_file_flush_ime_native(sion_fileptr->fd);
  }
#endif
  else {
    return _lsion_errorprint(
      SION_NOT_SUCCESS, _SION_ERROR_RETURN, "_lsion_file_flush: cannot find valid file flag (flags=%d)\n", sion_fileptr->flags);
  }
}

int _lsion_file_set_buffer(_sion_fileptr *sion_fileptr, char *buffer, int32_t buffer_size)
{
  if (sion_fileptr == NULL) {
    return SION_NOT_SUCCESS;
  }

  if (sion_fileptr->flags & SION_FILE_FLAG_ANSI) {
    return _lsion_file_set_buffer_ansi(sion_fileptr->fileptr, buffer, buffer_size);
  } else if (sion_fileptr->flags & SION_FILE_FLAG_POSIX) {
    return _lsion_file_set_buffer_posix(sion_fileptr->fd, buffer, buffer_size);
  }
#if defined(_SION_SIONFWD)
  else if (sion_fileptr->flags & SION_FILE_FLAG_SIONFWD) {
    return _sion_file_set_buffer_sionfwd(sion_fileptr->fd, buffer, buffer_size);
  }
#endif
#ifdef _SION_IME_NATIVE
  else if (sion_fileptr->flags & SION_FILE_FLAG_IME_NATIVE) {
    return _sion_file_set_buffer_ime_native(sion_fileptr->fd, buffer, buffer_size);
  }
#endif
  else {
    return _lsion_errorprint(SION_NOT_SUCCESS, _SION_ERROR_RETURN,
      "_lsion_file_set_buffer: cannot find valid file flag (flags=%d)\n", sion_fileptr->flags);
  }
}

int _lsion_file_get_fd(_sion_fileptr *sion_fileptr)
{
  if (sion_fileptr == NULL) {
    return SION_ID_UNDEF;
  }

  if (sion_fileptr->flags & SION_FILE_FLAG_POSIX) {
    return sion_fileptr->fd;
  } else if (sion_fileptr->flags & SION_FILE_FLAG_ANSI) {
    return fileno(sion_fileptr->fileptr);
  }
#if defined(_SION_SIONFWD)
  else if (sion_fileptr->flags & SION_FILE_FLAG_SIONFWD) {
    return SION_ID_UNDEF;
  }
#endif
#ifdef _SION_IME_NATIVE
  else if (sion_fileptr->flags & SION_FILE_FLAG_IME_NATIVE) {
    return sion_fileptr->fd;
  }
#endif
  else {
    return _lsion_errorprint(
      SION_ID_UNDEF, _SION_ERROR_RETURN, "_lsion_file_get_fd: cannot find valid file flag (flags=%d)\n", sion_fileptr->flags);
  }
}

/* ********************************************************************************************** */
/* *** INTERFACE Functions                                                                        */
/* ********************************************************************************************** */

FILE *_lsion_file_open_ansi_write_create(const char *fname, unsigned int addflags)
{
  return fopen(fname, "w");
}

FILE *_lsion_file_open_ansi_write_existing(const char *fname, unsigned int addflags)
{
  /* changed from w to r+ to prevent time truncation of empty existing file */
  return fopen(fname, "r+");
}

FILE *_lsion_file_open_ansi_read(const char *fname, unsigned int addflags)
{
  return fopen(fname, "r");
}

int _lsion_file_close_ansi(FILE *fileptr)
{
  return (0 == fclose(fileptr)) ? SION_SUCCESS : SION_NOT_SUCCESS;
}

long _lsion_file_get_opt_blksize_ansi(FILE *fileptr)
{
  int fd = fileno(fileptr);
  struct stat sbuf;
  if (fstat(fd, &sbuf) == 0) {
    return sbuf.st_blksize;
  } else {
    return SION_SIZE_NOT_VALID;
  }
}

int64_t _lsion_file_set_position_ansi(FILE *fileptr, int64_t startpointer)
{
  int64_t newpos;

  DPRINTFP((32, "_sion_set_position_ansi", -1, "enter (to %lld) (to %ld)\n", (long long)startpointer, (long)startpointer));

  off_t offset = (off_t)startpointer;
  if (offset != startpointer) {
    DPRINTFP((32, "_sion_set_position_ansi", -1,
      "_sion_set_position_ansi: cannot set position to %ld (%zu),%lld (%zu)  offset conversion error\n", offset, sizeof(offset),
      startpointer, sizeof(startpointer)));
    return _lsion_errorprint(SION_SIZE_NOT_VALID, _SION_ERROR_RETURN,
      "_sion_set_position_ansi: cannot set position to %ld (%zu),%lld (%zu)  offset conversion error\n", offset, sizeof(offset),
      startpointer, sizeof(startpointer));
  }
  off_t result;
  newpos = result = fseeko(fileptr, offset, SEEK_SET);
  DPRINTFP((1024, "_sion_set_position_ansi", -1, "set position=%lld  (LINUX)\n", (long long)result));
  DPRINTFP((32, "_sion_set_position_ansi", -1, "leave (%lld --> %lld)\n", (long long)startpointer, (long long)newpos));

  return newpos;
}

int64_t _lsion_file_get_position_ansi(FILE *fileptr)
{
  int64_t result;

  off_t resulto;
  resulto = ftello(fileptr);
  result = (int64_t)resulto;
  DPRINTFP((4096, "_sion_get_position", -1, "get position=%ld  (LINUX)\n", (long)result));

  return result;
}

int _lsion_file_flush_ansi(FILE *fileptr)
{
  return (0 == fflush(fileptr)) ? SION_SUCCESS : SION_NOT_SUCCESS;
}

int _lsion_file_set_buffer_ansi(FILE *fileptr, char *buffer, int32_t buffer_size)
{
  DPRINTFP((32, "_lsion_file_set_buffer", -1, "set buffer of fileptr\n"));
  return (0 == setvbuf(fileptr, buffer, _IOFBF, (size_t)buffer_size)) ? SION_SUCCESS : SION_NOT_SUCCESS;
}

int64_t _lsion_file_write_ansi(const void *data, int64_t bytes, FILE *fileptr)
{
  return (bytes == fwrite(data, 1, bytes, fileptr)) ? bytes : -1;
}

int64_t _lsion_file_read_ansi(void *data, int64_t bytes, FILE *fileptr)
{
  if (fread(data, 1, bytes, fileptr) < bytes) {
    if (feof(fileptr)) {
      return bytes;
    } else {
      return -1;
    }
  } else {
    return bytes;
  }
}

int _lsion_file_open_posix_write_create(const char *fname, unsigned int addflags)
{
  int fd;

  do {
    fd = open(fname, O_CREAT | O_RDWR, 0664);
  } while (-1 == fd && EINTR == errno);

  return fd;
}

int _lsion_file_open_posix_write_existing(const char *fname, unsigned int addflags)
{
  int fd;

  do {
    fd = open(fname, O_RDWR, 0664);
  } while (-1 == fd && EINTR == errno);

  return fd;
}

int _lsion_file_open_posix_read(const char *fname, unsigned int addflags)
{
  int fd;

  do {
    fd = open(fname, O_RDONLY, 0664);
  } while (-1 == fd && EINTR == errno);

  return fd;
}

int _lsion_file_close_posix(int fd)
{
  return (0 == close(fd)) ? SION_SUCCESS : SION_NOT_SUCCESS;
}

long _lsion_file_get_opt_blksize_posix(int fd)
{
  struct stat sbuf;
  if (fstat(fd, &sbuf) == 0) {
    return sbuf.st_blksize;
  } else {
    return SION_SIZE_NOT_VALID;
  }
}

int64_t _lsion_file_set_position_posix(int fd, int64_t startpointer)
{
  int64_t newpos = SION_SIZE_NOT_VALID;

  DPRINTFP((32, "_sion_set_position_posix", -1, "enter (to %lld)\n", (long long)startpointer));

  off_t offset = (off_t)startpointer;
  if (offset != startpointer) {
    return _lsion_errorprint(SION_SIZE_NOT_VALID, _SION_ERROR_RETURN,
      "_sion_set_position_posix: cannot set position to %ld (%zu),%lld (%zu)  offset conversion error\n", offset, sizeof(offset),
      startpointer, sizeof(startpointer));
  }
  off_t result;
  newpos = result = lseek(fd, offset, SEEK_SET);
  DPRINTFP((4096, "_sion_set_position_posix", -1, "set position=%lld  (LINUX)\n", (long long)result));
  DPRINTFP((32, "_sion_set_position_posix", -1, "leave (to %lld)\n", (long long)startpointer));

  return newpos;
}

int64_t _lsion_file_get_position_posix(int fd)
{
  int64_t result = SION_SIZE_NOT_VALID;

  off_t resulto;
  resulto = lseek(fd, 0, SEEK_CUR);
  result = (int64_t)resulto;

  DPRINTFP((4096, "_sion_get_position", -1, "get position=%ld  (LINUX)\n", (long)result));

  return result;
}

int _lsion_file_flush_posix(int fd)
{
  int frc;

  do {
    frc = fsync(fd);
  } while (frc != 0 && EINTR == errno);

  return (0 == frc) ? SION_SUCCESS : SION_NOT_SUCCESS;
}

int _lsion_file_set_buffer_posix(int fd, char *buffer, int32_t buffer_size)
{
  DPRINTFP((32, "_lsion_file_set_buffer", -1, "set buffer of fileptr\n"));
  return SION_SUCCESS;
}

int64_t _lsion_file_write_posix(const void *data, int64_t bytes, int fd)
{
  ssize_t n = 0, k;
  while (1) {
    k = write(fd, data, bytes);
    if (k == -1) {
      if (errno != EINTR) {
        // I/O error, return -1
        return -1;
      } // else, interrupted before starting, retry
    } else if (k == bytes) {
      // requested amount has been written
      return n + k;
    } else {
      // k < bytes, presumably interrupted or no space left, retry
      // - retry after interruption should continue
      // - retry with no space left should lead to error
      bytes -= k;
      n += k;
      data = (char *)data + k;
    }
  }
}

int64_t _lsion_file_read_posix(void *data, int64_t bytes, int fd)
{
  ssize_t n = 0, k;
  while (1) {
    k = read(fd, data, bytes);
    if (k == -1) {
      if (errno != EINTR) {
        // I/O error, return -1
        return -1;
      } // else, interrupted before starting, retry
    } else if (k == 0) {
      // presumably EOF, return number of bytes read up to here
      return n;
    } else if (k == bytes) {
      // requested amount has been read
      return n + k;
    } else {
      // k < bytes, presumably interrupted or EOF, retry
      // - retry after interruption should continue
      // - retry at EOF should lead to k == 0 next
      bytes -= k;
      n += k;
      data = (char *)data + k;
    }
  }
}

#if defined(_SION_SIONFWD)
/*!\brief SIONfwd: Create and open a new file for writing
 *
 * @param  *fname               filename to use
 * @param  addflags             optional additional flags
 *
 * @return file handle or `-1` on failure
 */
int _sion_file_open_sionfwd_write_create(const char *fname, unsigned int addflags)
{
  return sionfwd_open(fname, SIONFWD_WRITE | SIONFWD_CREATE);
}

/*!\brief SIONfwd: Open a new file for writing
 *
 * @param  *fname               filename to use
 * @param  addflags             optional additional flags
 *
 * @return file handle or `-1` on failure
 */
int _sion_file_open_sionfwd_write_existing(const char *fname, unsigned int addflags)
{
  return sionfwd_open(fname, SIONFWD_WRITE);
}

/*!\brief SIONfwd: Open a file for reading
 *
 * @param  *fname               filename to use
 * @param  addflags             optional additional flags
 *
 * @return file handle or `-1` on failure
 */
int _sion_file_open_sionfwd_read(const char *fname, unsigned int addflags)
{
  return sionfwd_open(fname, SIONFWD_READ);
}

/*!\brief SIONfwd: Close a file
 *
 * @param  fd                   POSIX file pointer
 *
 * @return `SION_SUCCESS` or `SION_NOT_SUCCESS`
 */
int _sion_file_close_sionfwd(int fd)
{
  return (0 == sionfwd_close(fd)) ? SION_SUCCESS : SION_NOT_SUCCESS;
}

/*!\brief SIONfwd: Get optional file system block size for a file
 *
 * @param  fd  file descriptor
 *
 * @return  blocksize or `-1` if not defined
 */
long _sion_file_get_opt_blksize_sionfwd(int fd)
{
  return sionfwd_stat_blksize(fd);
}

/*!\brief SIONfwd: Flush the data to the disk
 *
 * @param  fd  file descriptor
 *
 * @retval `SION_SUCCESS` if OK
 */
int _sion_file_flush_sionfwd(int fd)
{
  return (sionfwd_flush(fd) == 0) ? SION_SUCCESS : SION_NOT_SUCCESS;
}

/*!\brief SIONfwd: Purge the data to the disk
 *
 * Purge is only relevant for ANSI, using fsync instead
 *
 * @param  fd  file descriptor
 *
 * @retval `SION_SUCCESS` if OK
 */
int _sion_file_purge_sionfwd(int fd)
{
  return (sionfwd_flush(fd) == 0) ? SION_SUCCESS : SION_NOT_SUCCESS;
}

/*!\brief SIONfwd: set buffer of fd
 *
 * @param  fd                   file descriptor
 * @param  *buffer              pointer buffer
 * @param  *buffer_size         buffer size
 *
 * @retval `SION_SUCCESS` if OK
 */
int _sion_file_set_buffer_sionfwd(int fd, char *buffer, int32_t buffer_size)
{
  DPRINTFP((32, "_lsion_file_set_buffer", -1, "set buffer of fileptr\n"));
  return SION_SUCCESS;
}

/*!\brief SIONfwd: Write data to file
 *
 * @param  data                 pointer to data
 * @param  bytes                number of bytes to write
 * @param  fd                   file handle
 * @param  position             current position in the file, will be updated
 *
 * @return returns `bytes` if the requested amount could be written, otherwise `-1`
 */
int64_t _sion_file_write_sionfwd(const void *data, int64_t bytes, int fd, int64_t *position)
{
  int64_t written = sionfwd_pwrite(fd, data, bytes, *position);
  if (written != -1) {
    *position += written;
  }
  return written;
}

/*!\brief SIONfwd: Read data from file
 *
 * @param  data                 pointer to data
 * @param  bytes                number of bytes to read
 * @param  fd                   file handle
 * @param  position             current position in the file, will be updated
 *
 * @return returns `bytes` if the requested amount could be read,
 *         on EOF, returns `n` with `0 <= n < bytes`,
 *         otherwise `-1`
 */
int64_t _sion_file_read_sionfwd(void *data, int64_t bytes, int fd, int64_t *position)
{
  int64_t bread = sionfwd_pread(fd, data, bytes, *position);
  if (bread != -1) {
    *position += bread;
  }
  return bread;
}
#endif

/* ********************************************************************************************** */
/* *** IME NATIVE INTERFACE Functions                                                             */
/* ********************************************************************************************** */

#ifdef _SION_IME_NATIVE
int _sion_file_open_ime_native_write_create(const char *fname, unsigned int addflags)
{
  int fd;

  do {
    fd = ime_native_open(fname, O_CREAT | O_RDWR, 0664);
  } while (-1 == fd && EINTR == errno);

  return fd;
}

int _sion_file_open_ime_native_write_existing(const char *fname, unsigned int addflags)
{
  int fd;

  do {
    fd = ime_native_open(fname, O_RDWR, 0664);
  } while (-1 == fd && EINTR == errno);

  return fd;
}

int _sion_file_open_ime_native_read(const char *fname, unsigned int addflags)
{
  int fd;

  do {
    fd = ime_native_open(fname, O_RDONLY, 0664);
  } while (-1 == fd && EINTR == errno);

  return fd;
}

int _sion_file_close_ime_native(int fd)
{
  return (0 == ime_native_close(fd)) ? SION_SUCCESS : SION_NOT_SUCCESS;
}

long _sion_file_get_opt_blksize_ime_native(int fd)
{
  return SION_SIZE_NOT_VALID;
}

int64_t _sion_file_set_position_ime_native(int fd, int64_t startpointer)
{
  int64_t newpos = SION_SIZE_NOT_VALID;

  DPRINTFP((32, "_sion_set_position_ime_native", -1, "enter (to %lld)\n", (long long)startpointer));

  off_t offset = (off_t)startpointer;
  if (offset != startpointer) {
    return _lsion_errorprint(SION_SIZE_NOT_VALID, _SION_ERROR_RETURN,
      "_sion_set_position_ime_native: cannot set position to %ld (%zu),%lld (%zu)  offset conversion error\n", offset,
      sizeof(offset), startpointer, sizeof(startpointer));
  }
  off_t result;
  newpos = result = ime_native_lseek(fd, offset, SEEK_SET);

  DPRINTFP((32, "_sion_set_position_ime_native", -1, "leave (to %lld)\n", (long long)startpointer));

  return newpos;
}

int64_t _sion_file_get_position_ime_native(int fd)
{
  int64_t result = SION_SIZE_NOT_VALID;

  off_t resulto;
  resulto = ime_native_lseek(fd, 0, SEEK_CUR);
  result = (int64_t)resulto;

  DPRINTFP((4096, "_sion_get_position", -1, "get position=%ld  (LINUX)\n", (long)result));

  return result;
}

int _sion_file_flush_ime_native(int fd)
{
  int frc = 0;

  do {
    frc = ime_native_fsync(fd);
  } while (frc != 0 && EINTR == errno);

  return (0 == frc) ? SION_SUCCESS : SION_NOT_SUCCESS;
}

int _sion_file_set_buffer_ime_native(int fd, char *buffer, int32_t buffer_size)
{
  /* FIXME we can't really do something here. */
  DPRINTFP((32, "_lsion_file_set_buffer", -1, "set buffer of fileptr\n"));
  return SION_SUCCESS;
}

int64_t _sion_file_write_ime_native(const void *data, int64_t bytes, int fd)
{
  ssize_t n = 0, k;
  while (1) {
    k = ime_native_write(fd, data, bytes);
    if (k == -1) {
      if (errno != EINTR) {
        // I/O error, return -1
        return -1;
      } // else, interrupted before starting, retry
    } else if (k == bytes) {
      // requested amount has been written
      return n + k;
    } else {
      // k < bytes, presumably interrupted or no space left, retry
      // - retry after interruption should continue
      // - retry with no space left should lead to error
      bytes -= k;
      n += k;
      data = (char *)data + k;
    }
  }
}

int64_t _sion_file_read_ime_native(void *data, int64_t bytes, int fd)
{
  ssize_t n = 0, k;
  while (1) {
    k = ime_native_read(fd, data, bytes);
    if (k == -1) {
      if (errno != EINTR) {
        // I/O error, return -1
        return -1;
      } // else, interrupted before starting, retry
    } else if (k == 0) {
      // presumably EOF, return number of bytes read up to here
      return n;
    } else if (k == bytes) {
      // requested amount has been read
      return n + k;
    } else {
      // k < bytes, presumably interrupted or EOF, retry
      // - retry after interruption should continue
      // - retry at EOF should lead to k == 0 next
      bytes -= k;
      n += k;
      data = (char *)data + k;
    }
  }
}
#endif

/*!\brief Create and return _sion_fileptr
 *
 * @return _sion_fileptr
 */
_sion_fileptr *_lsion_file_alloc_and_init_sion_fileptr(void)
{
  _sion_fileptr *sion_fileptr;

  sion_fileptr = malloc(sizeof(_sion_fileptr));
  if (sion_fileptr == NULL) {
    _lsion_errorprint(SION_NOT_SUCCESS, _SION_ERROR_RETURN,
      "cannot allocate _sion_fileptr structure of size %lu (_sion_fileptr), aborting ...\n",
      (unsigned long)sizeof(_sion_fileptr));
    return NULL;
  }
  sion_fileptr->fileptr = NULL;
  sion_fileptr->fd = -1;
  sion_fileptr->flags = 0;
#if defined(_SION_SIONFWD)
  sion_fileptr->position = 0;
#endif

  return sion_fileptr;
}

/*!
 * @return File pointer description
 */
char *_lsion_get_fileptr_desc(_sion_fileptr *sion_fileptr)
{
  int flags;
  if (!sion_fileptr) {
    return "<undefined>";
  }
  flags = sion_fileptr->flags;

  if (flags & SION_FILE_FLAG_ANSI) {
    if (flags & SION_FILE_FLAG_WRITE) {
      if (flags & SION_FILE_FLAG_CREATE) {
        return "<ANSI,WRITE,CREATE>";
      } else {
        return "<ANSI,WRITE>";
      }
    } else {
      return "<ANSI,READ>";
    }
  } else if (flags & SION_FILE_FLAG_POSIX) {
    if (flags & SION_FILE_FLAG_WRITE) {
      if (flags & SION_FILE_FLAG_CREATE) {
        return "<POSIX,WRITE,CREATE>";
      } else {
        return "<POSIX,WRITE>";
      }
    } else {
      return "<POSIX,READ>";
    }
  }
#if defined(_SION_SIONFWD)
  else if (flags & SION_FILE_FLAG_SIONFWD) {
    if (flags & SION_FILE_FLAG_WRITE) {
      if (flags & SION_FILE_FLAG_CREATE) {
        return ("<SIONFWD,WRITE,CREATE>");
      } else {
        return ("<SIONFWD,WRITE>");
      }
    } else {
      return ("<SIONFWD,READ>");
    }
  }
#endif
#ifdef _SION_IME_NATIVE
  else {
    if (flags & SION_FILE_FLAG_IME_NATIVE) {
      if (flags & SION_FILE_FLAG_WRITE) {
        if (flags & SION_FILE_FLAG_CREATE) {
          return "<IME_NATIVE,WRITE,CREATE>";
        } else {
          return "<IME_NATIVE,WRITE>";
        }
      } else {
        return "<IME_NATIVE,READ>";
      }
    }
  }
#endif
  return "<unknown>";
}
