/* Copyright (C) 2021-2025 Free Software Foundation, Inc.
   Contributed by Oracle.

   This file is part of GNU Binutils.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, 51 Franklin Street - Fifth Floor, Boston,
   MA 02110-1301, USA.  */

#include "config.h"
#include <sys/types.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>     //  for close();

#include "util.h"
#include "Data_window.h"
#include "debug.h"

enum
{
  MINBUFSIZE    = 1 << 16,
  WIN_ALIGN     = 8
};

Data_window::Data_window (char *file_name)
{
  Dprintf (DEBUG_DATA_WINDOW, NTXT ("Data_window:%d %s\n"), (int) __LINE__, STR (file_name));
  page_size = sysconf (_SC_PAGESIZE);
  need_swap_endian = false;
  opened = false;
  fsize = 0;
  base = NULL;
  woffset = 0;
  wsize = 0;
  basesize = 0;
  fname = dbe_strdup (file_name);
  mmap_on_file = false;
  use_mmap = false;
#if DEBUG
  if (DBE_USE_MMAP)
    use_mmap = true;
#endif /* DEBUG */
  fd = open64 (fname, O_RDONLY);
  if (fd == -1)
    return;
  fsize = lseek (fd, 0, SEEK_END);
  if (fsize == 0)
    {
      close (fd);
      fd = -1;
      return;
    }
  opened = true;
  if (use_mmap)
    {
      if (fsize != -1)
	{
	  base = (void*) mmap (NULL, (size_t) fsize, PROT_READ, MAP_PRIVATE, fd, 0);
	  close (fd);
	  fd = -1;
	  if (base == MAP_FAILED)
	    {
	      base = NULL;
	      use_mmap = false;
	      return;
	    }
	  mmap_on_file = true;
	  wsize = fsize;
	}
    }
}

void *
Data_window::bind (int64_t file_offset, int64_t minSize)
{
  Span span;
  span.length = fsize - file_offset;
  span.offset = file_offset;
  return bind (&span, minSize);
}

void *
Data_window::bind (Span *span, int64_t minSize)
{
  // Do any necessary mapping to access the desired span of data
  // and return a pointer to the first byte.
  Dprintf (DEBUG_DATA_WINDOW, NTXT ("Data_window:bind:%d offset=%llx:%lld minSize=%lld \n"),
	   (int) __LINE__, (long long) span->offset, (long long) span->length, (long long) minSize);
  if (minSize == 0 || span->length < minSize)
    return NULL;

  if (span->offset < woffset || span->offset + minSize > woffset + wsize)
    {
      // Remap the window
      if (span->offset + minSize > fsize)
	return NULL;
      int myfd = fd;
      if (myfd == -1)
	{
	  if (fname)
	    myfd = open64 (fname, O_RDONLY, 0);
	  if (myfd == -1)
	    return NULL;
	}
      bool remap_failed = true;
      if (use_mmap)
	{
	  if (base)
	    {
	      munmap ((caddr_t) base, (size_t) wsize);
	      base = NULL;
	    }
	  woffset = span->offset & ~(page_size - 1);
	  wsize = page_size * ((MINBUFSIZE + page_size - 1) / page_size);
	  if (span->offset + minSize > woffset + wsize)
	    // Extend a window
	    wsize += page_size * ((span->offset + minSize -
				   woffset - wsize + page_size - 1) / page_size);
	  base = (void *) mmap (0, (size_t) wsize, PROT_READ, MAP_SHARED, fd, woffset);
	  if (base == MAP_FAILED)
	    {
	      base = NULL;
	      use_mmap = false;
	    }
	  remap_failed = (base == NULL);
	}
      if (remap_failed)
	{
	  remap_failed = false;
	  woffset = span->offset & ~(WIN_ALIGN - 1);
	  wsize = minSize + (span->offset % WIN_ALIGN);
	  if (wsize < MINBUFSIZE)
	    wsize = MINBUFSIZE;
	  if (wsize > fsize)
	    wsize = fsize;
	  if (basesize < wsize)
	    { // Need to realloc 'base'
	      free (base);
	      basesize = wsize;
	      base = (void *) xmalloc (basesize);
	      Dprintf (DEBUG_DATA_WINDOW,
		       NTXT ("Data_window:bind:%d realloc basesize=%llx woffset=%lld \n"),
		       (int) __LINE__, (long long) basesize, (long long) woffset);
	      if (base == NULL)
		{
		  basesize = 0;
		  remap_failed = true;
		}
	    }
	  if (wsize > fsize - woffset)
	    wsize = fsize - woffset;
	  off_t woff = (off_t) woffset;
	  if (base == NULL || woff != lseek (myfd, woff, SEEK_SET)
	      || wsize != read_from_file (myfd, base, wsize))
	    remap_failed = true;
	}
      if (fd == -1)
	close (myfd);
      if (remap_failed)
	{
	  woffset = 0;
	  wsize = 0;
	  return NULL;
	}
    }
  return (void *) ((char*) base + span->offset - woffset);
}

void *
Data_window::get_data (int64_t offset, int64_t size, void *datap)
{
  if (size <= 0)
    return NULL;
  void *buf = bind (offset, size);
  if (buf == NULL)
    return NULL;
  if (datap == NULL && !mmap_on_file)
    // Can be remmaped or reallocated. Need to make a copy
    datap = (void *) xmalloc (size);
  if (datap)
    {
      memcpy (datap, buf, (size_t) size);
      return datap;
    }
  return buf;
}

Data_window::~Data_window ()
{
  free (fname);
  if (fd != -1)
    close (fd);
  if (base)
    {
      if (use_mmap)
	munmap ((caddr_t) base, (size_t) wsize);
      else
	free (base);
    }
}

int64_t
Data_window::get_buf_size ()
{
  int64_t sz = MINBUFSIZE;
  if (sz < basesize)
    sz = basesize;
  if (sz > fsize)
    sz = fsize;
  return sz;
}

int64_t
Data_window::copy_to_file (int f, int64_t offset, int64_t size)
{
  long long bsz = get_buf_size ();
  for (long long n = 0; n < size;)
    {
      long long sz = (bsz <= (size - n)) ? bsz : (size - n);
      void *b = bind (offset + n, sz);
      if (b == NULL)
	return n;
      long long len = write (f, b, sz);
      if (len <= 0)
	return n;
      n += len;
    }
  return size;
}
