/* 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 <ctype.h>
#include <errno.h>
#include <unistd.h>
#include <getopt.h>

#include "util.h"
#include "StringMap.h"
#include "LoadObject.h"
#include "DbeSession.h"
#include "DbeFile.h"
#include "SourceFile.h"
#include "Elf.h"
#include "gp-archive.h"
#include "ArchiveExp.h"
#include "Print.h"
#include "Module.h"

er_archive::er_archive (int argc, char *argv[]) : DbeApplication (argc, argv)
{
  force = 0;
  common_archive_dir = NULL;
  quiet = 0;
  descendant = 1;
  use_relative_path = 0;
  s_option = ARCH_EXE_ONLY;
  mask = NULL;
}

er_archive::~er_archive ()
{
  if (mask)
    {
      for (long i = 0, sz = mask->size (); i < sz; i++)
	{
	  regex_t *regex_desc = mask->get (i);
	  regfree (regex_desc);
	  delete regex_desc;
	}
      delete mask;
    }
  delete common_archive_dir;
}

int
er_archive::mask_is_on (const char *str)
{
  if (mask == NULL)
    return 1;
  for (long i = 0, sz = mask->size (); i < sz; i++)
    {
      regex_t *regex_desc = mask->get (i);
      if (regexec (regex_desc, str, 0, NULL, 0) == 0)
	return 1;
    }
  return 0;
}

void
er_archive::usage ()
{
/*
  fprintf (stderr, GTXT ("Usage: %s [-nqFV] [-a on|ldobjects|src|usedldobjects|usedsrc|off] [-m regexp] experiment\n"), whoami);
*/

/*
  Ruud - Isolate this line because it has an argument.  Otherwise it would be at the
  end of this long list.
*/
  printf ( GTXT (
    "Usage: gprofng archive [OPTION(S)] EXPERIMENT\n"));

  printf ( GTXT (
    "\n"
    "Archive the associated application binaries and source files in a gprofng\n"
    "experiment to make it self contained and portable.\n"
    "\n"
    "Options:\n"
    "\n"
    " --version           print the version number and exit.\n"
    " --help              print usage information and exit.\n"
    " --verbose {on|off}  enable (on) or disable (off) verbose mode; the default is \"off\".\n"
    "\n"
    " -a {off|on|ldobjects|src|usedldobjects|usedsrc}  specify archiving of binaries and other files;\n"
    "                    in addition to disable this feature (off), or enable archiving off all\n"
    "                    loadobjects and sources (on), the other options support a more\n"
    "                    refined selection. All of these options enable archiving, but the\n"
    "                    keyword controls what exactly is selected: all load objects (ldobjects),\n"
    "                    all source files (src), the loadobjects asscoiated with a program counter\n"
    "                    (usedldobjects), or the source files associated with a program counter\n"
    "                    (usedsrc); the default is \"-a ldobjects\".\n"
    "\n"
    " -n                 archive the named experiment only, not any of its descendants.\n"
    "\n"
    " -q                 do not write any warnings to stderr; messages are archived and\n"
    "                    can be retrieved later.\n"
    "\n"
    " -F                 force writing or rewriting of the archive; ignored with the -n\n"
    "                    or -m options, or if this is a subexperiment.\n"
    "\n"
    " -d <path>          specifies the location of a common archive; this is a directory that\n"
    "                    contains archived files.\n"
    "\n"
    " -m <regex>         archive only those source, object, and debug info files whose full\n"
    "                    path name matches the given POSIX compliant regular expression.\n"
    "\n"
    "Limitations:\n"
    "\n"
    "Default archiving does not occur in case the application profiled terminates prematurely,\n"
    "or if archiving is disabled when collecting the performance data. In such cases, this\n"
    "tool can be used to afterwards archive the information, but it has to run on the same \n"
    "system where the profiling data was recorded.\n"
    "\n"
    "Documentation:\n"
    "\n"
    "A getting started guide for gprofng is maintained as a Texinfo manual. If the info and\n"
    "gprofng programs are properly installed at your site, the command \"info gprofng\"\n"
    "should give you access to this document.\n"
    "\n"
    "See also:\n"
    "\n"
    "gprofng(1), gprofng-collect-app(1), gprofng-display-html(1), "
    "gprofng-display-src(1), gprofng-display-text(1)\n"));
  exit (1);
}

Vector <LoadObject*> *
er_archive::get_loadObjs ()
{
  Vector <LoadObject*> *objs = new Vector<LoadObject*>();
  Vector <LoadObject*> *loadObjs = dbeSession->get_text_segments ();
  if (s_option != ARCH_NOTHING)
    {
      for (long i = 0, sz = VecSize(loadObjs); i < sz; i++)
	{
	  LoadObject *lo = loadObjs->get (i);
	  if ((lo->flags & SEG_FLAG_DYNAMIC) != 0)
	    continue;
	  DbeFile *df = lo->dbeFile;
	  if (df && ((df->filetype & DbeFile::F_FICTION) != 0))
	    continue;
	  if (!lo->isUsed && ((s_option & (ARCH_USED_EXE_ONLY | ARCH_USED_SRC_ONLY)) != 0))
	    continue;
	  objs->append (lo);
	}
    }
  if (DEBUG_ARCHIVE)
    {
      Dprintf (DEBUG_ARCHIVE, NTXT ("get_text_segments(): %d\n"),
	       (int) (loadObjs ? loadObjs->size () : -1));
      for (long i = 0, sz = loadObjs ? loadObjs->size () : 0; i < sz; i++)
	{
	  LoadObject *lo = loadObjs->get (i);
	  Dprintf (DEBUG_ARCHIVE, NTXT ("%s:%d  [%2ld] %s\n"),
		   get_basename (__FILE__), (int) __LINE__, i, STR (lo->dump ()));
	}
      Dprintf (DEBUG_ARCHIVE, NTXT ("\nget_loadObjs(): %d\n"),
	       (int) (objs ? objs->size () : -1));
      for (long i = 0, sz = VecSize(objs); i < sz; i++)
	{
	  LoadObject *lo = objs->get (i);
	  Dprintf (DEBUG_ARCHIVE, NTXT ("%s:%d  [%2ld] %s\n"),
		   get_basename (__FILE__), (int) __LINE__, i, STR (lo->dump ()));
	}
    }
  delete loadObjs;
  return objs;
}

/**
 * Clean old archive
 * Except the following cases:
 * 1. Founder experiment is an MPI experiment
 * 2. "-n" option is passed (do not archive descendants)
 * 3. "-m" option is passed (partial archiving)
 * 4. Experiment name is not the founder experiment (it is a sub-experiment)
 * @param expname
 * @param founder_exp
 * @return 0 - success
 */
int
er_archive::clean_old_archive (char *expname, ArchiveExp *founder_exp)
{
  if (0 == descendant)
    { // do not archive descendants
      fprintf (stderr, GTXT ("Warning: Option -F is ignored because -n option is specified (do not archive descendants)\n"));
      return 1;
    }
  if (NULL != mask)
    { // partial archiving
      fprintf (stderr, GTXT ("Warning: Option -F is ignored because -m option is specified\n"));
      return 1;
    }
  // Check if the experiment is the founder
  char *s1 = dbe_strdup (expname);
  char *s2 = dbe_strdup (founder_exp->get_expt_name ());
  if (!s1 || !s2)
    {
      fprintf (stderr, GTXT ("Cannot allocate memory\n"));
      exit (1);
    }
  // remove trailing slashes
  for (int n = strlen (s1); n > 0; n--)
    {
      if ('/' != s1[n - 1])
	break;
      s1[n - 1] = 0;
    }
  for (int n = strlen (s2); n > 0; n--)
    {
      if ('/' != s2[n - 1])
	break;
      s2[n - 1] = 0;
    }
  if (strcmp (s1, s2) != 0)
    { // not founder
      fprintf (stderr, GTXT ("Warning: Option -F is ignored because specified experiment name %s does not match founder experiment name %s\n"), s1, s2);
      free (s1);
      free (s2);
      return 1;
    }
  // Remove old "archives"
  char *arch = founder_exp->get_arch_name ();
  fprintf (stderr, GTXT ("INFO: removing existing archive: %s\n"), arch);
  if (dbe_stat (arch, NULL) == 0)
    {
      char *cmd = dbe_sprintf ("/bin/rm -rf %s", arch);
      system (cmd);
      free (cmd);
      if (dbe_stat (arch, NULL) != 0)
	{ // create "archives"
	  if (!founder_exp->create_dir (founder_exp->get_arch_name ()))
	    {
	      fprintf (stderr, GTXT ("Unable to create directory `%s'\n"), founder_exp->get_arch_name ());
	      exit (1);
	    }
	}
    }
  free (s1);
  free (s2);
  return 0;
} // clean_old_archive_if_necessary

void
er_archive::start (int argc, char *argv[])
{
  int last = argc - 1;
  if (check_args (argc, argv) != last)
    usage ();
  check_env_var ();
  if (s_option == ARCH_NOTHING)
    return;

  ArchiveExp *founder_exp = new ArchiveExp (argv[last]);
  if (founder_exp->get_status () == Experiment::FAILURE)
    {
      if (!quiet)
	fprintf (stderr, GTXT ("gp-archive: %s: %s\n"), argv[last],
		 pr_mesgs (founder_exp->fetch_errors (), NTXT (""), NTXT ("")));
      exit (1);
    }
  if (!founder_exp->create_dir (founder_exp->get_arch_name ()))
    {
      fprintf (stderr, GTXT ("Unable to create directory `%s'\n"), founder_exp->get_arch_name ());
      exit (1);
    }
  if (!common_archive_dir)
    common_archive_dir = dbe_strdup (getenv ("GPROFNG_ARCHIVE_COMMON_DIR"));
  if (common_archive_dir)
    {
      if (!founder_exp->create_dir (common_archive_dir))
	if (dbe_stat (common_archive_dir, NULL) != 0)
	  {
	    fprintf (stderr, GTXT ("Unable to create directory for common archive `%s'\n"), common_archive_dir);
	    exit (1);
	  }
    }
  // Clean old archives if necessary
  if (force)
    clean_old_archive (argv[last], founder_exp);
  Vector<ArchiveExp*> *exps = new Vector<ArchiveExp*>();
  exps->append (founder_exp);
  if (descendant)
    {
      Vector<char*> *exp_names = founder_exp->get_descendants_names ();
      if (exp_names)
	{
	  for (long i = 0, sz = exp_names->size (); i < sz; i++)
	    {
	      char *exp_path = exp_names->get (i);
	      ArchiveExp *exp = new ArchiveExp (exp_path);
	      if (exp->get_status () == Experiment::FAILURE)
		{
		  if (!quiet)
		    fprintf (stderr, GTXT ("gp-archive: %s: %s\n"), exp_path,
			     pr_mesgs (exp->fetch_errors (), "", ""));
		  delete exp;
		  continue;
		}
	      exps->append (exp);
	    }
	  exp_names->destroy ();
	  delete exp_names;
	}
    }
  for (long i = 0, sz = exps->size (); i < sz; i++)
    {
      ArchiveExp *exp = exps->get (i);
      exp->read_data (s_option);
    }

  Vector <DbeFile*> *copy_files = new Vector<DbeFile*>();
  Vector <LoadObject*> *loadObjs = get_loadObjs ();
  for (long i = 0, sz = VecSize(loadObjs); i < sz; i++)
    {
      LoadObject *lo = loadObjs->get (i);
      if (strcmp (lo->get_pathname (), "LinuxKernel") == 0)
	continue;
      DbeFile *df = lo->dbeFile;
      if ((df->filetype & DbeFile::F_FICTION) != 0)
	continue;
      if (df->get_location () == NULL)
	{
	  copy_files->append (df);
	  continue;
	}
      if ((df->filetype & DbeFile::F_JAVACLASS) != 0)
	{
	  if (df->container)
	    { // Found in .jar file
	      copy_files->append (df->container);
	    }
	  copy_files->append (df);
	  if ((s_option & ARCH_EXE_ONLY) != 0)
	    continue;
	}
      lo->sync_read_stabs ();
      Elf *elf = lo->get_elf ();
      if (elf && (lo->checksum != 0) && (lo->checksum != elf->elf_checksum ()))
	{
	  if (!quiet)
	    fprintf (stderr, GTXT ("gp-archive: '%s' has an unexpected checksum value; perhaps it was rebuilt. File ignored\n"),
		       df->get_location ());
	  continue;
	}
      copy_files->append (df);
      if (elf)
	{
	  Elf *f = elf->find_ancillary_files (lo->get_pathname ());
	  if (f)
	    copy_files->append (f->dbeFile);
	  for (long i1 = 0, sz1 = VecSize(elf->ancillary_files); i1 < sz1; i1++)
	    {
	      Elf *ancElf = elf->ancillary_files->get (i1);
	      copy_files->append (ancElf->dbeFile);
	    }
	}
      Vector<Module*> *modules = lo->seg_modules;
      for (long i1 = 0, sz1 = VecSize(modules); i1 < sz1; i1++)
	{
	  Module *mod = modules->get (i1);
	  if ((mod->flags & MOD_FLAG_UNKNOWN) != 0)
	    continue;
	  else if ((s_option & (ARCH_USED_EXE_ONLY | ARCH_USED_SRC_ONLY)) != 0 &&
		   !mod->isUsed)
	    continue;
	  if ((s_option & ARCH_ALL) != 0)
	    mod->read_stabs (false); // Find all Sources
	  if (mod->dot_o_file && mod->dot_o_file->dbeFile)
	    copy_files->append (mod->dot_o_file->dbeFile);
	}
    }
  delete loadObjs;

  int bmask = DbeFile::F_LOADOBJ | DbeFile::F_JAVACLASS | DbeFile::F_JAR_FILE |
	  DbeFile::F_DOT_O | DbeFile::F_DEBUG_FILE;
  if ((s_option & (ARCH_USED_SRC_ONLY | ARCH_ALL)) != 0)
    {
      bmask |= DbeFile::F_JAVA_SOURCE | DbeFile::F_SOURCE;
      Vector<SourceFile*> *sources = dbeSession->get_sources ();
      for (long i = 0, sz = VecSize(sources); i < sz; i++)
	{
	  SourceFile *src = sources->get (i);
	  if ((src->flags & SOURCE_FLAG_UNKNOWN) != 0)
	    continue;
	  else if ((s_option & ARCH_USED_SRC_ONLY) != 0)
	    {
	      if ((src->dbeFile->filetype & DbeFile::F_JAVA_SOURCE) != 0 &&
		  !src->isUsed)
		continue;
	    }
	  if (src->dbeFile)
	    copy_files->append (src->dbeFile);
	}
    }

  Vector <DbeFile*> *notfound_files = new Vector<DbeFile*>();
  for (long i = 0, sz = VecSize(copy_files); i < sz; i++)
    {
      DbeFile *df = copy_files->get (i);
      char *fnm = df->get_location ();
      char *nm = df->get_name ();
      Dprintf (DEBUG_ARCHIVE,
	       "%s::%d copy_files[%ld] filetype=%4d inArchive=%d '%s' --> '%s'\n",
	       get_basename (__FILE__), (int) __LINE__, i,
	       df->filetype, df->inArchive ? 1 : 0, STR (nm), STR (fnm));
      Dprintf (DEBUG_ARCHIVE && df->container,
	       "    copy_files[%ld]: Found '%s' in '%s'\n",
	       i, STR (nm), STR (df->container->get_name ()));
      if (fnm == NULL)
	{
	  if (!quiet)
	    notfound_files->append (df);
	  continue;
	}
      else if (df->inArchive)
	{
	  Dprintf (DEBUG_ARCHIVE,
		   "  NOT COPIED: copy_files[%ld]: inArchive=1 '%s'\n",
		   i, STR (nm));
	  continue;
	}
      else if ((df->filetype & bmask) == 0)
	{
	  Dprintf (DEBUG_ARCHIVE,
		   "  NOT COPIED: copy_files[%ld]: container=%p filetype=%d bmask=%d '%s'\n",
		   i, df->container, df->filetype, bmask, STR (nm));
	  continue;
	}
      else if (df->container &&
	       (df->filetype & (DbeFile::F_JAVA_SOURCE | DbeFile::F_SOURCE)) == 0)
	{
	  Dprintf (DEBUG_ARCHIVE,
		   "  NOT COPIED: copy_files[%ld]: container=%p filetype=%d bmask=%d '%s'\n",
		   i, df->container, df->filetype, bmask, STR (nm));
	  continue;
	}
      else if (!mask_is_on (df->get_name ()))
	{
	  Dprintf (DEBUG_ARCHIVE,
		   "  NOT COPIED: copy_files[%ld]: mask is off for '%s'\n",
		   i, STR (nm));
	  continue;
	}
      char *anm = founder_exp->getNameInArchive (nm, false);
      if (force)
	unlink (anm);
      int res = founder_exp->copy_file (fnm, anm, quiet, common_archive_dir, use_relative_path);
      if (0 == res)  // file successfully archived
	df->inArchive = 1;
      delete anm;
    }
  delete copy_files;

  if (notfound_files->size () > 0)
    {
      for (long i = 0, sz = notfound_files->size (); i < sz; i++)
	{
	  DbeFile *df = notfound_files->get (i);
	  fprintf (stderr, GTXT ("gp-archive: Cannot find file: `%s'\n"), df->get_name ());
	}
      fprintf (stderr,
        GTXT ("\n If you know the correct location of the missing file(s)"
	      " you can help gp-archive to find them by manually editing"
	      " the .gprofng.rc file."
	      " See the gp-archive man page for more details.\n"));
    }
  delete notfound_files;
}

int
er_archive::check_args (int argc, char *argv[])
{
  int opt;
  int rseen = 0;
  int dseen = 0;
  // Parsing the command line
  opterr = 0;
  optind = 1;
  static struct option long_options[] = {
    {"help",    no_argument,        0, 'h'},
    {"version", no_argument,        0, 'V'},
    {"whoami",  required_argument,  0, 'w'},
    {"outfile", required_argument,  0, 'O'},
    {NULL, 0, 0, 0}
  };
  while (1)
    {
      int option_index = 0;
      opt = getopt_long (argc, argv, NTXT (":VFa:d:qnr:m:"),
			 long_options, &option_index);
      if (opt == EOF)
	break;
      switch (opt)
	{
	case 'F':
	  force = 1;
	  break;
	case 'd': // Common archive directory (absolute path)
	  if (rseen)
	    {
	      fprintf (stderr, GTXT ("Error: invalid combination of options: -r and -d are in conflict.\n"));
	      return -1;
	    }
	  if (dseen)
	    fprintf (stderr, GTXT ("Warning: option -d was specified several times. Last value is used.\n"));
	  free (common_archive_dir);
	  common_archive_dir = xstrdup (optarg);
	  dseen = 1;
	  break;
	case 'q':
	  quiet = 1;
	  break;
	case 'n':
	  descendant = 0;
	  break;
	case 'r': // Common archive directory (relative path)
	  if (dseen)
	    {
	      fprintf (stderr, GTXT ("Error: invalid combination of options: -d and -r are in conflict.\n"));
	      return -1;
	    }
	  if (rseen)
	    fprintf (stderr, GTXT ("Warning: option -r was specified several times. Last value is used.\n"));
	  free (common_archive_dir);
	  common_archive_dir = xstrdup (optarg);
	  use_relative_path = 1;
	  rseen = 1;
	  break;
	case 'a':
	  if (strcmp (optarg, "off") == 0)
	    s_option = ARCH_NOTHING;
	  else if (strcmp (optarg, "on") == 0 ||
		   strcmp (optarg, "ldobjects") == 0)
	    s_option = ARCH_EXE_ONLY;
	  else if (strcmp (optarg, "usedldobjects") == 0)
	    s_option = ARCH_USED_EXE_ONLY;
	  else if (strcmp (optarg, "usedsrc") == 0)
	    s_option = ARCH_USED_EXE_ONLY | ARCH_USED_SRC_ONLY;
	  else if (strcmp (optarg, "all") == 0 || strcmp (optarg, "src") == 0)
	    s_option = ARCH_ALL;
	  else
	    {
	      fprintf (stderr, GTXT ("Error: invalid option: `-%c %s'\n"),
		       optopt, optarg);
	      return -1;
	    }
	  break;
	case 'm':
	  {
	    regex_t *regex_desc = new regex_t ();
	    if (regcomp (regex_desc, optarg, REG_EXTENDED | REG_NOSUB | REG_NEWLINE))
	      {
		delete regex_desc;
		fprintf (stderr, GTXT ("Error: invalid option: `-%c %s'\n"),
			 optopt, optarg);
		return -1;
	      }
	    if (mask == NULL)
	      mask = new Vector<regex_t *>();
	    mask->append (regex_desc);
	    break;
	  }
	case 'O':
	  {
	    int fd = open (optarg, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
	    if (fd == -1)
	      {
		fprintf (stderr, GTXT ("gp-archive: Can't open %s: %s\n"),
			 optarg, strerror (errno));
		break;
	      }
	    if (dup2 (fd, 2) == -1)
	      {
		close (fd);
		fprintf (stderr, GTXT ("gp-archive: Can't divert stderr: %s\n"),
			 strerror (errno));
		break;
	      }
	    if (dup2 (fd, 1) == -1)
	      {
		close (fd);
		fprintf (stderr, GTXT ("gp-archive: Can't divert stdout: %s\n"),
			 strerror (errno));
		break;
	      }
	    close (fd);
	    struct timeval tp;
	    gettimeofday (&tp, NULL);
	    fprintf (stderr, "### Start %s#", ctime (&tp.tv_sec));
	    for (int i = 0; i < argc; i++)
	      fprintf (stderr, " %s", argv[i]);
	    fprintf (stderr, "\n");
	    break;
	  }
	case 'V':
	  Application::print_version_info ();
	  exit (0);
	case 'w':
	  whoami = optarg;
	  break;
	case 'h':
	  usage ();
	  exit (0);
	case ':': // -s -m without operand
	  fprintf (stderr, GTXT ("Option -%c requires an operand\n"), optopt);
	  return -1;
	case '?':
	default:
	  fprintf (stderr, GTXT ("Unrecognized option: -%c\n"), optopt);
	  return -1;
	}
    }
  return optind;
}

void
er_archive::check_env_var ()
{
  char *ename = NTXT ("GPROFNG_ARCHIVE");
  char *var = getenv (ename);
  if (var == NULL)
    return;
  var = dbe_strdup (var);
  Vector<char*> *opts = new Vector<char*>();
  opts->append (ename);
  for (char *s = var;;)
    {
      while (*s && isblank (*s))
	s++;
      if (*s == 0)
	break;
      opts->append (s);
      while (*s && !isblank (*s))
	s++;
      if (*s == 0)
	break;
      *s = 0;
      s++;
    }
  if (opts->size () > 0)
    {
      char **arr = (char **) xmalloc (sizeof (char *) *opts->size ());
      for (long i = 0; i < opts->size (); i++)
	arr[i] = opts->get (i);
      if (-1 == check_args (opts->size (), arr))
	fprintf (stderr, GTXT ("Error: Wrong SP_ER_ARCHIVE: '%s'\n"), var);
      free (arr);
    }
  delete opts;
  free (var);
}

static int
real_main (int argc, char *argv[])
{
  er_archive *archive = new er_archive (argc, argv);
  dbeSession->archive_mode = 1;
  archive->start (argc, argv);
  dbeSession->unlink_tmp_files ();
  return 0;
}

/**
 * Call catch_out_of_memory(int (*real_main)(int, char*[]), int argc, char *argv[]) which will call real_main()
 * @param argc
 * @param argv
 * @return
 */
int
main (int argc, char *argv[])
{
  xmalloc_set_program_name (argv[0]);
  return catch_out_of_memory (real_main, argc, argv);
}
