/* Copyright (C) 2021-2026 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 <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/wait.h>
#include <sys/utsname.h>
#include <fcntl.h>
#include <signal.h>
#include <time.h>
#include <errno.h>
#include <sys/ptrace.h>

#include "gp-defs.h"
#include "cpu_frequency.h"
#include "util.h"
#include "collctrl.h"
#include "hwcdrv.h"
#include "gp-experiment.h"
#include "collect.h"
#include "StringBuilder.h"

extern char **environ;

static volatile int interrupt = 0;
static int saved_stdout = -1;
static int  saved_stderr = -1;
static int no_short_usage = 0;
static int usage_fd = 2;
static collect *collect_obj = NULL;
extern "C" void sigint_handler (int sig, siginfo_t *info, void *context);
static char *outredirect = NULL;
static int precheck;
static int nprocesses;
static Process **processes;

int
main (int argc, char *argv[])
{
  xmalloc_set_program_name (argv[0]);
  // disable any alarm that might be pending
  int r = alarm (0);
  if (r != 0)
    dbe_write (2, GTXT ("collect has alarm(%d) pending\n"), r);
  collect_obj = new collect (argc, argv, environ);
  collect_obj->start (argc, argv);
  delete collect_obj;
  return 0;
}

extern "C" void
sigint_handler (int, siginfo_t *, void *)
{
  interrupt = 1;
  if (collect_obj->cc != NULL)
    collect_obj->cc->interrupt ();
  return;
}

extern "C" void
sigalrm_handler (int, siginfo_t *, void *)
{
  dbe_write (2, GTXT ("collect: unexpected alarm clock signal received\n"));
  return;
}

extern "C" void
sigterm_handler (int, siginfo_t *, void *)
{
  for (int i = 0; i < nprocesses; i++)
    {
      Process *proc = processes[i];
      if (proc != NULL)
	kill (proc->pid, SIGTERM);
    }
}

collect::collect (int argc, char *argv[], char **envp)
: Application (argc, argv)
{
  verbose = 0;
  disabled = 0;
  cc = NULL;
  collect_warnings = NULL;
  collect_warnings_idx = 0;
  int ii;
  for (ii = 0; ii < MAX_LD_PRELOAD_TYPES; ii++)
    sp_preload_list[ii] = NULL;
  for (ii = 0; ii < MAX_LD_PRELOAD_TYPES; ii++)
    sp_libpath_list[ii] = NULL;
  java_path = NULL;
  java_how = NULL;
  jseen_global = 0;
  nlabels = 0;
  origargc = argc;
  origargv = argv;
  origenvp = envp;
  mem_so_me = false;
}

collect::~collect ()
{
  delete cc;
}

struct sigaction old_sigint_handler;
struct sigaction old_sigalrm_handler;

void
collect::start (int argc, char *argv[])
{
  char *ccret;
  char *extype;
  /* create a collector control structure, disabling aggressive warning */
  cc = new Coll_Ctrl (0, false, false);
  if (prog_name)
    {
      char *s = strrchr (prog_name, '/');
      if (s && (s - prog_name) > 5) // Remove /bin/
	{
	  s = dbe_sprintf (NTXT ("%.*s"), (int) (s - prog_name - 4), prog_name);
	  cc->set_project_home (s);
	  free (s);
	}
    }
  char * errenable = cc->enable_expt ();
  if (errenable)
    {
      writeStr (2, errenable);
      free (errenable);
    }

  /* install a handler for SIGALRM */
  struct sigaction act;
  memset (&act, 0, sizeof (struct sigaction));
  sigemptyset (&act.sa_mask);
  act.sa_handler = (SignalHandler) sigalrm_handler;
  act.sa_flags = SA_RESTART | SA_SIGINFO;
  if (sigaction (SIGALRM, &act, &old_sigalrm_handler) == -1)
    {
      writeStr (2, GTXT ("Unable to install SIGALRM handler\n"));
      exit (-1);
    }

  /* install a handler for SIGINT */
  sigemptyset (&act.sa_mask);
  act.sa_handler = (SignalHandler) sigint_handler;
  act.sa_flags = SA_RESTART | SA_SIGINFO;
  if (sigaction (SIGINT, &act, &old_sigint_handler) == -1)
    {
      writeStr (2, GTXT ("Unable to install SIGINT handler\n"));
      exit (-1);
    }

  /* install a handler for SIGTERM */
  sigemptyset (&act.sa_mask);
  act.sa_sigaction = sigterm_handler;
  act.sa_flags = SA_RESTART | SA_SIGINFO;
  if (sigaction (SIGTERM, &act, NULL) == -1)
    {
      writeStr (2, GTXT ("Unable to install SIGTERM handler\n"));
      exit (-1);
    }
  if (argc > 1 && strncmp (argv[1], NTXT ("--whoami="), 9) == 0)
    {
      whoami = argv[1] + 9;
      argc--;
      argv++;
    }

  /* check for no arguments -- usage message */
  if (argc == 1)
    {
      verbose = 1;
      usage_fd = 1;
      validate_config (0);
      usage ();
      exit (0);
    }
  else if (argc == 2 && strcmp (argv[1], NTXT ("-h")) == 0)
    {
      /* only one argument, -h */
      verbose = 1;
      validate_config (0);
      /* now print the HWC usage message */
      show_hwc_usage ();
      exit (0);
    }
  else if (argc == 2 && (strcmp (argv[1], NTXT ("-help")) == 0 ||
			 strcmp (argv[1], NTXT ("--help")) == 0))
    {
      /* only one argument, -help or --help */
      verbose = 1;
      usage_fd = 1;
      validate_config (0);
      usage ();
      exit (0);
    }
// Ruud
  else if ((argc == 2) &&
	   (strcmp (argv[1], NTXT ("--version")) == 0))
    {
      /* only one argument, --version */

      /* print the version info */
      Application::print_version_info ();
      exit (0);
    }

  /* precheck the arguments -- scan for -O, -M flagS */
  precheck = 1;
  targ_index = check_args (argc, argv);
  if (targ_index < 0)
    {
      /* message has already been written */
      usage_fd = 2;
      short_usage ();
      exit (1);
    }
  /* crack the arguments */
  precheck = 0;
  targ_index = check_args (argc, argv);
  if (targ_index <= 0)
    {
      /* message has already been written */
      usage_fd = 2;
      short_usage ();
      exit (1);
    }
  if (targ_index != 0)
    check_target (argc, argv);
  if (disabled != 0 && cc->get_count () == 0)
    {
      // show collection parameters; count data
      ccret = cc->show (0);
      writeStr (1, ccret);
    }

  // see if Java version should be checked
  if (cc->get_java_default () == 0 && java_path != NULL)
    validate_java (java_path, java_how, verbose);

  /* if count data is requested, exec bit to do the real work */
  /* even for a dryrun */
  if (cc->get_count () != 0)
    get_count_data ();

  /* if a dry run, just exit */
  if (disabled != 0)
    {
      writeStr (1, cc->show_expt ());
      StringBuilder sb;
      sb.append (GTXT ("Exec argv[] = "));
      for (int i = 0; i < nargs; i++)
	sb.appendf (NTXT ("%s "), arglist[i]);
      sb.append (NTXT ("\n"));
      char *s = sb.toString ();
      writeStr (1, s);
      free (s);
      exit (0);
    }

  // If the mem_so_me flag is set, preload mem.so
  //	and launch the process
  if (mem_so_me)
    {
      /* set env vars for mem.so */
      if (putenv_memso () != 0)
	exit (1); /* message has already been written */
      /* ensure original outputs restored for target */
      reset_output ();

      /* now exec the target ... */
      if (cc->get_debug_mode () == 1)
	{
	  traceme (arglist[0], arglist);
	  extype = NTXT ("traceme");
	}
      else
	{
	  execvp (arglist[0], arglist);
	  extype = NTXT ("exevcp");
	}
      /* oops, exec of the target failed */
      char *em = strerror (errno);
      set_output ();    /* restore output for collector */
      if (em == NULL)
	dbe_write (2, GTXT ("memso %s of %s failed: errno = %d\n"), extype, argv[targ_index], errno);
      else
	dbe_write (2, GTXT ("memso %s of %s failed: %s\n"), extype, argv[targ_index], em);
      exit (1);
    }

  /* normal path, setting up an experiment and launching the target */
  /* set up the experiment */
  ccret = cc->setup_experiment ();
  if (ccret != NULL)
    {
      dbe_write (2, NTXT ("%s\n"), ccret);
      free (ccret);
      exit (1);
    }
  /* Beyond this point, the experiment is created */
  if (collect_warnings != NULL)
    {
      warn_open ();
      for (int i = 0; i < collect_warnings_idx; i++)
	warn_comment (SP_JCMD_CWARN, COL_WARN_APP_NOT_READY, collect_warnings[i], (int) strlen (collect_warnings[i]));
      warn_close ();
    }
  /* check cpu frequency variation for intel*/
  unsigned char mode = COL_CPUFREQ_NONE;
  int max_freq = get_cpu_frequency (&mode);
  char freq_scaling[256];
  char turbo_mode[256];
  *freq_scaling = 0;
  *turbo_mode = 0;
  if (mode & COL_CPUFREQ_SCALING)
    snprintf (freq_scaling, sizeof (freq_scaling), NTXT (" frequency_scaling=\"enabled\""));
  if (mode & COL_CPUFREQ_TURBO)
    snprintf (turbo_mode, sizeof (turbo_mode), NTXT (" turbo_mode=\"enabled\""));
  if (mode != COL_CPUFREQ_NONE)
    {
      warn_open ();
      if (warn_file != NULL)
	{
	  warn_write ("<powerm>\n<frequency clk=\"%d\"%s%s/>\n</powerm>\n",
		      max_freq, freq_scaling, turbo_mode);
	  warn_close ();
	}
    }

  /* check for labels to write to notes file */
  if (nlabels != 0)
    {
      char *nbuf;
      char nbuf2[MAXPATHLEN];
      // fetch the experiment name and CWD
      char *exp = cc->get_experiment ();
      char *ev = getcwd (nbuf2, sizeof (nbuf2));

      // format the environment variable for the experiment directory name
      if (ev != NULL && exp[0] != '/')
	// cwd succeeded, and experiment is a relative path
	nbuf = dbe_sprintf (NTXT ("%s/%s/%s"), nbuf2, exp, SP_NOTES_FILE);
      else
	// getcwd failed or experiment is a fullpath
	nbuf = dbe_sprintf (NTXT ("%s/%s"), exp, SP_NOTES_FILE);

      FILE *f = fopen (nbuf, NTXT ("w"));
      free (nbuf);
      if (f != NULL)
	{
	  for (int i = 0; i < nlabels; i++)
	    fprintf (f, NTXT ("%s\n"), label[i]);
	  fclose (f);
	}
    }
  /* check for user interrupt */
  if (interrupt == 1)
    {
      cc->delete_expt ();
      writeStr (2, GTXT ("User interrupt\n"));
      exit (0);
    }

  /* print data-collection parameters */
  if (verbose)
    {
      ccret = cc->show (0);
      if (ccret != NULL)
	writeStr (2, ccret);
    }
  ccret = cc->show_expt ();
  if (ccret != NULL)
    writeStr (1, ccret);    /* write this to stdout */

  pid_t pid = (pid_t) cc->get_attach_pid ();
  if (pid == (pid_t) 0)
    {
      /* No attach */
      /* Set the environment for libcollector */
      if (putenv_libcollector () != 0)
	{
	  /* message has already been written */
	  cc->delete_expt ();
	  exit (1);
	}
      /* ensure original output fds restored for target */
      reset_output ();

      /* now exec the target ... */
      if (cc->get_debug_mode () == 1)
	{
	  traceme (arglist[0], arglist);
	  extype = NTXT ("traceme");
	}
      else
	{
	  execvp (arglist[0], arglist);
	  extype = NTXT ("execvp");
	}

      /* we reach this point only if the target launch failed */
      char *em = strerror (errno);

      /* restore output for collector */
      set_output ();

      /* exec failed; delete experiment */
      cc->delete_expt ();

      /* print a message and exit */
      if (em == NULL)
	dbe_write (2, GTXT ("%s of %s failed: errno = %d\n"), extype, argv[targ_index], errno);
      else
	dbe_write (2, GTXT ("%s of %s failed: %s\n"), extype, argv[targ_index], em);
      exit (1);
    }
  else
    abort ();
}

/**
 * Prepare a warning message and pass it to warn_write()
 * @Parameters:
 * kind Type of comment
 * num ID
 * s Comment sting
 * len Length of the string
 * @Return: none.
 */
void
collect::warn_comment (const char *kind, int num, char *s, int len)
{
  if (len != 0)
    warn_write (NTXT ("<event kind=\"%s\" id=\"%d\">%.*s</event>\n"),
		kind, num, len, s);
  else if (s == NULL)
    warn_write (NTXT ("<event kind=\"%s\" id=\"%d\"/>\n"), kind, num);
  else
    warn_write (NTXT ("<event kind=\"%s\" id=\"%d\">%s</event>\n"), kind, num, s);
}

/**
 * Open the warnings file in Append mode ("aw")
 */
void
collect::warn_open ()
{
  // open the warnings file
  warnfilename = dbe_sprintf (NTXT ("%s/%s"), cc->get_experiment (), SP_WARN_FILE);
  int fd = open (warnfilename, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
  warn_file = fdopen (fd, NTXT ("aw"));
}

/**
 * Close the warnings file
 */
void
collect::warn_close ()
{
  (void) fclose (warn_file);
}

/**
 * Format the warning message and write it to the warnings file
 */
void
collect::warn_write (const char *format, ...)
{
  char buf[4096];
  // format the input arguments into a string
  va_list va;
  va_start (va, format);
  vsnprintf (buf, sizeof (buf), format, va);
  va_end (va);
  // write it to the warnings file (warnings.xml)
  fwrite (buf, 1, strlen (buf), warn_file);
  fflush (warn_file);
}

/* process the args, setting expt. params,
 *	and finding offset for a.out name
 */
int
collect::check_args (int argc, char *argv[])
{
  int hseen = 0;
  int hoffseen = 0;
  int lseen = 0;
  int tseen = 0;
  int pseen = 0;
  int sseen = 0;
  int yseen = 0;
  int Fseen = 0;
  int Aseen = 0;
  int Sseen = 0;
  int Hseen = 0;
  int iseen = 0;
  int Jseen = 0;
  int ofseen = 0;
  char *expName = NULL;
  bool overwriteExp = false;
  char *ccret;
  char *ccwarn;
  for (targ_index = 1; targ_index < argc; targ_index++)
    {
      if (argv[targ_index] == NULL)
	break;
      if (dbe_strcmp (argv[targ_index], "--") == 0)
	{
	  targ_index++;
	  break;
	}
      if (argv[targ_index][0] != '-')
	break;
      int param;
      switch (argv[targ_index][1])
	{
	case 'y':
	  {
	    if (precheck == 1)
	      {
		targ_index++;
		if (argv[targ_index] == NULL)
		  return 0;
		break;
	      }
	    char *ptr;
	    int resume = 1;
	    if (checkflagterm (argv[targ_index]) == -1) return -1;
	    if (yseen != 0)
	      {
		dupflagseen ('y');
		return -1;
	      }
	    yseen++;
	    targ_index++;
	    if (argv[targ_index] == NULL)
	      {
		writeStr (2, GTXT ("-y requires a signal argument\n"));
		return -1;
	      }
	    if ((ptr = strrchr (argv[targ_index], ',')) != NULL)
	      {
		if ((*(ptr + 1) != 'r') || (*(ptr + 2) != 0))
		  {
		    /* not the right trailer */
		    dbe_write (2, GTXT ("Invalid delay signal %s\n"), argv[targ_index]);
		    return -1;
		  }
		resume = 0;
		*ptr = 0;
	      }
	    param = cc->find_sig (argv[targ_index]);
	    if (param < 0)
	      {
		/* invalid signal */
		dbe_write (2, GTXT ("Invalid delay signal %s\n"), argv[targ_index]);
		return -1;
	      }
	    ccret = cc->set_pauseresume_signal (param, resume);
	    if (ccret != NULL)
	      {
		/* invalid signal; write message */
		writeStr (2, ccret);
		return -1;
	      }
	    break;
	  }
	case 'l':
	  if (precheck == 1)
	    {
	      targ_index++;
	      if (argv[targ_index] == NULL)
		return 0;
	      break;
	    }
	  if (checkflagterm (argv[targ_index]) == -1) return -1;
	  if (lseen != 0)
	    {
	      dupflagseen ('l');
	      return -1;
	    }
	  lseen++;
	  targ_index++;
	  if (argv[targ_index] == NULL)
	    {
	      writeStr (2, GTXT ("-l requires a signal argument\n"));
	      return -1;
	    }
	  param = cc->find_sig (argv[targ_index]);
	  if (param < 0)
	    {
	      /* invalid signal */
	      dbe_write (2, GTXT ("Invalid sample signal %s\n"), argv[targ_index]);
	      return -1;
	    }
	  ccret = cc->set_sample_signal (param);
	  if (ccret != NULL)
	    {
	      /* invalid signal; write message */
	      writeStr (2, ccret);
	      free (ccret);
	      return -1;
	    }
	  break;

#ifdef GPROFNG_DOES_NOT_SUPPORT
	case 'P':
	  if (precheck == 1)
	    {
	      targ_index++;
	      if (argv[targ_index] == NULL)
		return 0;
	      break;
	    }
	  if (checkflagterm (argv[targ_index]) == -1)
	    return -1;
	  if (Pseen != 0)
	    {
	      dupflagseen ('P');
	      return -1;
	    }
	  Pseen++;
	  targ_index++;
	  if (argv[targ_index] == NULL)
	    {
	      writeStr (2, GTXT ("-P requires a process pid argument\n"));
	      return -1;
	    }
	  ccret = cc->set_attach_pid (argv[targ_index]);
	  if (ccret != NULL)
	    {
	      /* error; write message */
	      writeStr (2, ccret);
	      free (ccret);
	      return -1;
	    }
	  break;
#endif
	case 't':
	  if (precheck == 1)
	    {
	      targ_index++;
	      if (argv[targ_index] == NULL)
		return 0;
	      break;
	    }

	  if (checkflagterm (argv[targ_index]) == -1) return -1;
	  if (tseen != 0)
	    {
	      dupflagseen ('t');
	      return -1;
	    }
	  tseen++;
	  targ_index++;
	  if (argv[targ_index] == NULL)
	    {
	      writeStr (2, GTXT ("-t requires a run-duration argument\n"));
	      return -1;
	    }
	  ccret = cc->set_time_run (argv[targ_index]);
	  if (ccret != NULL)
	    {
	      /* error; write message */
	      writeStr (2, ccret);
	      free (ccret);
	      return -1;
	    }
	  break;
	case 'p':
	  {
	    char *warnmsg;
	    if (precheck == 1)
	      {
		targ_index++;
		if (argv[targ_index] == NULL)
		  return 0;
		break;
	      }
	    if (checkflagterm (argv[targ_index]) == -1) return -1;
	    if (pseen != 0)
	      {
		dupflagseen ('p');
		return -1;
	      }
	    pseen++;
	    targ_index++;
	    if (argv[targ_index] == NULL)
	      {
		writeStr (2, GTXT ("-p requires a clock-profiling argument\n"));
		return -1;
	      }
	    ccret = cc->set_clkprof (argv[targ_index], &warnmsg);
	    if (ccret != NULL)
	      {
		writeStr (2, ccret);
		free (ccret);
		return -1;
	      }
	    if (warnmsg != NULL)
	      {
		writeStr (2, warnmsg);
		free (warnmsg);
	      }
	    break;
	  }
	case 's':
	  if (precheck == 1)
	    {
	      targ_index++;
	      if (argv[targ_index] == NULL)
		return 0;
	      break;
	    }
	  if (checkflagterm (argv[targ_index]) == -1) return -1;
	  if (sseen != 0)
	    {
	      dupflagseen ('s');
	      return -1;
	    }
	  sseen++;
	  targ_index++;
	  if (argv[targ_index] == NULL)
	    {
	      writeStr (2, GTXT ("-s requires a synchronization-tracing argument\n"));
	      return -1;
	    }
	  ccret = cc->set_synctrace (argv[targ_index]);
	  if (ccret != NULL)
	    {
	      writeStr (2, ccret);
	      free (ccret);
	      return -1;
	    }
	  break;
	case 'h':
	  {
	    if (precheck == 1)
	      {
		targ_index++;
		if (argv[targ_index] == NULL)
		  return 0;
		break;
	      }
	    if (checkflagterm (argv[targ_index]) == -1)
	      return -1;
	    targ_index++;
	    if ((argv[targ_index] == NULL) || (strlen (argv[targ_index]) == 0))
	      {
		writeStr (2, GTXT ("-h requires a HW-counter-profiling argument\n"));
		return -1;
	      }
	    // Check for some special cases
	    char * string = argv[targ_index];
	    if (strcmp (argv[targ_index], NTXT ("off")) == 0)
	      {
		if (hseen != 0)
		  {
		    no_short_usage = 1;
		    writeStr (2, GTXT ("-h off cannot be used with any other -h arguments\n"));
		    return -1;
		  }
		hoffseen = 1;
		hseen = 1;
		cc->disable_hwc ();
		break;
	      }
	    // Check to see if we can use HWC
	    unsigned hwc_maxregs = hwc_get_max_concurrent (false);
	    if (hwc_maxregs == 0)
	      {
		char buf[1024];
		char *pch = hwcfuncs_errmsg_get (buf, sizeof (buf), 0);
		if (*pch)
		  dbe_write (2, GTXT ("HW counter profiling is not supported on this system: %s%s"),
			     pch, pch[strlen (pch) - 1] == '\n' ? "" : "\n");
		else
		  dbe_write (2, GTXT ("HW counter profiling is not supported on this system\n"));
		no_short_usage = 1;
		return -1;
	      }
	    // Make sure there's no other -h after -h off
	    if (hoffseen != 0)
	      {
		no_short_usage = 1;
		writeStr (2, GTXT ("No -h arguments can be used after -h off\n"));
		return -1;
	      }
	    // set up to process HW counters (to know about default counters)
	    cc->setup_hwc ();
	    hseen++;
	    char *warnmsg;
	    if (strcmp (argv[targ_index], NTXT ("on")) == 0)
	      ccret = cc->add_default_hwcstring ("on", &warnmsg, true);
	    else if (strcmp (argv[targ_index], NTXT ("hi")) == 0 ||
		     strcmp (argv[targ_index], NTXT ("high")) == 0)
	      ccret = cc->add_default_hwcstring ("hi", &warnmsg, true);
	    else if (strcmp (argv[targ_index], NTXT ("lo")) == 0 ||
		     strcmp (argv[targ_index], NTXT ("low")) == 0)
	      ccret = cc->add_default_hwcstring ("lo", &warnmsg, true);
	    else if (strcmp (argv[targ_index], NTXT ("auto")) == 0)
	      ccret = cc->add_default_hwcstring ("auto", &warnmsg, true);
	    else
	      ccret = cc->add_hwcstring (string, &warnmsg);
	    if (ccret != NULL)
	      {
		/* set global flag to suppress the short_usage message for any subsequent HWC errors */
		no_short_usage = 1;
		writeStr (2, ccret);
		free (ccret);
		return -1;
	      }
	    if (warnmsg != NULL)
	      {
		writeStr (2, warnmsg);
		free (warnmsg);
	      }
	    break;
	  }
	case 'O':
	  overwriteExp = true;
	  ATTRIBUTE_FALLTHROUGH
	case 'o':
	  if (precheck == 1)
	    {
	      targ_index++;
	      if (argv[targ_index] == NULL)
		return 0;
	      break;
	    }
	  if (checkflagterm (argv[targ_index]) == -1)
	    return -1;
	  if (argv[targ_index + 1] == NULL)
	    {
	      dbe_write (2, GTXT ("Argument %s must be followed by a file name\n"),
			 argv[targ_index]);
	      return -1;
	    }
	  if (expName != NULL)
	    {
	      dbe_write (2, GTXT ("Only one -o or -O argument may be used\n"));
	      dupflagseen ('o');
	      return -1;
	    }
	  expName = argv[targ_index + 1];
	  targ_index++;
	  break;
	case 'S':
	  if (precheck == 1)
	    {
	      targ_index++;
	      if (argv[targ_index] == NULL)
		return 0;
	      break;
	    }
	  if (checkflagterm (argv[targ_index]) == -1) return -1;
	  if (argv[targ_index + 1] == NULL)
	    {
	      dbe_write (2, GTXT ("Argument %s must be followed by a sample interval name\n"),
			 argv[targ_index]);
	      return -1;
	    }
	  if (Sseen != 0)
	    {
	      dupflagseen ('S');
	      return -1;
	    }
	  Sseen++;
	  ccret = cc->set_sample_period (argv[targ_index + 1]);
	  if (ccret != NULL)
	    {
	      writeStr (2, ccret);
	      free (ccret);
	      return -1;
	    }
	  targ_index++;
	  break;
	case 'H':
	  if (precheck == 1)
	    {
	      targ_index++;
	      if (argv[targ_index] == NULL)
		return 0;
	      break;
	    }
	  if (checkflagterm (argv[targ_index]) == -1)
	    return -1;
	  if (argv[targ_index + 1] == NULL)
	    {
	      dbe_write (2, GTXT ("Argument %s requires a heap-tracing argument\n"),
			 argv[targ_index]);
	      return -1;
	    }
	  if (Hseen != 0)
	    {
	      dupflagseen ('H');
	      return -1;
	    }
	  Hseen++;
	  ccret = cc->set_heaptrace (argv[targ_index + 1]);
	  if (ccret != NULL)
	    {
	      writeStr (2, ccret);
	      free (ccret);
	      return -1;
	    }
	  if (cc->get_java_default () == 1)
	    cc->set_java_mode (NTXT ("off"));
	  targ_index++;
	  break;
	case 'i':
	  if (precheck == 1)
	    {
	      targ_index++;
	      if (argv[targ_index] == NULL)
		return 0;
	      break;
	    }
	  if (checkflagterm (argv[targ_index]) == -1)
	    return -1;
	  if (argv[targ_index + 1] == NULL)
	    {
	      fprintf (stderr, GTXT ("Argument %s requires an I/O-tracing argument\n"),
		       argv[targ_index]);
	      return -1;
	    }
	  if (iseen != 0)
	    {
	      dupflagseen ('i');
	      return -1;
	    }
	  iseen++;
	  ccret = cc->set_iotrace (argv[targ_index + 1]);
	  if (ccret != NULL)
	    {
	      writeStr (2, ccret);
	      free (ccret);
	      return -1;
	    }
	  targ_index++;
	  break;
	case 'j':
	  if (precheck == 1)
	    {
	      targ_index++;
	      if (argv[targ_index] == NULL)
		return 0;
	      break;
	    }
	  if (checkflagterm (argv[targ_index]) == -1) return -1;
	  if (argv[targ_index + 1] == NULL)
	    {
	      dbe_write (2, GTXT ("Argument %s requires a java-profiling argument\n"),
			 argv[targ_index]);
	      return -1;
	    }
	  if (jseen_global != 0)
	    {
	      dupflagseen ('j');
	      return -1;
	    }
	  jseen_global++;
	  ccret = cc->set_java_mode (argv[targ_index + 1]);
	  if (ccret != NULL)
	    {
	      writeStr (2, ccret);
	      free (ccret);
	      return -1;
	    }
	  targ_index++;
	  break;
	case 'J':
	  if (precheck == 1)
	    {
	      targ_index++;
	      if (argv[targ_index] == NULL)
		return 0;
	      break;
	    }
	  if (checkflagterm (argv[targ_index]) == -1) return -1;
	  if (argv[targ_index + 1] == NULL)
	    {
	      dbe_write (2, GTXT ("Argument %s requires a java argument\n"),
			 argv[targ_index]);
	      return -1;
	    }
	  if (Jseen != 0)
	    {
	      dupflagseen ('J');
	      return -1;
	    }
	  Jseen++;
	  ccret = cc->set_java_args (argv[targ_index + 1]);
	  if (ccret != NULL)
	    {
	      writeStr (2, ccret);
	      free (ccret);
	      return -1;
	    }
	  targ_index++;
	  break;
	case 'F':
	  if (precheck == 1)
	    {
	      targ_index++;
	      if (argv[targ_index] == NULL)
		return 0;
	      break;
	    }
	  if (checkflagterm (argv[targ_index]) == -1)
	    return -1;
	  if (argv[targ_index + 1] == NULL)
	    {
	      dbe_write (2, GTXT ("Argument %s requires a descendant-following argument\n"),
			 argv[targ_index]);
	      return -1;
	    }
	  if (Fseen != 0)
	    {
	      dupflagseen ('F');
	      return -1;
	    }
	  Fseen++;
	  ccret = cc->set_follow_mode (argv[targ_index + 1]);
	  if (ccret != NULL)
	    {
	      writeStr (2, ccret);
	      free (ccret);
	      return -1;
	    }
	  targ_index++;
	  break;
	case 'a':
	  if (precheck == 1)
	    {
	      targ_index++;
	      if (argv[targ_index] == NULL)
		return 0;
	      break;
	    }
	  if (checkflagterm (argv[targ_index]) == -1)
	    return -1;
	  if (argv[targ_index + 1] == NULL)
	    {
	      dbe_write (2, GTXT ("Argument %s requires a load-object archiving argument\n"),
			 argv[targ_index]);
	      return -1;
	    }
	  if (Aseen != 0)
	    {
	      dupflagseen ('a');
	      return -1;
	    }
	  Aseen++;
	  ccret = cc->set_archive_mode (argv[targ_index + 1]);
	  if (ccret != NULL)
	    {
	      writeStr (2, ccret);
	      free (ccret);
	      return -1;
	    }
	  targ_index++;
	  break;
	case 'C':
	  if (precheck == 1)
	    {
	      targ_index++;
	      if (argv[targ_index] == NULL)
		return 0;
	      break;
	    }
	  if (checkflagterm (argv[targ_index]) == -1)
	    return -1;
	  if (argv[targ_index + 1] == NULL)
	    {
	      dbe_write (2, GTXT ("Argument %s must be followed by a comment\n"),
			 argv[targ_index]);
	      return -1;
	    }
	  if (nlabels == MAXLABELS)
	    {
	      dbe_write (2, GTXT ("No more than %d comments may be specified\n"),
			 MAXLABELS);
	      return -1;
	    }
	  label[nlabels] = argv[targ_index + 1];
	  nlabels++;
	  targ_index++;
	  break;
	case 'n':
	case 'v':
	case 'V':
	  if (precheck == 1)
	    break;
	  do_flag (&argv[targ_index][1]);
	  break;
	case 'Z':
	  // special undocumented argument for debug builds only to allow analyzer to
	  // LD_PRELOAD mem.so for the target it spawns
	  mem_so_me = true;
	  break;
	case '-':
	  if (strcmp (argv[targ_index], NTXT ("--verbose")) == 0)
	    do_flag ("v");
	  else if (strcmp (argv[targ_index], "--outfile") == 0)
	    {
	      if (precheck == 0)
		{
		  targ_index++;
		  if (argv[targ_index] == NULL)
		    return 0;
		  break;
		}
	      // process this argument now
	      if (argv[targ_index + 1] == NULL)
		{
		  dbe_write (2, GTXT ("Argument %s requires a file argument\n"),
			     argv[targ_index]);
		  return -1;
		}
	      if (ofseen != 0)
		{
		  dupflagseen (argv[targ_index]);
		  return -1;
		}
	      ofseen++;
	      if (outredirect == NULL)
		{
		  outredirect = argv[targ_index + 1];
		  set_output ();
		} // else already redirected; ignore with no message
	      targ_index++;
	    }
	  else
	    {
	      dbe_write (2, GTXT ("collect: unrecognized argument `%s'\n"), argv[targ_index]);
	      return -1;
	    }
	  break;
	default:
	  dbe_write (2, GTXT ("collect: unrecognized argument `%s'\n"), argv[targ_index]);
	  return -1;
	}
    }
  if (targ_index >= argc)
    return -1;
  if (argv[targ_index] == NULL)
    {
      if (precheck == 1)
	return 0;
      if (cc->get_attach_pid () != 0)  /* no target is OK, if we're attaching */
	return 0;
      writeStr (2, GTXT ("Name of target must be specified\n"));
      return -1;
    }
  if (expName)
    {
      ccwarn = NULL;
      ccret = cc->set_expt (expName, &ccwarn, overwriteExp);
      if (ccwarn)
	{
	  writeStr (2, ccwarn);
	  free (ccwarn);
	}
      if (ccret)
	{
	  writeStr (2, ccret);
	  return -1;
	}
    }
  if (cc->get_attach_pid () != 0)
    {
      writeStr (2, GTXT ("Name of target must not be specified when -P is used\n"));
      return -1;
    }
  return targ_index;
}

int
collect::checkflagterm (const char *c)
{
  if (c[2] != 0)
    {
      dbe_write (2, GTXT ("collect: unrecognized argument `%s'\n"), c);
      return -1;
    }
  return 0;
}

int
collect::do_flag (const char *flags)
{
  char *s;
  for (int i = 0;; i++)
    {
      switch (flags[i])
	{
	case 0: // end of string
	  return 0;
	case 'n':
	  disabled = 1;
	  if (verbose != 1)
	    {
	      Application::print_version_info ();
	      verbose = 1;
	    }
	  break;
	case 'x':
	  s = cc->set_debug_mode (1);
	  if (s)
	    {
	      writeStr (2, s);
	      free (s);
	    }
	  break;
	case 'v':
	  if (verbose != 1)
	    {
	      Application::print_version_info ();
	      verbose = 1;
	    }
	  break;
	case 'V':
	  Application::print_version_info ();
	  /* no further processing.... */
	  exit (0);
	}
    }
}

/*
 * traceme - cause the caller to stop at the end of the next exec()
 *	 so that a debugger can attach to the new program
 *
 * Takes same arguments as execvp()
 */
int
collect::traceme (const char *execvp_file, char *const execvp_argv[])
{
  int ret = -1;
  pid_t pid = fork ();
  if (pid == 0)
    { // child
      // child will set up itself to be PTRACE'd, and then exec the target executable
      /* reset the SP_COLLECTOR_FOUNDER value to the new pid */
      pid_t mypid = getpid ();
      char *ev = dbe_sprintf (NTXT ("%s=%d"), SP_COLLECTOR_FOUNDER, mypid);
      if (putenv (ev) != 0)
	{
	  dbe_write (2, GTXT ("fork-child: Can't putenv of \"%s\": run aborted\n"), ev);
	  return 1;
	}
      ptrace (PTRACE_TRACEME, 0, NULL, NULL); // initiate trace
      ret = execvp (execvp_file, execvp_argv); // execvp user command
      return ret; // execvp failed
    }
  else if (pid > 0)
    { // parent
      int status;
      if (waitpid (pid, &status, 0) != pid)
	{ // wait for execvp to cause signal
	  writeStr (2, GTXT ("parent waitpid() failed\n"));
	  return -2;
	}
      if (!WIFSTOPPED (status))
	writeStr (2, GTXT ("WIFSTOPPED(status) failed\n"));

      // originally, PTRACE_DETACH would send SIGTSTP, but now we do it here:
      if (kill (pid, SIGTSTP) != 0)
	writeStr (2, GTXT ("kill(pid, SIGTSTP) failed\n"));
      if (ptrace (PTRACE_DETACH, pid, NULL, 0) != 0)
	{ // detach trace
	  writeStr (2, GTXT ("ptrace(PTRACE_DETACH) failed\n"));
	  return -4;
	}
      dbe_write (2, GTXT ("Waiting for attach from debugger: pid=%d\n"), (int) pid);

      // wait for an external debugger to attach
      if (waitpid (pid, &status, 0) != pid)
	{ // keep parent alive until child quits
	  writeStr (2, GTXT ("parent final waitpid() failed\n"));
	  return -5;
	}
    }
  else
    return -1; // fork failed
  exit (0);
}

void
collect::dupflagseen (char c)
{
  dbe_write (2, GTXT ("Only one -%c argument may be used\n"), c);
}

void
collect::dupflagseen (const char *s)
{
  dbe_write (2, GTXT ("Only one %s argument may be used\n"), s);
}

int
collect::set_output ()
{
  static int initial = 1;
  if (outredirect)
    {
      int fd = open (outredirect, O_WRONLY | O_CREAT | O_APPEND,
		     S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
      if (fd == -1)
	{
	  dbe_write (2, GTXT ("Warning: Can't open collector output `%s': %s\n"),
		     outredirect, strerror (errno));
	}
      else
	{
	  if ((saved_stdout = dup (1)) == -1 || dup2 (fd, 1) == -1)
	    dbe_write (2, GTXT ("Warning: Can't divert collector %s: %s\n"),
		       NTXT ("stdout"), strerror (errno));
	  if ((saved_stderr = dup (2)) == -1 || dup2 (fd, 2) == -1)
	    dbe_write (2, GTXT ("Warning: Can't divert collector %s: %s\n"),
		       NTXT ("stderr"), strerror (errno));
	  close (fd);
	  if ((saved_stdout != -1) && (saved_stderr != -1))
	    {
	      if (initial)
		{
		  struct timeval tp;
		  gettimeofday (&tp, NULL);
		  writeStr (2, ctime (&tp.tv_sec));
		  initial = 0;
		}
	      return 1; // diversion in place
	    }
	}
    }
  return 0; // no diversion
}

void
collect::reset_output ()
{
  if (saved_stdout != -1 &&
      (dup2 (saved_stdout, 1) == -1 || close (saved_stdout)))
    dbe_write (2, GTXT ("Warning: Can't restore collector stdout: %s\n"),
	       strerror (errno));
  if (saved_stderr != -1 &&
      (dup2 (saved_stderr, 2) == -1 || close (saved_stderr)))
    dbe_write (2, GTXT ("Warning: Can't restore collector stderr: %s\n"),
	       strerror (errno));
}

void
collect::usage ()
{

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

/*
-------------------------------------------------------------------------------
  For a reason I don't understand, the continuation line(s) need to start at
  column 26 in order for help2man to do the righ thing. Ruud
-------------------------------------------------------------------------------
*/
  printf ( GTXT (
    "\n"
    "Collect performance data on the target program. In addition to Program\n"
    "Counter PC) sampling, hardware event counters and various tracing options\n"
    "are supported.\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"
    " -p {off|on|lo|hi|<value>}  disable (off) or enable (on) clock-profiling using a default\n"
    "                    sampling granularity, or enable clock-profiling implicitly by\n"
    "                    setting the sampling granularity (lo, hi, or a specific value\n"
    "                    in ms); by default clock profiling is enabled.\n"
    "\n"
    " -h {<ctr_def>...,<ctr_n_def>}  enable hardware event counter profiling and select\n"
    "                    the counter(s); to see the supported counters on this system use\n"
    "                    the -h option without other arguments.\n"
    "\n"
    " -o <exp_name>     specify the name for (and path to) the experiment directory; the\n"
    "                    the default path is the current directory.\n"
    "\n"
    " -O <exp_name>     the same as -o, but unlike the -o option, silently overwrite an\n"
    "                    existing experiment directory with the same name.\n"
    "\n"
    " -C <label>        add up to 10 comment labels to the experiment; comments appear in\n"
    "                    the notes section of the header.\n"
    "\n"
    " -j {on|off|<path>} enable (on), or disable (off) Java profiling when the target\n"
    "                     program is a JVM; optionally set the <path> to a non-default JVM;\n"
    "                     the default is \"-j on\".\n"
    "\n"
    " -J <java-args>    specify arguments to the JVM.\n"
    "\n"
    " -t <duration>[m|s]  specify the duration over which to record data; the default unit\n"
    "                      is seconds (s), but can be set to minutes (m).\n"
    "\n"
    " -n                  dry run; display several run-time settings, but do not run the\n"
    "                      target, or collect performance data.\n"
    "\n"
    " -y <signal>[,r]     specify delayed initialization and a pause/resume signal; by default\n"
    "                      the target starts in paused mode; if the optional r keyword is\n"
    "                      provided, start in resumed mode.\n"
    "\n"
    " -F {off|on|=<regex>}  control to follow descendant processes; disable (off), enable (on),\n"
    "                        or collect data on all descendant processes whose name matches the\n"
    "                        specified regular expression; the default is \"-F on\".\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"
    " -S {off|on|<seconds>}  disable (off) or enable (on) periodic sampling of process-wide resource\n"
    "                         utilization; by default sampling occurs every second; use the <seconds>\n"
    "                         option to change this; the default is \"-S on\".\n"
    "\n"
    " -l <signal>       specify a signal that will trigger a sample of process-wide resource utilization.\n"
    "\n"
    " -s <option>[,<API>]  enable synchronization wait tracing; <option> is used to define the specifics\n"
    "                       of the tracing (on, off, <threshold>, or all); <API> is used to select the API:\n"
    "                       \"n\" selects native/Pthreads, \"j\" selects Java, and \"nj\" selects both;\n"
    "                       the default is \"-s off\".\n"
    "\n"
    " -H {off|on|N1[-N2]}  disable (off), or enable (on) heap tracing, or\n"
    "                      specify the heap data collection range. The default is \"-H off\".\n"
    "\n"
    " -i {off|on}        disable (off), or enable (on) I/O tracing; the default is \"-i off\".\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-archive(1), gprofng-display-html(1), "
    "gpgprofng-display-src(1), gprofng-display-text(1)\n"
    "\nReport bugs to <https://sourceware.org/bugzilla/>\n"));
}

void
collect::short_usage ()
{
  if (no_short_usage == 0)
    dbe_write (usage_fd, GTXT ("Run \"%s --help\" for a usage message.\n"), whoami);
}

void
collect::show_hwc_usage ()
{
  usage_fd = 1;
  short_usage ();
  cc->setup_hwc ();
  hwc_usage (false, whoami, NULL);
}

void
collect::writeStr (int f, const char *buf)
{
  if (buf != NULL)
    write (f, buf, strlen (buf));
}
