/* C++ modules.  Experimental!	-*- c++ -*-
   Copyright (C) 2017-2023 Free Software Foundation, Inc.
   Written by Nathan Sidwell <nathan@acm.org> while at FaceBook

   This file is part of GCC.

   GCC 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.

   GCC 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 GCC; see the file COPYING3.  If not see
<http://www.gnu.org/licenses/>.  */

#include "config.h"

#include "resolver.h"
// C++
#include <algorithm>
#include <memory>
// C
#include <cstring>
// OS
#include <fcntl.h>
#include <unistd.h>
#if 0 // 1 for testing no mmap
#define MAPPED_READING 0
#else
#ifdef IN_GCC
#if HAVE_MMAP_FILE && _POSIX_MAPPED_FILES > 0
#define MAPPED_READING 1
#else
#define MAPPED_READING 0
#endif
#else
#ifdef HAVE_SYS_MMAN_H
#include <sys/mman.h>
#define MAPPED_READING 1
#else
#define MAPPED_READING 0
#endif
#endif
#endif

#include <sys/types.h>
#include <sys/stat.h>

#if !defined (IN_GCC) && !MAPPED_READING
#define xmalloc(X) malloc(X)
#endif

#if !HOST_HAS_O_CLOEXEC
#define O_CLOEXEC 0
#endif

#ifndef DIR_SEPARATOR
#define DIR_SEPARATOR '/'
#endif

module_resolver::module_resolver (bool map, bool xlate)
  : default_map (map), default_translate (xlate)
{
}

module_resolver::~module_resolver ()
{
  if (fd_repo >= 0)
    close (fd_repo);
}

bool
module_resolver::set_repo (std::string &&r, bool force)
{
  if (force || repo.empty ())
    {
      repo = std::move (r);
      force = true;
    }
  return force;
}

bool
module_resolver::add_mapping (std::string &&module, std::string &&file,
			      bool force)
{
  auto res = map.emplace (std::move (module), std::move (file));
  if (res.second)
    force = true;
  else if (force)
    res.first->second = std::move (file);

  return force;
}

int
module_resolver::read_tuple_file (int fd, char const *prefix, bool force)
{
  struct stat stat;
  if (fstat (fd, &stat) < 0)
    return -errno;

  if (!stat.st_size)
    return 0;

  void *buffer = nullptr;
#if MAPPED_READING
  // Just map the file, we're gonna read all of it, so no need for
  // line buffering
  buffer = mmap (nullptr, stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
  if (buffer == MAP_FAILED)
    return -errno;
  struct Deleter {
    void operator()(void* p) const { munmap(p, size); }
    size_t size;
  };
  std::unique_ptr<void, Deleter> guard(buffer, Deleter{(size_t)stat.st_size});
#else
  buffer = xmalloc (stat.st_size);
  if (!buffer)
    return -errno;
  struct Deleter { void operator()(void* p) const { free(p); } };
  std::unique_ptr<void, Deleter> guard(buffer);
  if (read (fd, buffer, stat.st_size) != stat.st_size)
    return -errno;
#endif

  size_t prefix_len = prefix ? strlen (prefix) : 0;
  unsigned lineno = 0;

  for (char const *begin = reinterpret_cast <char const *> (buffer),
	 *end = begin + stat.st_size, *eol;
       begin != end; begin = eol + 1)
    {
      lineno++;
      eol = std::find (begin, end, '\n');
      if (eol == end)
	// last line has no \n, ignore the line, you lose
	break;

      auto *pos = begin;
      bool pfx_search = prefix_len != 0;

    pfx_search:
      while (*pos == ' ' || *pos == '\t')
	pos++;

      auto *space = pos;
      while (*space != '\n' && *space != ' ' && *space != '\t')
	space++;

      if (pos == space)
	// at end of line, nothing here	
	continue;

      if (pfx_search)
	{
	  if (size_t (space - pos) == prefix_len
	      && std::equal (pos, space, prefix))
	    pfx_search = false;
	  pos = space;
	  goto pfx_search;
	}

      std::string module (pos, space);
      while (*space == ' ' || *space == '\t')
	space++;
      std::string file (space, eol);

      if (module[0] == '$')
	{
	  if (module == "$root")
	    set_repo (std::move (file));
	  else
	    return lineno;
	}
      else
	{
	  if (file.empty ())
	    file = GetCMIName (module);
	  add_mapping (std::move (module), std::move (file), force);
	}
    }

  return 0;
}

char const *
module_resolver::GetCMISuffix ()
{
  return "gcm";
}

module_resolver *
module_resolver::ConnectRequest (Cody::Server *s, unsigned version,
				 std::string &a, std::string &i)
{
  if (!version || version > Cody::Version)
    s->ErrorResponse ("version mismatch");
  else if (a != "GCC")
    // Refuse anything but GCC
    ErrorResponse (s, std::string ("only GCC supported"));
  else if (!ident.empty () && ident != i)
    // Failed ident check
    ErrorResponse (s, std::string ("bad ident"));
  else
    // Success!
    s->ConnectResponse ("gcc");

  return this;
}

int
module_resolver::ModuleRepoRequest (Cody::Server *s)
{
  s->PathnameResponse (repo);
  return 0;
}

int
module_resolver::cmi_response (Cody::Server *s, std::string &module)
{
  auto iter = map.find (module);
  if (iter == map.end ())
    {
      std::string file = default_map ? GetCMIName (module) : std::string ();
      auto res = map.emplace (module, file);
      iter = res.first;
    }

  if (iter->second.empty ())
    s->ErrorResponse ("no such module");
  else
    s->PathnameResponse (iter->second);

  return 0;
}

int
module_resolver::ModuleExportRequest (Cody::Server *s, Cody::Flags,
				      std::string &module)
{
  return cmi_response (s, module);
}

int
module_resolver::ModuleImportRequest (Cody::Server *s, Cody::Flags,
				      std::string &module)
{
  return cmi_response (s, module);
}

int
module_resolver::IncludeTranslateRequest (Cody::Server *s, Cody::Flags,
					  std::string &include)
{
  auto iter = map.find (include);
  if (iter == map.end () && default_translate)
    {
      // Not found, look for it
      auto file = GetCMIName (include);
      struct stat statbuf;
      bool ok = true;

#if HAVE_FSTATAT
      int fd_dir = AT_FDCWD;
      if (!repo.empty ())
	{
	  if (fd_repo == -1)
	    {
	      fd_repo = open (repo.c_str (),
			      O_RDONLY | O_CLOEXEC | O_DIRECTORY);
	      if (fd_repo < 0)
		fd_repo = -2;
	    }
	  fd_dir = fd_repo;
	}

      if (!repo.empty () && fd_repo < 0)
	ok = false;
      else if (fstatat (fd_dir, file.c_str (), &statbuf, 0) < 0
	       || !S_ISREG (statbuf.st_mode))
	ok = false;
#else
      auto append = repo;
      append.push_back (DIR_SEPARATOR);
      append.append (file);
      if (stat (append.c_str (), &statbuf) < 0
	  || !S_ISREG (statbuf.st_mode))
	ok = false;
#endif
      if (!ok)
	// Mark as not present
	file.clear ();
      auto res = map.emplace (include, file);
      iter = res.first;
    }

  if (iter == map.end () || iter->second.empty ())
    s->BoolResponse (false);
  else
    s->PathnameResponse (iter->second);

  return 0;
}

/* This handles a client notification to the server that a CMI has been
   produced for a module.  For this simplified server, we just accept
   the transaction and respond with "OK".  */

int
module_resolver::ModuleCompiledRequest (Cody::Server *s, Cody::Flags,
				      std::string &)
{
  s->OKResponse();
  return 0;
}