/* LIBGIMP - The GIMP Library
 * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
 *
 * gimputils.c
 * Copyright (C) 2003  Sven Neumann <sven@gimp.org>
 *
 * This library is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.  If not, see
 * <https://www.gnu.org/licenses/>.
 */

#include "config.h"

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

#ifdef PLATFORM_OSX
#include <AppKit/AppKit.h>
#endif

#ifdef HAVE_EXECINFO_H
/* Allowing backtrace() API. */
#include <execinfo.h>
#endif

#include <gio/gio.h>
#include <glib/gprintf.h>

#if defined(G_OS_WIN32)
# include <windows.h>
# include <shlobj.h>

#else /* G_OS_WIN32 */

/* For waitpid() */
#include <sys/wait.h>
#include <unistd.h>
#include <errno.h>

/* For thread IDs. */
#include <sys/types.h>
#include <sys/syscall.h>

#ifdef HAVE_SYS_PRCTL_H
#include <sys/prctl.h>
#endif

#ifdef HAVE_SYS_THR_H
#include <sys/thr.h>
#endif

#endif /* G_OS_WIN32 */

#include "gimpbasetypes.h"
#include "gimputils.h"

#include "libgimp/libgimp-intl.h"


/**
 * SECTION: gimputils
 * @title: gimputils
 * @short_description: Utilities of general interest
 *
 * Utilities of general interest
 **/

static gboolean gimp_utils_generic_available (const gchar *program,
                                              gint         major,
                                              gint         minor);
static gboolean gimp_utils_gdb_available     (gint         major,
                                              gint         minor);

/**
 * gimp_utf8_strtrim:
 * @str: an UTF-8 encoded string (or %NULL)
 * @max_chars: the maximum number of characters before the string get
 * trimmed
 *
 * Creates a (possibly trimmed) copy of @str. The string is cut if it
 * exceeds @max_chars characters or on the first newline. The fact
 * that the string was trimmed is indicated by appending an ellipsis.
 *
 * Returns: A (possibly trimmed) copy of @str which should be freed
 * using g_free() when it is not needed any longer.
 **/
gchar *
gimp_utf8_strtrim (const gchar *str,
                   gint         max_chars)
{
  /* FIXME: should we make this translatable? */
  const gchar ellipsis[] = "...";
  const gint  e_len      = strlen (ellipsis);

  if (str)
    {
      const gchar *p;
      const gchar *newline = NULL;
      gint         chars   = 0;
      gunichar     unichar;

      for (p = str; *p; p = g_utf8_next_char (p))
        {
          if (++chars > max_chars)
            break;

          unichar = g_utf8_get_char (p);

          switch (g_unichar_break_type (unichar))
            {
            case G_UNICODE_BREAK_MANDATORY:
            case G_UNICODE_BREAK_LINE_FEED:
              newline = p;
              break;
            default:
              continue;
            }

          break;
        }

      if (*p)
        {
          gsize  len     = p - str;
          gchar *trimmed = g_new (gchar, len + e_len + 2);

          memcpy (trimmed, str, len);
          if (newline)
            trimmed[len++] = ' ';

          g_strlcpy (trimmed + len, ellipsis, e_len + 1);

          return trimmed;
        }

      return g_strdup (str);
    }

  return NULL;
}

/**
 * gimp_any_to_utf8:
 * @str:            The string to be converted to UTF-8.
 * @len:            The length of the string, or -1 if the string
 *                  is nul-terminated.
 * @warning_format: The message format for the warning message if conversion
 *                  to UTF-8 fails. See the <function>printf()</function>
 *                  documentation.
 * @...:            The parameters to insert into the format string.
 *
 * This function takes any string (UTF-8 or not) and always returns a valid
 * UTF-8 string.
 *
 * If @str is valid UTF-8, a copy of the string is returned.
 *
 * If UTF-8 validation fails, g_locale_to_utf8() is tried and if it
 * succeeds the resulting string is returned.
 *
 * Otherwise, the portion of @str that is UTF-8, concatenated
 * with "(invalid UTF-8 string)" is returned. If not even the start
 * of @str is valid UTF-8, only "(invalid UTF-8 string)" is returned.
 *
 * Return value: The UTF-8 string as described above.
 **/
gchar *
gimp_any_to_utf8 (const gchar  *str,
                  gssize        len,
                  const gchar  *warning_format,
                  ...)
{
  const gchar *start_invalid;
  gchar       *utf8;

  g_return_val_if_fail (str != NULL, NULL);

  if (g_utf8_validate (str, len, &start_invalid))
    {
      if (len < 0)
        utf8 = g_strdup (str);
      else
        utf8 = g_strndup (str, len);
    }
  else
    {
      utf8 = g_locale_to_utf8 (str, len, NULL, NULL, NULL);
    }

  if (! utf8)
    {
      if (warning_format)
        {
          va_list warning_args;

          va_start (warning_args, warning_format);

          g_logv (G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE,
                  warning_format, warning_args);

          va_end (warning_args);
        }

      if (start_invalid > str)
        {
          gchar *tmp;

          tmp = g_strndup (str, start_invalid - str);
          utf8 = g_strconcat (tmp, " ", _("(invalid UTF-8 string)"), NULL);
          g_free (tmp);
        }
      else
        {
          utf8 = g_strdup (_("(invalid UTF-8 string)"));
        }
    }

  return utf8;
}

/**
 * gimp_filename_to_utf8:
 * @filename: The filename to be converted to UTF-8.
 *
 * Convert a filename in the filesystem's encoding to UTF-8
 * temporarily.  The return value is a pointer to a string that is
 * guaranteed to be valid only during the current iteration of the
 * main loop or until the next call to gimp_filename_to_utf8().
 *
 * The only purpose of this function is to provide an easy way to pass
 * a filename in the filesystem encoding to a function that expects an
 * UTF-8 encoded filename.
 *
 * Return value: A temporarily valid UTF-8 representation of @filename.
 *               This string must not be changed or freed.
 **/
const gchar *
gimp_filename_to_utf8 (const gchar *filename)
{
  /* Simpleminded implementation, but at least allocates just one copy
   * of each translation. Could check if already UTF-8, and if so
   * return filename as is. Could perhaps (re)use a suitably large
   * cyclic buffer, but then would have to verify that all calls
   * really need the return value just for a "short" time.
   */

  static GHashTable *ht = NULL;
  gchar             *filename_utf8;

  if (! filename)
    return NULL;

  if (! ht)
    ht = g_hash_table_new (g_str_hash, g_str_equal);

  filename_utf8 = g_hash_table_lookup (ht, filename);

  if (! filename_utf8)
    {
      filename_utf8 = g_filename_display_name (filename);
      g_hash_table_insert (ht, g_strdup (filename), filename_utf8);
    }

  return filename_utf8;
}

/**
 * gimp_file_get_utf8_name:
 * @file: a #GFile
 *
 * This function works like gimp_filename_to_utf8() and returns
 * a UTF-8 encoded string that does not need to be freed.
 *
 * It converts a #GFile's path or uri to UTF-8 temporarily.  The
 * return value is a pointer to a string that is guaranteed to be
 * valid only during the current iteration of the main loop or until
 * the next call to gimp_file_get_utf8_name().
 *
 * The only purpose of this function is to provide an easy way to pass
 * a #GFile's name to a function that expects an UTF-8 encoded string.
 *
 * See g_file_get_parse_name().
 *
 * Since: 2.10
 *
 * Return value: A temporarily valid UTF-8 representation of @file's name.
 *               This string must not be changed or freed.
 **/
const gchar *
gimp_file_get_utf8_name (GFile *file)
{
  gchar *name;

  g_return_val_if_fail (G_IS_FILE (file), NULL);

  name = g_file_get_parse_name (file);

  g_object_set_data_full (G_OBJECT (file), "gimp-parse-name", name,
                          (GDestroyNotify) g_free);

  return name;
}

/**
 * gimp_file_has_extension:
 * @file:      a #GFile
 * @extension: an ASCII extension
 *
 * This function checks if @file's URI ends with @extension. It behaves
 * like g_str_has_suffix() on g_file_get_uri(), except that the string
 * comparison is done case-insensitively using g_ascii_strcasecmp().
 *
 * Since: 2.10
 *
 * Return value: %TRUE if @file's URI ends with @extension,
 *               %FALSE otherwise.
 **/
gboolean
gimp_file_has_extension (GFile       *file,
                         const gchar *extension)
{
  gchar    *uri;
  gint      uri_len;
  gint      ext_len;
  gboolean  result = FALSE;

  g_return_val_if_fail (G_IS_FILE (file), FALSE);
  g_return_val_if_fail (extension != NULL, FALSE);

  uri = g_file_get_uri (file);

  uri_len = strlen (uri);
  ext_len = strlen (extension);

  if (uri_len && ext_len && (uri_len > ext_len))
    {
      if (g_ascii_strcasecmp (uri + uri_len - ext_len, extension) == 0)
        result = TRUE;
    }

  g_free (uri);

  return result;
}

/**
 * gimp_file_show_in_file_manager:
 * @file:  a #GFile
 * @error: return location for a #GError
 *
 * Shows @file in the system file manager.
 *
 * Since: 2.10
 *
 * Return value: %TRUE on success, %FALSE otherwise. On %FALSE, @error
 *               is set.
 **/
gboolean
gimp_file_show_in_file_manager (GFile   *file,
                                GError **error)
{
  g_return_val_if_fail (G_IS_FILE (file), FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

#if defined(G_OS_WIN32)

  {
    gboolean ret;
    char *filename;
    int n;
    LPWSTR w_filename = NULL;
    ITEMIDLIST *pidl = NULL;

    ret = FALSE;

    /* Calling this function multiple times should do no harm, but it is
       easier to put this here as it needs linking against ole32. */
    CoInitialize (NULL);

    filename = g_file_get_path (file);
    if (!filename)
      {
        g_set_error_literal (error, G_FILE_ERROR, 0,
                             _("File path is NULL"));
        goto out;
      }

    n = MultiByteToWideChar (CP_UTF8, MB_ERR_INVALID_CHARS,
                             filename, -1, NULL, 0);
    if (n == 0)
      {
        g_set_error_literal (error, G_FILE_ERROR, 0,
                             _("Error converting UTF-8 filename to wide char"));
        goto out;
      }

    w_filename = g_malloc_n (n + 1, sizeof (wchar_t));
    n = MultiByteToWideChar (CP_UTF8, MB_ERR_INVALID_CHARS,
                             filename, -1,
                             w_filename, (n + 1) * sizeof (wchar_t));
    if (n == 0)
      {
        g_set_error_literal (error, G_FILE_ERROR, 0,
                             _("Error converting UTF-8 filename to wide char"));
        goto out;
      }

    pidl = ILCreateFromPathW (w_filename);
    if (!pidl)
      {
        g_set_error_literal (error, G_FILE_ERROR, 0,
                             _("ILCreateFromPath() failed"));
        goto out;
      }

    SHOpenFolderAndSelectItems (pidl, 0, NULL, 0);
    ret = TRUE;

  out:
    if (pidl)
      ILFree (pidl);
    g_free (w_filename);
    g_free (filename);

    return ret;
  }

#elif defined(PLATFORM_OSX)

  {
    gchar    *uri;
    NSString *filename;
    NSURL    *url;
    gboolean  retval = TRUE;

    uri = g_file_get_uri (file);
    filename = [NSString stringWithUTF8String:uri];

    url = [NSURL URLWithString:filename];
    if (url)
      {
        NSArray *url_array = [NSArray arrayWithObject:url];

        [[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs:url_array];
      }
    else
      {
        g_set_error (error, G_FILE_ERROR, 0,
                     _("Cannot convert '%s' into a valid NSURL."), uri);
        retval = FALSE;
      }

    g_free (uri);

    return retval;
  }

#else /* UNIX */

  {
    GDBusProxy      *proxy;
    GVariant        *retval;
    GVariantBuilder *builder;
    gchar           *uri;

    proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
                                           G_DBUS_PROXY_FLAGS_NONE,
                                           NULL,
                                           "org.freedesktop.FileManager1",
                                           "/org/freedesktop/FileManager1",
                                           "org.freedesktop.FileManager1",
                                           NULL, error);

    if (! proxy)
      {
        g_prefix_error (error,
                        _("Connecting to org.freedesktop.FileManager1 failed: "));
        return FALSE;
      }

    uri = g_file_get_uri (file);

    builder = g_variant_builder_new (G_VARIANT_TYPE ("as"));
    g_variant_builder_add (builder, "s", uri);

    g_free (uri);

    retval = g_dbus_proxy_call_sync (proxy,
                                     "ShowItems",
                                     g_variant_new ("(ass)",
                                                    builder,
                                                    ""),
                                     G_DBUS_CALL_FLAGS_NONE,
                                     -1, NULL, error);

    g_variant_builder_unref (builder);
    g_object_unref (proxy);

    if (! retval)
      {
        g_prefix_error (error, _("Calling ShowItems failed: "));
        return FALSE;
      }

    g_variant_unref (retval);

    return TRUE;
  }

#endif
}

/**
 * gimp_strip_uline:
 * @str: underline infested string (or %NULL)
 *
 * This function returns a copy of @str stripped of underline
 * characters. This comes in handy when needing to strip mnemonics
 * from menu paths etc.
 *
 * In some languages, mnemonics are handled by adding the mnemonic
 * character in brackets (like "File (_F)"). This function recognizes
 * this construct and removes the whole bracket construction to get
 * rid of the mnemonic (see bug 157561).
 *
 * Return value: A (possibly stripped) copy of @str which should be
 *               freed using g_free() when it is not needed any longer.
 **/
gchar *
gimp_strip_uline (const gchar *str)
{
  gchar    *escaped;
  gchar    *p;
  gboolean  past_bracket = FALSE;

  if (! str)
    return NULL;

  p = escaped = g_strdup (str);

  while (*str)
    {
      if (*str == '_')
        {
          /*  "__" means a literal "_" in the menu path  */
          if (str[1] == '_')
            {
             *p++ = *str++;
             str++;
             continue;
            }

          /*  find the "(_X)" construct and remove it entirely  */
          if (past_bracket && str[1] && *(g_utf8_next_char (str + 1)) == ')')
            {
              str = g_utf8_next_char (str + 1) + 1;
              p--;
            }
          else
            {
              str++;
            }
        }
      else
        {
          past_bracket = (*str == '(');

          *p++ = *str++;
        }
    }

  *p = '\0';

  return escaped;
}

/**
 * gimp_escape_uline:
 * @str: Underline infested string (or %NULL)
 *
 * This function returns a copy of @str with all underline converted
 * to two adjacent underlines. This comes in handy when needing to display
 * strings with underlines (like filenames) in a place that would convert
 * them to mnemonics.
 *
 * Return value: A (possibly escaped) copy of @str which should be
 * freed using g_free() when it is not needed any longer.
 *
 * Since: 2.2
 **/
gchar *
gimp_escape_uline (const gchar *str)
{
  gchar *escaped;
  gchar *p;
  gint   n_ulines = 0;

  if (! str)
    return NULL;

  for (p = (gchar *) str; *p; p++)
    if (*p == '_')
      n_ulines++;

  p = escaped = g_malloc (strlen (str) + n_ulines + 1);

  while (*str)
    {
      if (*str == '_')
        *p++ = '_';

      *p++ = *str++;
    }

  *p = '\0';

  return escaped;
}

/**
 * gimp_canonicalize_identifier:
 * @identifier: The identifier string to canonicalize.
 *
 * Turns any input string into a canonicalized string.
 *
 * Canonical identifiers are e.g. expected by the PDB for procedure
 * and parameter names. Every character of the input string that is
 * not either '-', 'a-z', 'A-Z' or '0-9' will be replaced by a '-'.
 *
 * Return value: The canonicalized identifier. This is a newly
 *               allocated string that should be freed with g_free()
 *               when no longer needed.
 *
 * Since: 2.4
 **/
gchar *
gimp_canonicalize_identifier (const gchar *identifier)
{
  gchar *canonicalized = NULL;

  if (identifier)
    {
      gchar *p;

      canonicalized = g_strdup (identifier);

      for (p = canonicalized; *p != 0; p++)
        {
          gchar c = *p;

          if (c != '-' &&
              (c < '0' || c > '9') &&
              (c < 'A' || c > 'Z') &&
              (c < 'a' || c > 'z'))
            *p = '-';
        }
    }

  return canonicalized;
}

/**
 * gimp_enum_get_desc:
 * @enum_class: a #GEnumClass
 * @value:      a value from @enum_class
 *
 * Retrieves #GimpEnumDesc associated with the given value, or %NULL.
 *
 * Return value: the value's #GimpEnumDesc.
 *
 * Since: 2.2
 **/
GimpEnumDesc *
gimp_enum_get_desc (GEnumClass *enum_class,
                    gint        value)
{
  const GimpEnumDesc *value_desc;

  g_return_val_if_fail (G_IS_ENUM_CLASS (enum_class), NULL);

  value_desc =
    gimp_enum_get_value_descriptions (G_TYPE_FROM_CLASS (enum_class));

  if (value_desc)
    {
      while (value_desc->value_desc)
        {
          if (value_desc->value == value)
            return (GimpEnumDesc *) value_desc;

          value_desc++;
        }
    }

  return NULL;
}

/**
 * gimp_enum_get_value:
 * @enum_type:  the #GType of a registered enum
 * @value:      an integer value
 * @value_name: return location for the value's name (or %NULL)
 * @value_nick: return location for the value's nick (or %NULL)
 * @value_desc: return location for the value's translated description (or %NULL)
 * @value_help: return location for the value's translated help (or %NULL)
 *
 * Checks if @value is valid for the enum registered as @enum_type.
 * If the value exists in that enum, its name, nick and its translated
 * description and help are returned (if @value_name, @value_nick,
 * @value_desc and @value_help are not %NULL).
 *
 * Return value: %TRUE if @value is valid for the @enum_type,
 *               %FALSE otherwise
 *
 * Since: 2.2
 **/
gboolean
gimp_enum_get_value (GType         enum_type,
                     gint          value,
                     const gchar **value_name,
                     const gchar **value_nick,
                     const gchar **value_desc,
                     const gchar **value_help)
{
  GEnumClass *enum_class;
  GEnumValue *enum_value;
  gboolean    success = FALSE;

  g_return_val_if_fail (G_TYPE_IS_ENUM (enum_type), FALSE);

  enum_class = g_type_class_ref (enum_type);
  enum_value = g_enum_get_value (enum_class, value);

  if (enum_value)
    {
      if (value_name)
        *value_name = enum_value->value_name;

      if (value_nick)
        *value_nick = enum_value->value_nick;

      if (value_desc || value_help)
        {
          GimpEnumDesc *enum_desc;

          enum_desc = gimp_enum_get_desc (enum_class, value);

          if (value_desc)
            {
              if (enum_desc && enum_desc->value_desc)
                {
                  const gchar *context;

                  context = gimp_type_get_translation_context (enum_type);

                  if (context)  /*  the new way, using NC_()    */
                    *value_desc = g_dpgettext2 (gimp_type_get_translation_domain (enum_type),
                                                context,
                                                enum_desc->value_desc);
                  else          /*  for backward compatibility  */
                    *value_desc = g_strip_context (enum_desc->value_desc,
                                                   dgettext (gimp_type_get_translation_domain (enum_type),
                                                             enum_desc->value_desc));
                }
              else
                {
                  *value_desc = NULL;
                }
            }

          if (value_help)
            {
              *value_help = ((enum_desc && enum_desc->value_help) ?
                             dgettext (gimp_type_get_translation_domain (enum_type),
                                       enum_desc->value_help) :
                             NULL);
            }
        }

      success = TRUE;
    }

  g_type_class_unref (enum_class);

  return success;
}

/**
 * gimp_enum_value_get_desc:
 * @enum_class: a #GEnumClass
 * @enum_value: a #GEnumValue from @enum_class
 *
 * Retrieves the translated description for a given @enum_value.
 *
 * Return value: the translated description of the enum value
 *
 * Since: 2.2
 **/
const gchar *
gimp_enum_value_get_desc (GEnumClass *enum_class,
                          GEnumValue *enum_value)
{
  GType         type = G_TYPE_FROM_CLASS (enum_class);
  GimpEnumDesc *enum_desc;

  enum_desc = gimp_enum_get_desc (enum_class, enum_value->value);

  if (enum_desc && enum_desc->value_desc)
    {
      const gchar *context;

      context = gimp_type_get_translation_context (type);

      if (context)  /*  the new way, using NC_()    */
        return g_dpgettext2 (gimp_type_get_translation_domain (type),
                             context,
                             enum_desc->value_desc);
      else          /*  for backward compatibility  */
        return g_strip_context (enum_desc->value_desc,
                                dgettext (gimp_type_get_translation_domain (type),
                                          enum_desc->value_desc));
    }

  return enum_value->value_name;
}

/**
 * gimp_enum_value_get_help:
 * @enum_class: a #GEnumClass
 * @enum_value: a #GEnumValue from @enum_class
 *
 * Retrieves the translated help for a given @enum_value.
 *
 * Return value: the translated help of the enum value
 *
 * Since: 2.2
 **/
const gchar *
gimp_enum_value_get_help (GEnumClass *enum_class,
                          GEnumValue *enum_value)
{
  GType         type = G_TYPE_FROM_CLASS (enum_class);
  GimpEnumDesc *enum_desc;

  enum_desc = gimp_enum_get_desc (enum_class, enum_value->value);

  if (enum_desc && enum_desc->value_help)
    return dgettext (gimp_type_get_translation_domain (type),
                     enum_desc->value_help);

  return NULL;
}

/**
 * gimp_enum_value_get_abbrev:
 * @enum_class: a #GEnumClass
 * @enum_value: a #GEnumValue from @enum_class
 *
 * Retrieves the translated abbreviation for a given @enum_value.
 *
 * Return value: the translated abbreviation of the enum value
 *
 * Since: 2.10
 **/
const gchar *
gimp_enum_value_get_abbrev (GEnumClass *enum_class,
                            GEnumValue *enum_value)
{
  GType         type = G_TYPE_FROM_CLASS (enum_class);
  GimpEnumDesc *enum_desc;

  enum_desc = gimp_enum_get_desc (enum_class, enum_value->value);

  if (enum_desc                              &&
      enum_desc[1].value == enum_desc->value &&
      enum_desc[1].value_desc)
    {
      return g_dpgettext2 (gimp_type_get_translation_domain (type),
                           gimp_type_get_translation_context (type),
                           enum_desc[1].value_desc);
    }

  return NULL;
}

/**
 * gimp_flags_get_first_desc:
 * @flags_class: a #GFlagsClass
 * @value:       a value from @flags_class
 *
 * Retrieves the first #GimpFlagsDesc that matches the given value, or %NULL.
 *
 * Return value: the value's #GimpFlagsDesc.
 *
 * Since: 2.2
 **/
GimpFlagsDesc *
gimp_flags_get_first_desc (GFlagsClass *flags_class,
                           guint        value)
{
  const GimpFlagsDesc *value_desc;

  g_return_val_if_fail (G_IS_FLAGS_CLASS (flags_class), NULL);

  value_desc =
    gimp_flags_get_value_descriptions (G_TYPE_FROM_CLASS (flags_class));

  if (value_desc)
    {
      while (value_desc->value_desc)
        {
          if ((value_desc->value & value) == value_desc->value)
            return (GimpFlagsDesc *) value_desc;

          value_desc++;
        }
    }

  return NULL;
}

/**
 * gimp_flags_get_first_value:
 * @flags_type: the #GType of registered flags
 * @value:      an integer value
 * @value_name: return location for the value's name (or %NULL)
 * @value_nick: return location for the value's nick (or %NULL)
 * @value_desc: return location for the value's translated description (or %NULL)
 * @value_help: return location for the value's translated help (or %NULL)
 *
 * Checks if @value is valid for the flags registered as @flags_type.
 * If the value exists in that flags, its name, nick and its
 * translated description and help are returned (if @value_name,
 * @value_nick, @value_desc and @value_help are not %NULL).
 *
 * Return value: %TRUE if @value is valid for the @flags_type,
 *               %FALSE otherwise
 *
 * Since: 2.2
 **/
gboolean
gimp_flags_get_first_value (GType         flags_type,
                            guint         value,
                            const gchar **value_name,
                            const gchar **value_nick,
                            const gchar **value_desc,
                            const gchar **value_help)
{
  GFlagsClass *flags_class;
  GFlagsValue *flags_value;

  g_return_val_if_fail (G_TYPE_IS_FLAGS (flags_type), FALSE);

  flags_class = g_type_class_peek (flags_type);
  flags_value = g_flags_get_first_value (flags_class, value);

  if (flags_value)
    {
      if (value_name)
        *value_name = flags_value->value_name;

      if (value_nick)
        *value_nick = flags_value->value_nick;

      if (value_desc || value_help)
        {
          GimpFlagsDesc *flags_desc;

          flags_desc = gimp_flags_get_first_desc (flags_class, value);

          if (value_desc)
            *value_desc = ((flags_desc && flags_desc->value_desc) ?
                           dgettext (gimp_type_get_translation_domain (flags_type),
                                     flags_desc->value_desc) :
                           NULL);

          if (value_help)
            *value_help = ((flags_desc && flags_desc->value_desc) ?
                           dgettext (gimp_type_get_translation_domain (flags_type),
                                     flags_desc->value_help) :
                           NULL);
        }

      return TRUE;
    }

  return FALSE;
}

/**
 * gimp_flags_value_get_desc:
 * @flags_class: a #GFlagsClass
 * @flags_value: a #GFlagsValue from @flags_class
 *
 * Retrieves the translated description for a given @flags_value.
 *
 * Return value: the translated description of the flags value
 *
 * Since: 2.2
 **/
const gchar *
gimp_flags_value_get_desc (GFlagsClass *flags_class,
                           GFlagsValue *flags_value)
{
  GType         type = G_TYPE_FROM_CLASS (flags_class);
  GimpFlagsDesc *flags_desc;

  flags_desc = gimp_flags_get_first_desc (flags_class, flags_value->value);

  if (flags_desc->value_desc)
    {
      const gchar *context;

      context = gimp_type_get_translation_context (type);

      if (context)  /*  the new way, using NC_()    */
        return g_dpgettext2 (gimp_type_get_translation_domain (type),
                             context,
                             flags_desc->value_desc);
      else          /*  for backward compatibility  */
        return g_strip_context (flags_desc->value_desc,
                                dgettext (gimp_type_get_translation_domain (type),
                                          flags_desc->value_desc));
    }

  return flags_value->value_name;
}

/**
 * gimp_flags_value_get_help:
 * @flags_class: a #GFlagsClass
 * @flags_value: a #GFlagsValue from @flags_class
 *
 * Retrieves the translated help for a given @flags_value.
 *
 * Return value: the translated help of the flags value
 *
 * Since: 2.2
 **/
const gchar *
gimp_flags_value_get_help (GFlagsClass *flags_class,
                           GFlagsValue *flags_value)
{
  GType         type = G_TYPE_FROM_CLASS (flags_class);
  GimpFlagsDesc *flags_desc;

  flags_desc = gimp_flags_get_first_desc (flags_class, flags_value->value);

  if (flags_desc->value_help)
    return dgettext (gimp_type_get_translation_domain (type),
                     flags_desc->value_help);

  return NULL;
}

/**
 * gimp_flags_value_get_abbrev:
 * @flags_class: a #GFlagsClass
 * @flags_value: a #GFlagsValue from @flags_class
 *
 * Retrieves the translated abbreviation for a given @flags_value.
 *
 * Return value: the translated abbreviation of the flags value
 *
 * Since: 2.10
 **/
const gchar *
gimp_flags_value_get_abbrev (GFlagsClass *flags_class,
                             GFlagsValue *flags_value)
{
  GType          type = G_TYPE_FROM_CLASS (flags_class);
  GimpFlagsDesc *flags_desc;

  flags_desc = gimp_flags_get_first_desc (flags_class, flags_value->value);

  if (flags_desc                               &&
      flags_desc[1].value == flags_desc->value &&
      flags_desc[1].value_desc)
    {
      return g_dpgettext2 (gimp_type_get_translation_domain (type),
                           gimp_type_get_translation_context (type),
                           flags_desc[1].value_desc);
    }

  return NULL;
}

/**
 * gimp_stack_trace_available:
 * @optimal: whether we get optimal traces.
 *
 * Returns #TRUE if we have dependencies to generate backtraces. If
 * @optimal is #TRUE, the function will return #TRUE only when we
 * are able to generate optimal traces (i.e. with GDB or LLDB);
 * otherwise we return #TRUE even if only backtrace() API is available.
 *
 * On Win32, we return TRUE if Dr. Mingw is built-in, FALSE otherwise.
 *
 * Note: this function is not crash-safe, i.e. you should not try to use
 * it in a callback when the program is already crashing. In such a
 * case, call gimp_stack_trace_print() or gimp_stack_trace_query()
 * directly.
 *
 * Since: 2.10
 **/
gboolean
gimp_stack_trace_available (gboolean optimal)
{
#ifndef G_OS_WIN32
  gchar    *lld_path = NULL;
  gboolean  has_lldb = FALSE;

  /* Similarly to gdb, we could check for lldb by calling:
   * gimp_utils_generic_available ("lldb", major, minor).
   * We don't do so on purpose because on macOS, when lldb is absent, it
   * triggers a popup asking to install Xcode. So instead, we just
   * search for the executable in path.
   * This is the reason why this function is not crash-safe, since
   * g_find_program_in_path() allocates memory.
   * See issue #1999.
   */
  lld_path = g_find_program_in_path ("lldb");
  if (lld_path)
    {
      has_lldb = TRUE;
      g_free (lld_path);
    }

  if (gimp_utils_gdb_available (7, 0) || has_lldb)
    return TRUE;
#ifdef HAVE_EXECINFO_H
  if (! optimal)
    return TRUE;
#endif
#else /* G_OS_WIN32 */
#ifdef HAVE_EXCHNDL
  return TRUE;
#endif
#endif /* G_OS_WIN32 */
  return FALSE;
}

/**
 * gimp_stack_trace_print:
 * @prog_name: the program to attach to.
 * @stream: a #FILE * stream.
 * @trace: location to store a newly allocated string of the trace.
 *
 * Attempts to generate a stack trace at current code position in
 * @prog_name. @prog_name is mostly a helper and can be set to NULL.
 * Nevertheless if set, it has to be the current program name (argv[0]).
 * This function is not meant to generate stack trace for third-party
 * programs, and will attach the current process id only.
 * Internally, this function uses `gdb` or `lldb` if they are available,
 * or the stacktrace() API on platforms where it is available. It always
 * fails on Win32.
 *
 * The stack trace, once generated, will either be printed to @stream or
 * returned as a newly allocated string in @trace, if not #NULL.
 *
 * In some error cases (e.g. segmentation fault), trying to allocate
 * more memory will trigger more segmentation faults and therefore loop
 * our error handling (which is just wrong). Therefore printing to a
 * file description is an implementation without any memory allocation.

 * Return value: #TRUE if a stack trace could be generated, #FALSE
 * otherwise.
 *
 * Since: 2.10
 **/
gboolean
gimp_stack_trace_print (const gchar   *prog_name,
                        gpointer      stream,
                        gchar       **trace)
{
  gboolean stack_printed = FALSE;

  /* This works only on UNIX systems. */
#ifndef G_OS_WIN32
  GString *gtrace = NULL;
  gchar    gimp_pid[16];
  gchar    buffer[256];
  ssize_t  read_n;
  int      sync_fd[2];
  int      out_fd[2];
  pid_t    fork_pid;
  pid_t    pid = getpid();
  gint     eintr_count = 0;
#if defined(G_OS_WIN32)
  DWORD    tid = GetCurrentThreadId ();
#elif defined(PLATFORM_OSX)
  uint64   tid64;
  long     tid;

  pthread_threadid_np (NULL, &tid64);
  tid = (long) tid64;
#elif defined(SYS_gettid)
  long     tid = syscall (SYS_gettid);
#elif defined(HAVE_THR_SELF)
  long     tid = 0;
  thr_self (&tid);
#endif

  g_snprintf (gimp_pid, 16, "%u", (guint) pid);

  if (pipe (sync_fd) == -1)
    {
      return FALSE;
    }

  if (pipe (out_fd) == -1)
    {
      close (sync_fd[0]);
      close (sync_fd[1]);

      return FALSE;
    }

  fork_pid = fork ();
  if (fork_pid == 0)
    {
      /* Child process. */
      gchar *args[9] = { "gdb", "-batch",
                         "-ex", "info threads",
                         "-ex", "thread apply all backtrace full",
                         (gchar *) prog_name, NULL, NULL };

      if (prog_name == NULL)
        args[6] = "-p";

      args[7] = gimp_pid;

      /* Wait until the parent enabled us to ptrace it. */
      {
        gchar dummy;

        close (sync_fd[1]);
        while (read (sync_fd[0], &dummy, 1) < 0 && errno == EINTR);
        close (sync_fd[0]);
      }

      /* Redirect the debugger output. */
      dup2 (out_fd[1], STDOUT_FILENO);
      close (out_fd[0]);
      close (out_fd[1]);

      /* Run GDB if version 7.0 or over. Why I do such a check is that
       * it turns out older versions may not only fail, but also have
       * very undesirable side effects like terminating the debugged
       * program, at least on FreeBSD where GDB 6.1 is apparently
       * installed by default on the stable release at day of writing.
       * See bug 793514. */
      if (! gimp_utils_gdb_available (7, 0) ||
          execvp (args[0], args) == -1)
        {
          /* LLDB as alternative if the GDB call failed or if it was in
           * a too-old version. */
          gchar *args_lldb[15] = { "lldb", "--attach-pid", NULL, "--batch",
                                   "--one-line", "thread list",
                                   "--one-line", "thread backtrace all",
                                   "--one-line", "bt all",
                                   "--one-line-on-crash", "bt",
                                   "--one-line-on-crash", "quit", NULL };

          args_lldb[2] = gimp_pid;

          execvp (args_lldb[0], args_lldb);
        }

      _exit (0);
    }
  else if (fork_pid > 0)
    {
      /* Main process */
      int status;

      /* Allow the child to ptrace us, and signal it to start. */
      close (sync_fd[0]);
#ifdef PR_SET_PTRACER
      prctl (PR_SET_PTRACER, fork_pid, 0, 0, 0);
#endif
      close (sync_fd[1]);

      /* It is important to close the writing side of the pipe, otherwise
       * the read() will wait forever without getting the information that
       * writing is finished.
       */
      close (out_fd[1]);

      while ((read_n = read (out_fd[0], buffer, 256)) != 0)
        {
          if (read_n < 0)
            {
              /* LLDB on macOS seems to trigger a few EINTR error (see
               * !13), though read() finally ends up working later. So
               * let's not make this error fatal, and instead try again.
               * Yet to avoid infinite loop (in case the error really
               * happens at every call), we abandon after a few
               * consecutive errors.
               */
              if (errno == EINTR && eintr_count <= 5)
                {
                  eintr_count++;
                  continue;
                }
              break;
            }
          eintr_count = 0;
          if (! stack_printed)
            {
#if defined(PLATFORM_OSX)
              if (stream)
                g_fprintf (stream,
                           "\n# Stack traces obtained from PID %d - Thread 0x%lx #\n\n",
                           pid, tid);
#elif defined(G_OS_WIN32) || defined(SYS_gettid) || defined(HAVE_THR_SELF)
              if (stream)
                g_fprintf (stream,
                           "\n# Stack traces obtained from PID %d - Thread %lu #\n\n",
                           pid, tid);
#endif
              if (trace)
                {
                  gtrace = g_string_new (NULL);
#if defined(PLATFORM_OSX)
                  g_string_printf (gtrace,
                                   "\n# Stack traces obtained from PID %d - Thread 0x%lx #\n\n",
                                   pid, tid);
#elif defined(G_OS_WIN32) || defined(SYS_gettid) || defined(HAVE_THR_SELF)
                  g_string_printf (gtrace,
                                   "\n# Stack traces obtained from PID %d - Thread %lu #\n\n",
                                   pid, tid);
#endif
                }
            }
          /* It's hard to know if the debugger was found since it
           * happened in the child. Let's just assume that any output
           * means it succeeded.
           */
          stack_printed = TRUE;

          buffer[read_n] = '\0';
          if (stream)
            g_fprintf (stream, "%s", buffer);
          if (trace)
            g_string_append (gtrace, (const gchar *) buffer);
        }
      close (out_fd[0]);

#ifdef PR_SET_PTRACER
      /* Clear ptrace permission set above */
      prctl (PR_SET_PTRACER, 0, 0, 0, 0);
#endif

      waitpid (fork_pid, &status, 0);
    }
  /* else if (fork_pid == (pid_t) -1)
   * Fork failed!
   * Just continue, maybe the backtrace() API will succeed.
   */

#ifdef HAVE_EXECINFO_H
  if (! stack_printed)
    {
      /* As a last resort, try using the backtrace() Linux API. It is a bit
       * less fancy than gdb or lldb, which is why it is not given priority.
       */
      void *bt_buf[100];
      int   n_symbols;

      n_symbols = backtrace (bt_buf, 100);
      if (trace && n_symbols)
        {
          char **symbols;
          int    i;

          symbols = backtrace_symbols (bt_buf, n_symbols);
          if (symbols)
            {
              for (i = 0; i < n_symbols; i++)
                {
                  if (stream)
                    g_fprintf (stream, "%s\n", (const gchar *) symbols[i]);
                  if (trace)
                    {
                      if (! gtrace)
                        gtrace = g_string_new (NULL);
                      g_string_append (gtrace,
                                       (const gchar *) symbols[i]);
                      g_string_append_c (gtrace, '\n');
                    }
                }
              free (symbols);
            }
        }
      else if (n_symbols)
        {
          /* This allows to generate traces without memory allocation.
           * In some cases, this is necessary, especially during
           * segfault-type crashes.
           */
          backtrace_symbols_fd (bt_buf, n_symbols, fileno ((FILE *) stream));
        }
      stack_printed = (n_symbols > 0);
    }
#endif /* HAVE_EXECINFO_H */

  if (trace)
    {
      if (gtrace)
        *trace = g_string_free (gtrace, FALSE);
      else
        *trace = NULL;
    }
#endif /* G_OS_WIN32 */

  return stack_printed;
}

/**
 * gimp_stack_trace_query:
 * @prog_name: the program to attach to.
 *
 * This is mostly the same as g_on_error_query() except that we use our
 * own backtrace function, much more complete.
 * @prog_name must be the current program name (argv[0]).
 * It does nothing on Win32.
 *
 * Since: 2.10
 **/
void
gimp_stack_trace_query (const gchar *prog_name)
{
#ifndef G_OS_WIN32
  gchar buf[16];

 retry:

  g_fprintf (stdout,
             "%s (pid:%u): %s: ",
             prog_name,
             (guint) getpid (),
             "[E]xit, show [S]tack trace or [P]roceed");
  fflush (stdout);

  if (isatty(0) && isatty(1))
    fgets (buf, 8, stdin);
  else
    strcpy (buf, "E\n");

  if ((buf[0] == 'E' || buf[0] == 'e')
      && buf[1] == '\n')
    _exit (0);
  else if ((buf[0] == 'P' || buf[0] == 'p')
           && buf[1] == '\n')
    return;
  else if ((buf[0] == 'S' || buf[0] == 's')
           && buf[1] == '\n')
    {
      if (! gimp_stack_trace_print (prog_name, stdout, NULL))
        g_fprintf (stderr, "%s\n", "Stack trace not available on your system.");
      goto retry;
    }
  else
    goto retry;
#endif
}


/* Private functions. */

static gboolean
gimp_utils_generic_available (const gchar *program,
                              gint         major,
                              gint         minor)
{
#ifndef G_OS_WIN32
  pid_t pid;
  int   out_fd[2];

  if (pipe (out_fd) == -1)
    {
      return FALSE;
    }

  /* XXX: I don't use g_spawn_sync() or similar glib functions because
   * to read the contents of the stdout, these functions would allocate
   * memory dynamically. As we know, when debugging crashes, this is a
   * definite blocker. So instead I simply use a buffer on the stack
   * with a lower level fork() call.
   */
  pid = fork ();
  if (pid == 0)
    {
      /* Child process. */
      gchar *args[3] = { (gchar *) program, "--version", NULL };

      /* Redirect the debugger output. */
      dup2 (out_fd[1], STDOUT_FILENO);
      close (out_fd[0]);
      close (out_fd[1]);

      /* Run version check. */
      execvp (args[0], args);
      _exit (-1);
    }
  else if (pid > 0)
    {
      /* Main process */
      gchar    buffer[256];
      ssize_t  read_n;
      int      status;
      gint     installed_major = 0;
      gint     installed_minor = 0;
      gboolean major_reading = FALSE;
      gboolean minor_reading = FALSE;
      gint     i;
      gchar    c;

      waitpid (pid, &status, 0);

      if (! WIFEXITED (status) || WEXITSTATUS (status) != 0)
        return FALSE;

      /* It is important to close the writing side of the pipe, otherwise
       * the read() will wait forever without getting the information that
       * writing is finished.
       */
      close (out_fd[1]);

      /* I could loop forever until EOL, but I am pretty sure the
       * version information is stored on the first line and one call to
       * read() with 256 characters should be more than enough.
       */
      read_n = read (out_fd[0], buffer, 256);

      /* This is quite a very stupid parser. I only look for the first
       * numbers and consider them as version information. This works
       * fine for both GDB and LLDB as far as I can see for the output
       * of `${program} --version` but this should obviously not be
       * considered as a *really* generic version test.
       */
      for (i = 0; i < read_n; i++)
        {
          c = buffer[i];
          if (c >= '0' && c <= '9')
            {
              if (minor_reading)
                {
                  installed_minor = 10 * installed_minor + (c - '0');
                }
              else
                {
                  major_reading = TRUE;
                  installed_major = 10 * installed_major + (c - '0');
                }
            }
          else if (c == '.')
            {
              if (major_reading)
                {
                  minor_reading = TRUE;
                  major_reading = FALSE;
                }
              else if (minor_reading)
                {
                  break;
                }
            }
          else if (c == '\n')
            {
              /* Version information should be in the first line. */
              break;
            }
        }
      close (out_fd[0]);

      return (installed_major > 0 &&
              (installed_major > major ||
               (installed_major == major && installed_minor >= minor)));
    }
#endif

  /* Fork failed, or Win32. */
  return FALSE;
}

static gboolean
gimp_utils_gdb_available (gint major,
                          gint minor)
{
  return gimp_utils_generic_available ("gdb", major, minor);
}
