/* Copyright (C) 2006-2014 Free Software Foundation, Inc.
   Contributed by François-Xavier Coudert

This file is part of the GNU Fortran runtime library (libgfortran).

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

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

Under Section 7 of GPL version 3, you are granted additional
permissions described in the GCC Runtime Library Exception, version
3.1, as published by the Free Software Foundation.

You should have received a copy of the GNU General Public License and
a copy of the GCC Runtime Library Exception along with this program;
see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
<http://www.gnu.org/licenses/>.  */

#include "libgfortran.h"

#include <string.h>
#include <stdlib.h>

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#ifdef HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif

#include <limits.h>

#include "unwind.h"


/* Macros for common sets of capabilities: can we fork and exec, and
   can we use pipes to communicate with the subprocess.  */
#define CAN_FORK (defined(HAVE_FORK) && defined(HAVE_EXECVE) \
		  && defined(HAVE_WAIT))
#define CAN_PIPE (CAN_FORK && defined(HAVE_PIPE) \
		  && defined(HAVE_DUP2) && defined(HAVE_CLOSE))

#ifndef PATH_MAX
#define PATH_MAX 4096
#endif


/* GDB style #NUM index for each stack frame.  */

static void 
bt_header (int num)
{
  st_printf ("#%d  ", num);
}


/* fgets()-like function that reads a line from a fd, without
   needing to malloc() a buffer, and does not use locks, hence should
   be async-signal-safe.  */

static char *
fd_gets (char *s, int size, int fd)
{
  for (int i = 0; i < size; i++)
    {
      char c;
      ssize_t nread = read (fd, &c, 1);
      if (nread == 1)
	{
	  s[i] = c;
	  if (c == '\n')
	    {
	      if (i + 1 < size)
		s[i+1] = '\0';
	      else
		s[i] = '\0';
	      break;
	    }
	}
      else
	{
	  s[i] = '\0';
	  if (i == 0)
	    return NULL;
	  break;
	}
    }
  return s;
}


extern char *addr2line_path;

/* Struct containing backtrace state.  */
typedef struct
{
  int frame_number;
  int direct_output;
  int outfd;
  int infd;
  int error;
}
bt_state;

static _Unwind_Reason_Code
trace_function (struct _Unwind_Context *context, void *state_ptr)
{
  bt_state* state = (bt_state*) state_ptr;
  _Unwind_Ptr ip;
#ifdef HAVE_GETIPINFO
  int ip_before_insn = 0;
  ip = _Unwind_GetIPInfo (context, &ip_before_insn);
  
  /* If the unwinder gave us a 'return' address, roll it back a little
     to ensure we get the correct line number for the call itself.  */
  if (! ip_before_insn)
    --ip;
#else  
  ip = _Unwind_GetIP (context);
#endif

  if (state->direct_output)
    {
      bt_header(state->frame_number);
      st_printf ("%p\n", (void*) ip);
    }
  else
    {
      char addr_buf[GFC_XTOA_BUF_SIZE], func[1024], file[PATH_MAX];
      char *p;
      const char* addr = gfc_xtoa (ip, addr_buf, sizeof (addr_buf));
      write (state->outfd, addr, strlen (addr));
      write (state->outfd, "\n", 1);

      if (! fd_gets (func, sizeof(func), state->infd))
	{
	  state->error = 1;
	  goto done;
	}
      if (! fd_gets (file, sizeof(file), state->infd))
	{
	  state->error = 1;
	  goto done;
	}
	    
	for (p = func; *p != '\n' && *p != '\r'; p++)
	  ;
	*p = '\0';
	
	/* _start is a setup routine that calls main(), and main() is
	   the frontend routine that calls some setup stuff and then
	   calls MAIN__, so at this point we should stop.  */
	if (strcmp (func, "_start") == 0 || strcmp (func, "main") == 0)
	  return _URC_END_OF_STACK;
	
	bt_header (state->frame_number);
	estr_write ("0x");
	estr_write (addr);

	if (func[0] != '?' && func[1] != '?')
	  {
	    estr_write (" in ");
	    estr_write (func);
	  }
	
	if (strncmp (file, "??", 2) == 0)
	  estr_write ("\n");
	else
	  {
	    estr_write (" at ");
	    estr_write (file);
	  }
    }

 done:

  state->frame_number++;
  
  return _URC_NO_REASON;
}


/* Display the backtrace.  */

void
backtrace (void)
{
  bt_state state;
  state.frame_number = 0;
  state.error = 0;

#if CAN_PIPE

  if (addr2line_path == NULL)
    goto fallback_noerr;

  /* We attempt to extract file and line information from addr2line.  */
  do
  {
    /* Local variables.  */
    int f[2], pid, inp[2];

    /* Don't output an error message if something goes wrong, we'll simply
       fall back to printing the addresses.  */
    if (pipe (f) != 0)
      break;
    if (pipe (inp) != 0)
      break;
    if ((pid = fork ()) == -1)
      break;

    if (pid == 0)
      {
	/* Child process.  */
#define NUM_FIXEDARGS 7
	char *arg[NUM_FIXEDARGS];
	char *newenv[] = { NULL };

	close (f[0]);

	close (inp[1]);
	if (dup2 (inp[0], STDIN_FILENO) == -1)
	  _exit (1);
	close (inp[0]);

	close (STDERR_FILENO);

	if (dup2 (f[1], STDOUT_FILENO) == -1)
	  _exit (1);
	close (f[1]);

	arg[0] = addr2line_path;
	arg[1] = (char *) "-e";
	arg[2] = full_exe_path ();
	arg[3] = (char *) "-f";
	arg[4] = (char *) "-s";
	arg[5] = (char *) "-C";
	arg[6] = NULL;
	execve (addr2line_path, arg, newenv);
	_exit (1);
#undef NUM_FIXEDARGS
      }

    /* Father process.  */
    close (f[1]);
    close (inp[0]);

    state.outfd = inp[1];
    state.infd = f[0];
    state.direct_output = 0;
    _Unwind_Backtrace (trace_function, &state);
    if (state.error)
      goto fallback;
    close (inp[1]);
    close (f[0]);
    wait (NULL);
    return;

fallback:
    estr_write ("** Something went wrong while running addr2line. **\n"
		"** Falling back to a simpler backtrace scheme. **\n");
  }
  while (0);

fallback_noerr:
#endif /* CAN_PIPE */

  /* Fallback to the simple backtrace without addr2line.  */
  state.direct_output = 1;
  _Unwind_Backtrace (trace_function, &state);
}
iexport(backtrace);
