/* Copyright (C) 2018-2019 Free Software Foundation, Inc.
   Contributed by Jakub Jelinek <jakub@redhat.com>.

   This file is part of the GNU Offloading and Multi Processing Library
   (libgomp).

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

   Libgomp 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 "libgomp.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_INTTYPES_H
# include <inttypes.h>  /* For PRIx64.  */
#endif
#ifdef HAVE_UNAME
#include <sys/utsname.h>
#endif

bool
gomp_print_string (const char *str, size_t len)
{
  return fwrite (str, 1, len, stderr) != len;
}

void
gomp_set_affinity_format (const char *format, size_t len)
{
  if (len < gomp_affinity_format_len)
    memcpy (gomp_affinity_format_var, format, len);
  else
    {
      char *p;
      if (gomp_affinity_format_len)
	p = gomp_realloc (gomp_affinity_format_var, len + 1);
      else
	p = gomp_malloc (len + 1);
      memcpy (p, format, len);
      gomp_affinity_format_var = p;
      gomp_affinity_format_len = len + 1;
    }
  gomp_affinity_format_var[len] = '\0';
}

void
omp_set_affinity_format (const char *format)
{
  gomp_set_affinity_format (format, strlen (format));
}

size_t
omp_get_affinity_format (char *buffer, size_t size)
{
  size_t len = strlen (gomp_affinity_format_var);
  if (size)
    {
      if (len < size)
	memcpy (buffer, gomp_affinity_format_var, len + 1);
      else
	{
	  memcpy (buffer, gomp_affinity_format_var, size - 1);
	  buffer[size - 1] = '\0';
	}
    }
  return len;
}

void
gomp_display_string (char *buffer, size_t size, size_t *ret,
		     const char *str, size_t len)
{
  size_t r = *ret;
  if (size && r < size)
    {
      size_t l = len;
      if (size - r < len)
	l = size - r;
      memcpy (buffer + r, str, l);
    }
  *ret += len;
  if (__builtin_expect (r > *ret, 0))
    gomp_fatal ("overflow in omp_capture_affinity");
}

static void
gomp_display_repeat (char *buffer, size_t size, size_t *ret,
		     char c, size_t len)
{
  size_t r = *ret;
  if (size && r < size)
    {
      size_t l = len;
      if (size - r < len)
	l = size - r;
      memset (buffer + r, c, l);
    }
  *ret += len;
  if (__builtin_expect (r > *ret, 0))
    gomp_fatal ("overflow in omp_capture_affinity");
}

static void
gomp_display_num (char *buffer, size_t size, size_t *ret,
		  bool zero, bool right, size_t sz, char *buf)
{
  size_t l = strlen (buf);
  if (sz == (size_t) -1 || l >= sz)
    {
      gomp_display_string (buffer, size, ret, buf, l);
      return;
    }
  if (zero)
    {
      if (buf[0] == '-')
	gomp_display_string (buffer, size, ret, buf, 1);
      else if (buf[0] == '0' && buf[1] == 'x')
	gomp_display_string (buffer, size, ret, buf, 2);
      gomp_display_repeat (buffer, size, ret, '0', sz - l);
      if (buf[0] == '-')
	gomp_display_string (buffer, size, ret, buf + 1, l - 1);
      else if (buf[0] == '0' && buf[1] == 'x')
	gomp_display_string (buffer, size, ret, buf + 2, l - 2);
      else
	gomp_display_string (buffer, size, ret, buf, l);
    }
  else if (right)
    {
      gomp_display_repeat (buffer, size, ret, ' ', sz - l);
      gomp_display_string (buffer, size, ret, buf, l);
    }
  else
    {
      gomp_display_string (buffer, size, ret, buf, l);
      gomp_display_repeat (buffer, size, ret, ' ', sz - l);
    }
}

static void
gomp_display_int (char *buffer, size_t size, size_t *ret,
		  bool zero, bool right, size_t sz, int num)
{
  char buf[3 * sizeof (int) + 2];
  sprintf (buf, "%d", num);
  gomp_display_num (buffer, size, ret, zero, right, sz, buf);
}

static void
gomp_display_string_len (char *buffer, size_t size, size_t *ret,
			 bool right, size_t sz, char *str, size_t len)
{
  if (sz == (size_t) -1 || len >= sz)
    {
      gomp_display_string (buffer, size, ret, str, len);
      return;
    }

  if (right)
    {
      gomp_display_repeat (buffer, size, ret, ' ', sz - len);
      gomp_display_string (buffer, size, ret, str, len);
    }
  else
    {
      gomp_display_string (buffer, size, ret, str, len);
      gomp_display_repeat (buffer, size, ret, ' ', sz - len);
    }
}

static void
gomp_display_hostname (char *buffer, size_t size, size_t *ret,
		       bool right, size_t sz)
{
#ifdef HAVE_GETHOSTNAME
  {
    char buf[256];
    char *b = buf;
    size_t len = 256;
    do
      {
	b[len - 1] = '\0';
	if (gethostname (b, len - 1) == 0)
	  {
	    size_t l = strlen (b);
	    if (l < len - 1)
	      {
		gomp_display_string_len (buffer, size, ret,
					 right, sz, b, l);
		if (b != buf)
		  free (b);
		return;
	      }
	  }
	if (len == 1048576)
	  break;
	len = len * 2;
	if (len == 512)
	  b = gomp_malloc (len);
	else
	  b = gomp_realloc (b, len);
      }
    while (1);
    if (b != buf)
      free (b);
  }
#endif
#ifdef HAVE_UNAME
  {
    struct utsname buf;
    if (uname (&buf) == 0)
      {
	gomp_display_string_len (buffer, size, ret, right, sz,
				 buf.nodename, strlen (buf.nodename));
	return;
      }
  }
#endif
  gomp_display_string_len (buffer, size, ret, right, sz, "node", 4);
}

struct affinity_types_struct {
  char long_str[18];
  char long_len;
  char short_c; };

static struct affinity_types_struct affinity_types[] =
{
#define AFFINITY_TYPE(l, s) \
  { #l, sizeof (#l) - 1, s }
  AFFINITY_TYPE (team_num, 't'),
  AFFINITY_TYPE (num_teams, 'T'),
  AFFINITY_TYPE (nesting_level, 'L'),
  AFFINITY_TYPE (thread_num, 'n'),
  AFFINITY_TYPE (num_threads, 'N'),
  AFFINITY_TYPE (ancestor_tnum, 'a'),
  AFFINITY_TYPE (host, 'H'),
  AFFINITY_TYPE (process_id, 'P'),
  AFFINITY_TYPE (native_thread_id, 'i'),
  AFFINITY_TYPE (thread_affinity, 'A')
#undef AFFINITY_TYPE
};

size_t
gomp_display_affinity (char *buffer, size_t size,
		       const char *format, gomp_thread_handle handle,
		       struct gomp_team_state *ts, unsigned int place)
{
  size_t ret = 0;
  do
    {
      const char *p = strchr (format, '%');
      bool zero = false;
      bool right = false;
      size_t sz = -1;
      char c;
      int val;
      if (p == NULL)
	p = strchr (format, '\0');
      if (p != format)
	gomp_display_string (buffer, size, &ret,
			     format, p - format);
      if (*p == '\0')
	break;
      p++;
      if (*p == '%')
	{
	  gomp_display_string (buffer, size, &ret, "%", 1);
	  format = p + 1;
	  continue;
	}
      if (*p == '0')
	{
	  zero = true;
	  p++;
	  if (*p != '.')
	    gomp_fatal ("leading zero not followed by dot in affinity format");
	}
      if (*p == '.')
	{
	  right = true;
	  p++;
	}
      if (*p >= '1' && *p <= '9')
	{
	  char *end;
	  sz = strtoul (p, &end, 10);
	  p = end;
	}
      else if (zero || right)
	gomp_fatal ("leading zero or right justification in affinity format "
		    "requires size");
      c = *p;
      if (c == '{')
	{
	  int i;
	  for (i = 0;
	       i < sizeof (affinity_types) / sizeof (affinity_types[0]); ++i)
	    if (strncmp (p + 1, affinity_types[i].long_str,
			 affinity_types[i].long_len) == 0
		&& p[affinity_types[i].long_len + 1] == '}')
	      {
		c = affinity_types[i].short_c;
		p += affinity_types[i].long_len + 1;
		break;
	      }
	  if (c == '{')
	    {
	      char *q = strchr (p + 1, '}');
	      if (q)
		gomp_fatal ("unsupported long type name '%.*s' in affinity "
			    "format", (int) (q - (p + 1)), p + 1);
	      else
		gomp_fatal ("unterminated long type name '%s' in affinity "
			    "format", p + 1);
	    }
	}
      switch (c)
	{
	case 't':
	  val = omp_get_team_num ();
	  goto do_int;
	case 'T':
	  val = omp_get_num_teams ();
	  goto do_int;
	case 'L':
	  val = ts->level;
	  goto do_int;
	case 'n':
	  val = ts->team_id;
	  goto do_int;
	case 'N':
	  val = ts->team ? ts->team->nthreads : 1;
	  goto do_int;
	case 'a':
	  val = ts->team ? ts->team->prev_ts.team_id : -1;
	  goto do_int;
	case 'H':
	  gomp_display_hostname (buffer, size, &ret, right, sz);
	  break;
	case 'P':
#ifdef HAVE_GETPID
	  val = getpid ();
#else
	  val = 0;
#endif
	  goto do_int;
	case 'i':
#if defined(LIBGOMP_USE_PTHREADS) && defined(__GNUC__)
	  {
	    char buf[3 * (sizeof (handle) + sizeof (uintptr_t) + sizeof (int))
		     + 4];
	    /* This macro returns expr unmodified for integral or pointer
	       types and 0 for anything else (e.g. aggregates).  */
#define gomp_nonaggregate(expr) \
  __builtin_choose_expr (__builtin_classify_type (expr) == 1		    \
			 || __builtin_classify_type (expr) == 5, expr, 0)
	    /* This macro returns expr unmodified for integral types,
	       (uintptr_t) (expr) for pointer types and 0 for anything else
	       (e.g. aggregates).  */
#define gomp_integral(expr) \
  __builtin_choose_expr (__builtin_classify_type (expr) == 5,		    \
			 (uintptr_t) gomp_nonaggregate (expr),		    \
			 gomp_nonaggregate (expr))

	    if (sizeof (gomp_integral (handle)) == sizeof (unsigned long))
	      sprintf (buf, "0x%lx", (unsigned long) gomp_integral (handle));
#if defined (HAVE_INTTYPES_H) && defined (PRIx64)
	    else if (sizeof (gomp_integral (handle)) == sizeof (uint64_t))
	      sprintf (buf, "0x%" PRIx64, (uint64_t) gomp_integral (handle));
#else
	    else if (sizeof (gomp_integral (handle))
		     == sizeof (unsigned long long))
	      sprintf (buf, "0x%llx",
		       (unsigned long long) gomp_integral (handle));
#endif
	    else
	      sprintf (buf, "0x%x", (unsigned int) gomp_integral (handle));
	    gomp_display_num (buffer, size, &ret, zero, right, sz, buf);
	    break;
	  }
#else
	  val = 0;
	  goto do_int;
#endif
	case 'A':
	  if (sz == (size_t) -1)
	    gomp_display_affinity_place (buffer, size, &ret,
					 place - 1);
	  else if (right)
	    {
	      size_t len = 0;
	      gomp_display_affinity_place (NULL, 0, &len, place - 1);
	      if (len < sz)
		gomp_display_repeat (buffer, size, &ret, ' ', sz - len);
	      gomp_display_affinity_place (buffer, size, &ret, place - 1);
	    }
	  else
	    {
	      size_t start = ret;
	      gomp_display_affinity_place (buffer, size, &ret, place - 1);
	      if (ret - start < sz)
		gomp_display_repeat (buffer, size, &ret, ' ', sz - (ret - start));
	    }
	  break;
	do_int:
	  gomp_display_int (buffer, size, &ret, zero, right, sz, val);
	  break;
	default:
	  gomp_fatal ("unsupported type %c in affinity format", c);
	}
      format = p + 1;
    }
  while (1);
  return ret;
}

size_t
omp_capture_affinity (char *buffer, size_t size, const char *format)
{
  struct gomp_thread *thr = gomp_thread ();
  size_t ret
    = gomp_display_affinity (buffer, size,
			     format && *format
			     ? format : gomp_affinity_format_var,
			     gomp_thread_self (), &thr->ts, thr->place);
  if (size)
    {
      if (ret >= size)
	buffer[size - 1] = '\0';
      else
	buffer[ret] = '\0';
    }
  return ret;
}
ialias (omp_capture_affinity)

void
omp_display_affinity (const char *format)
{
  char buf[512];
  char *b;
  size_t ret = ialias_call (omp_capture_affinity) (buf, sizeof buf, format);
  if (ret < sizeof buf)
    {
      buf[ret] = '\n';
      gomp_print_string (buf, ret + 1);
      return;
    }
  b = gomp_malloc (ret + 1);
  ialias_call (omp_capture_affinity) (b, ret + 1, format);
  b[ret] = '\n';
  gomp_print_string (b, ret + 1);
  free (b);
}

void
gomp_display_affinity_thread (gomp_thread_handle handle,
			      struct gomp_team_state *ts, unsigned int place)
{
  char buf[512];
  char *b;
  size_t ret = gomp_display_affinity (buf, sizeof buf, gomp_affinity_format_var,
				      handle, ts, place);
  if (ret < sizeof buf)
    {
      buf[ret] = '\n';
      gomp_print_string (buf, ret + 1);
      return;
    }
  b = gomp_malloc (ret + 1);
  gomp_display_affinity (b, ret + 1, gomp_affinity_format_var,
  			 handle, ts, place);
  b[ret] = '\n';
  gomp_print_string (b, ret + 1);
  free (b);
}
