/* Caching of GDB/DWARF index files.

   Copyright (C) 1994-2024 Free Software Foundation, Inc.

   This file is part of GDB.

   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 of the License, 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, see <http://www.gnu.org/licenses/>.  */

#include "dwarf2/index-cache.h"

#include "build-id.h"
#include "cli/cli-cmds.h"
#include "cli/cli-decode.h"
#include "command.h"
#include "gdbsupport/scoped_mmap.h"
#include "gdbsupport/pathstuff.h"
#include "dwarf2/index-write.h"
#include "dwarf2/read.h"
#include "dwarf2/dwz.h"
#include "objfiles.h"
#include "gdbsupport/selftest.h"
#include <string>
#include <stdlib.h>
#include "run-on-main-thread.h"

/* When set to true, show debug messages about the index cache.  */
static bool debug_index_cache = false;

#define index_cache_debug(FMT, ...)					       \
  debug_prefixed_printf_cond_nofunc (debug_index_cache, "index-cache", \
				     FMT, ## __VA_ARGS__)

/* The index cache directory, used for "set/show index-cache directory".  */
static std::string index_cache_directory;

/* See dwarf-index.cache.h.  */
index_cache global_index_cache;

/* set/show index-cache commands.  */
static cmd_list_element *set_index_cache_prefix_list;
static cmd_list_element *show_index_cache_prefix_list;

/* Default destructor of index_cache_resource.  */
index_cache_resource::~index_cache_resource () = default;

/* See dwarf-index-cache.h.  */

void
index_cache::set_directory (std::string dir)
{
  gdb_assert (!dir.empty ());

  m_dir = std::move (dir);

  index_cache_debug ("now using directory %s", m_dir.c_str ());
}

/* See dwarf-index-cache.h.  */

void
index_cache::enable ()
{
  index_cache_debug ("enabling (%s)", m_dir.c_str ());

  m_enabled = true;
}

/* See dwarf-index-cache.h.  */

void
index_cache::disable ()
{
  index_cache_debug ("disabling");

  m_enabled = false;
}

 /* See index-cache.h.  */

index_cache_store_context::index_cache_store_context (const index_cache &ic,
						      dwarf2_per_bfd *per_bfd)
  :  m_enabled (ic.enabled ()),
     m_dir (ic.m_dir),
     m_per_bfd (per_bfd)
{
  /* Capturing globals may only be done on the main thread.  */
  gdb_assert (is_main_thread ());

  if (!m_enabled)
    return;

  /* Get build id of objfile.  */
  const bfd_build_id *build_id = build_id_bfd_get (per_bfd->obfd);
  if (build_id == nullptr)
    {
      index_cache_debug ("objfile %s has no build id",
			 bfd_get_filename (per_bfd->obfd));
      m_enabled = false;
      return;
    }
  m_build_id_str = build_id_to_string (build_id);

  /* Get build id of dwz file, if present.  */
  const dwz_file *dwz = dwarf2_get_dwz_file (per_bfd);

  if (dwz != nullptr)
    {
      const bfd_build_id *dwz_build_id = build_id_bfd_get (dwz->dwz_bfd.get ());

      if (dwz_build_id == nullptr)
	{
	  index_cache_debug ("dwz objfile %s has no build id",
			     dwz->filename ());
	  m_enabled = false;
	  return;
	}

      m_dwz_build_id_str = build_id_to_string (dwz_build_id);
    }

  if (m_dir.empty ())
    {
      warning (_("The index cache directory name is empty, skipping store."));
      m_enabled = false;
      return;
    }

  try
    {
      /* Try to create the containing directory.  */
      if (!mkdir_recursive (m_dir.c_str ()))
	{
	  warning (_("index cache: could not make cache directory: %s"),
		   safe_strerror (errno));
	  m_enabled = false;
	  return;
	}
    }
  catch (const gdb_exception_error &except)
    {
      index_cache_debug ("couldn't store index cache for objfile %s: %s",
			 bfd_get_filename (per_bfd->obfd), except.what ());
      m_enabled = false;
    }
}

/* See dwarf-index-cache.h.  */

void
index_cache_store_context::store () const
{
  if (!m_enabled)
    return;

  const char *dwz_build_id_ptr = (m_dwz_build_id_str.has_value ()
				  ? m_dwz_build_id_str->c_str ()
				  : nullptr);

  try
    {
      index_cache_debug ("writing index cache for objfile %s",
			 bfd_get_filename (m_per_bfd->obfd));

      /* Write the index itself to the directory, using the build id as the
	 filename.  */
      write_dwarf_index (m_per_bfd, m_dir.c_str (),
			 m_build_id_str.c_str (), dwz_build_id_ptr,
			 dw_index_kind::GDB_INDEX);
    }
  catch (const gdb_exception_error &except)
    {
      index_cache_debug ("couldn't store index cache for objfile %s: %s",
			 bfd_get_filename (m_per_bfd->obfd), except.what ());
    }
}

#if HAVE_SYS_MMAN_H

/* Hold the resources for an mmapped index file.  */

struct index_cache_resource_mmap final : public index_cache_resource
{
  /* Try to mmap FILENAME.  Throw an exception on failure, including if the
     file doesn't exist. */
  index_cache_resource_mmap (const char *filename)
    : mapping (mmap_file (filename))
  {}

  scoped_mmap mapping;
};

/* See dwarf-index-cache.h.  */

gdb::array_view<const gdb_byte>
index_cache::lookup_gdb_index (const bfd_build_id *build_id,
			       std::unique_ptr<index_cache_resource> *resource)
{
  if (!enabled ())
    return {};

  if (m_dir.empty ())
    {
      warning (_("The index cache directory name is empty, skipping cache "
		 "lookup."));
      return {};
    }

  /* Compute where we would expect a gdb index file for this build id to be.  */
  std::string filename = make_index_filename (build_id, INDEX4_SUFFIX);

  try
    {
      index_cache_debug ("trying to read %s",
			 filename.c_str ());

      /* Try to map that file.  */
      index_cache_resource_mmap *mmap_resource
	= new index_cache_resource_mmap (filename.c_str ());

      /* Yay, it worked!  Hand the resource to the caller.  */
      resource->reset (mmap_resource);

      return gdb::array_view<const gdb_byte>
	  ((const gdb_byte *) mmap_resource->mapping.get (),
	   mmap_resource->mapping.size ());
    }
  catch (const gdb_exception_error &except)
    {
      index_cache_debug ("couldn't read %s: %s",
			 filename.c_str (), except.what ());
    }

  return {};
}

#else /* !HAVE_SYS_MMAN_H */

/* See dwarf-index-cache.h.  This is a no-op on unsupported systems.  */

gdb::array_view<const gdb_byte>
index_cache::lookup_gdb_index (const bfd_build_id *build_id,
			       std::unique_ptr<index_cache_resource> *resource)
{
  return {};
}

#endif

/* See dwarf-index-cache.h.  */

std::string
index_cache::make_index_filename (const bfd_build_id *build_id,
				  const char *suffix) const
{
  std::string build_id_str = build_id_to_string (build_id);

  return m_dir + SLASH_STRING + build_id_str + suffix;
}

/* True when we are executing "show index-cache".  This is used to improve the
   printout a little bit.  */
static bool in_show_index_cache_command = false;

/* "show index-cache" handler.  */

static void
show_index_cache_command (const char *arg, int from_tty)
{
  /* Note that we are executing "show index-cache".  */
  auto restore_flag = make_scoped_restore (&in_show_index_cache_command, true);

  /* Call all "show index-cache" subcommands.  */
  cmd_show_list (show_index_cache_prefix_list, from_tty);

  gdb_printf ("\n");
  gdb_printf
    (_("The index cache is currently %s.\n"),
     global_index_cache.enabled () ? _("enabled") : _("disabled"));
}

/* "set/show index-cache enabled" set callback.  */

static void
set_index_cache_enabled_command (bool value)
{
  if (value)
    global_index_cache.enable ();
  else
    global_index_cache.disable ();
}

/* "set/show index-cache enabled" get callback.  */

static bool
get_index_cache_enabled_command ()
{
  return global_index_cache.enabled ();
}

/* "set/show index-cache enabled" show callback.  */

static void
show_index_cache_enabled_command (ui_file *stream, int from_tty,
				  cmd_list_element *cmd, const char *value)
{
  gdb_printf (stream, _("The index cache is %s.\n"), value);
}

/* "set index-cache directory" handler.  */

static void
set_index_cache_directory_command (const char *arg, int from_tty,
				   cmd_list_element *element)
{
  /* Make sure the index cache directory is absolute and tilde-expanded.  */
  index_cache_directory = gdb_abspath (index_cache_directory);
  global_index_cache.set_directory (index_cache_directory);
}

/* "show index-cache stats" handler.  */

static void
show_index_cache_stats_command (const char *arg, int from_tty)
{
  const char *indent = "";

  /* If this command is invoked through "show index-cache", make the display a
     bit nicer.  */
  if (in_show_index_cache_command)
    {
      indent = "  ";
      gdb_printf ("\n");
    }

  gdb_printf (_("%s  Cache hits (this session): %u\n"),
	      indent, global_index_cache.n_hits ());
  gdb_printf (_("%sCache misses (this session): %u\n"),
	      indent, global_index_cache.n_misses ());
}

void _initialize_index_cache ();
void
_initialize_index_cache ()
{
  /* Set the default index cache directory.  */
  std::string cache_dir = get_standard_cache_dir ();
  if (!cache_dir.empty ())
    {
      index_cache_directory = cache_dir;
      global_index_cache.set_directory (std::move (cache_dir));
    }
  else
    warning (_("Couldn't determine a path for the index cache directory."));

  /* set index-cache */
  add_basic_prefix_cmd ("index-cache", class_files,
			_("Set index-cache options."),
			&set_index_cache_prefix_list,
			false, &setlist);

  /* show index-cache */
  add_prefix_cmd ("index-cache", class_files, show_index_cache_command,
		  _("Show index-cache options."), &show_index_cache_prefix_list,
		  false, &showlist);

  /* set/show index-cache enabled */
  set_show_commands setshow_index_cache_enabled_cmds
    = add_setshow_boolean_cmd ("enabled", class_files,
			       _("Enable the index cache."),
			       _("Show whether the index cache is enabled."),
			       _("When on, enable the use of the index cache."),
			       set_index_cache_enabled_command,
			       get_index_cache_enabled_command,
			       show_index_cache_enabled_command,
			       &set_index_cache_prefix_list,
			       &show_index_cache_prefix_list);

  /* set index-cache on */
  cmd_list_element *set_index_cache_on_cmd
    = add_alias_cmd ("on", setshow_index_cache_enabled_cmds.set, class_files,
		     false, &set_index_cache_prefix_list);
  deprecate_cmd (set_index_cache_on_cmd, "set index-cache enabled on");
  set_index_cache_on_cmd->default_args = "on";

  /* set index-cache off */
  cmd_list_element *set_index_cache_off_cmd
    = add_alias_cmd ("off", setshow_index_cache_enabled_cmds.set, class_files,
		     false, &set_index_cache_prefix_list);
  deprecate_cmd (set_index_cache_off_cmd, "set index-cache enabled off");
  set_index_cache_off_cmd->default_args = "off";

  /* set index-cache directory */
  add_setshow_filename_cmd ("directory", class_files, &index_cache_directory,
			    _("Set the directory of the index cache."),
			    _("Show the directory of the index cache."),
			    NULL,
			    set_index_cache_directory_command, NULL,
			    &set_index_cache_prefix_list,
			    &show_index_cache_prefix_list);

  /* show index-cache stats */
  add_cmd ("stats", class_files, show_index_cache_stats_command,
	   _("Show some stats about the index cache."),
	   &show_index_cache_prefix_list);

  /* set debug index-cache */
  add_setshow_boolean_cmd ("index-cache", class_maintenance,
			   &debug_index_cache,
			   _("Set display of index-cache debug messages."),
			   _("Show display of index-cache debug messages."),
			   _("\
When non-zero, debugging output for the index cache is displayed."),
			    NULL, NULL,
			    &setdebuglist, &showdebuglist);
}