text: /*
   * Copyright © 2012 Red Hat, Inc
   *
   * 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 2.1 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 <http://www.gnu.org/licenses/>.
   *
   * Author: Matthias Clasen
   */
  
  #include "config.h"
  
  #include <stdlib.h>
  #include <stdio.h>
  
  #include <sys/types.h>
  #include <sys/stat.h>
  #include <fcntl.h>
  #include <string.h>
  #include <locale.h>
  
  #ifdef HAVE_LIBELF
  #include <libelf.h>
  #include <gelf.h>
  #endif
  
  #ifdef HAVE_MMAP
  #include <sys/mman.h>
  #endif
  
  #include <gio/gio.h>
  #include <glib/gstdio.h>
  #include <gi18n.h>
  
  #ifdef G_OS_WIN32
  #include "glib/glib-private.h"
  #endif
  
  #if defined(HAVE_LIBELF) && defined(HAVE_MMAP)
  #define USE_LIBELF
  #endif
  
  /* GResource functions {{{1 */
  static GResource *
  get_resource (const gchar *file)
  {
    gchar *content;
    gsize size;
    GResource *resource;
    GBytes *data;
  
    resource = NULL;
  
    if (g_file_get_contents (file, &content, &size, NULL))
      {
        data = g_bytes_new_take (content, size);
        resource = g_resource_new_from_data (data, NULL);
        g_bytes_unref (data);
      }
  
    return resource;
  }
  
  static void
  list_resource (GResource   *resource,
                 const gchar *path,
                 const gchar *section,
                 const gchar *prefix,
                 gboolean     details)
  {
    gchar **children;
    gsize size;
    guint32 flags;
    gint i;
    gchar *child;
    GError *error = NULL;
    gint len;
  
    children = g_resource_enumerate_children (resource, path, 0, &error);
    if (error)
      {
        g_printerr ("%s\n", error->message);
        g_error_free (error);
        return;
      }
    for (i = 0; children[i]; i++)
      {
        child = g_strconcat (path, children[i], NULL);
  
        len = MIN (strlen (child), strlen (prefix));
        if (strncmp (child, prefix, len) != 0)
          {
            g_free (child);
            continue;
          }
  
        if (g_resource_get_info (resource, child, 0, &size, &flags, NULL))
          {
            if (details)
              g_print ("%s%s%6"G_GSIZE_FORMAT " %s %s\n", section, section[0] ? " " : "", size, (flags & G_RESOURCE_FLAGS_COMPRESSED) ? "c" : "u", child);
            else
              g_print ("%s\n", child);
          }
        else
          list_resource (resource, child, section, prefix, details);
  
        g_free (child);
      }
    g_strfreev (children);
  }
  
  static void
  extract_resource (GResource   *resource,
                    const gchar *path)
  {
    GBytes *bytes;
  
    bytes = g_resource_lookup_data (resource, path, 0, NULL);
    if (bytes != NULL)
      {
        gconstpointer data;
        gsize size, written;
  
        data = g_bytes_get_data (bytes, &size);
        written = fwrite (data, 1, size, stdout);
        if (written < size)
          g_printerr ("Data truncated\n");
        g_bytes_unref (bytes);
      }
  }
  
  /* Elf functions {{{1 */
  
  #ifdef USE_LIBELF
  
  static Elf *
  get_elf (const gchar *file,
           gint        *fd)
  {
    Elf *elf;
  
    if (elf_version (EV_CURRENT) == EV_NONE )
      return NULL;
  
    *fd = g_open (file, O_RDONLY, 0);
    if (*fd < 0)
      return NULL;
  
    elf = elf_begin (*fd, ELF_C_READ, NULL);
    if (elf == NULL)
      {
        g_close (*fd, NULL);
        *fd = -1;
        return NULL;
      }
  
    if (elf_kind (elf) != ELF_K_ELF)
      {
        g_close (*fd, NULL);
        *fd = -1;
        return NULL;
      }
  
    return elf;
  }
  
  typedef gboolean (*SectionCallback) (GElf_Shdr   *shdr,
                                       const gchar *name,
                                       gpointer     data);
  
  static void
  elf_foreach_resource_section (Elf             *elf,
                                SectionCallback  callback,
                                gpointer         data)
  {
    size_t shstrndx, shnum;
    size_t scnidx;
    Elf_Scn *scn;
    GElf_Shdr *shdr, shdr_mem;
    const gchar *section_name;
  
    elf_getshdrstrndx (elf, &shstrndx);
    g_assert (shstrndx >= 0);
  
    elf_getshdrnum (elf, &shnum);
    g_assert (shnum >= 0);
  
    for (scnidx = 1; scnidx < shnum; scnidx++)
      {
        scn = elf_getscn (elf, scnidx);
        if (scn == NULL)
          continue;
  
        shdr = gelf_getshdr (scn, &shdr_mem);
        if (shdr == NULL)
          continue;
  
        if (shdr->sh_type != SHT_PROGBITS)
          continue;
  
        section_name = elf_strptr (elf, shstrndx, shdr->sh_name);
        if (section_name == NULL ||
            !g_str_has_prefix (section_name, ".gresource."))
          continue;
  
        if (!callback (shdr, section_name + strlen (".gresource."), data))
          break;
      }
  }
  
  static GResource *
  resource_from_section (GElf_Shdr *shdr,
                         int        fd)
  {
    gsize page_size, page_offset;
    char *contents;
    GResource *resource;
  
    resource = NULL;
  
    page_size = sysconf(_SC_PAGE_SIZE);
    page_offset = shdr->sh_offset % page_size;
    contents = mmap (NULL,  shdr->sh_size + page_offset,
                     PROT_READ, MAP_PRIVATE, fd, shdr->sh_offset - page_offset);
    if (contents != MAP_FAILED)
      {
        GBytes *bytes;
        GError *error = NULL;
  
        bytes = g_bytes_new_static (contents + page_offset, shdr->sh_size);
        resource = g_resource_new_from_data (bytes, &error);
        g_bytes_unref (bytes);
        if (error)
          {
            g_printerr ("%s\n", error->message);
            g_error_free (error);
          }
      }
    else
      {
        g_printerr ("Can't mmap resource section");
      }
  
    return resource;
  }
  
  typedef struct
  {
    int fd;
    const gchar *section;
    const gchar *path;
    gboolean details;
    gboolean found;
  } CallbackData;
  
  static gboolean
  list_resources_cb (GElf_Shdr   *shdr,
                     const gchar *section,
                     gpointer     data)
  {
    CallbackData *d = data;
    GResource *resource;
  
    if (d->section && strcmp (section, d->section) != 0)
      return TRUE;
  
    d->found = TRUE;
  
    resource = resource_from_section (shdr, d->fd);
    list_resource (resource, "/",
                   d->section ? "" : section,
                   d->path,
                   d->details);
    g_resource_unref (resource);
  
    if (d->section)
      return FALSE;
  
    return TRUE;
  }
  
  static void
  elf_list_resources (Elf         *elf,
                      int          fd,
                      const gchar *section,
                      const gchar *path,
                      gboolean     details)
  {
    CallbackData data;
  
    data.fd = fd;
    data.section = section;
    data.path = path;
    data.details = details;
    data.found = FALSE;
  
    elf_foreach_resource_section (elf, list_resources_cb, &data);
  
    if (!data.found)
      g_printerr ("Can't find resource section %s\n", section);
  }
  
  static gboolean
  extract_resource_cb (GElf_Shdr   *shdr,
                       const gchar *section,
                       gpointer     data)
  {
    CallbackData *d = data;
    GResource *resource;
  
    if (d->section && strcmp (section, d->section) != 0)
      return TRUE;
  
    d->found = TRUE;
  
    resource = resource_from_section (shdr, d->fd);
    extract_resource (resource, d->path);
    g_resource_unref (resource);
  
    if (d->section)
      return FALSE;
  
    return TRUE;
  }
  
  static void
  elf_extract_resource (Elf         *elf,
                        int          fd,
                        const gchar *section,
                        const gchar *path)
  {
    CallbackData data;
  
    data.fd = fd;
    data.section = section;
    data.path = path;
    data.found = FALSE;
  
    elf_foreach_resource_section (elf, extract_resource_cb, &data);
  
    if (!data.found)
      g_printerr ("Can't find resource section %s\n", section);
  }
  
  static gboolean
  print_section_name (GElf_Shdr   *shdr,
                      const gchar *name,
                      gpointer     data)
  {
    g_print ("%s\n", name);
    return TRUE;
  }
  
  #endif /* USE_LIBELF */
  
    /* Toplevel commands {{{1 */
  
  static void
  cmd_sections (const gchar *file,
                const gchar *section,
                const gchar *path,
                gboolean     details)
  {
    GResource *resource;
  
  #ifdef USE_LIBELF
  
    Elf *elf;
    gint fd;
  
    if ((elf = get_elf (file, &fd)))
      {
        elf_foreach_resource_section (elf, print_section_name, NULL);
        elf_end (elf);
        close (fd);
      }
    else
  
  #endif
  
    if ((resource = get_resource (file)))
      {
        /* No sections */
        g_resource_unref (resource);
      }
    else
      {
        g_printerr ("Don't know how to handle %s\n", file);
  #ifndef USE_LIBELF
        g_printerr ("gresource is built without elf support\n");
  #endif
      }
  }
  
  static void
  cmd_list (const gchar *file,
            const gchar *section,
            const gchar *path,
            gboolean     details)
  {
    GResource *resource;
  
  #ifdef USE_LIBELF
    Elf *elf;
    int fd;
  
    if ((elf = get_elf (file, &fd)))
      {
        elf_list_resources (elf, fd, section, path ? path : "", details);
        elf_end (elf);
        close (fd);
      }
    else
  
  #endif
  
    if ((resource = get_resource (file)))
      {
        list_resource (resource, "/", "", path ? path : "", details);
        g_resource_unref (resource);
      }
    else
      {
        g_printerr ("Don't know how to handle %s\n", file);
  #ifndef USE_LIBELF
        g_printerr ("gresource is built without elf support\n");
  #endif
      }
  }
  
  static void
  cmd_extract (const gchar *file,
               const gchar *section,
               const gchar *path,
               gboolean     details)
  {
    GResource *resource;
  
  #ifdef USE_LIBELF
  
    Elf *elf;
    int fd;
  
    if ((elf = get_elf (file, &fd)))
      {
        elf_extract_resource (elf, fd, section, path);
        elf_end (elf);
        close (fd);
      }
    else
  
  #endif
  
    if ((resource = get_resource (file)))
      {
        extract_resource (resource, path);
        g_resource_unref (resource);
      }
    else
      {
        g_printerr ("Don't know how to handle %s\n", file);
  #ifndef USE_LIBELF
        g_printerr ("gresource is built without elf support\n");
  #endif
      }
  }
  
  static gint
  cmd_help (gboolean     requested,
            const gchar *command)
  {
    const gchar *description;
    const gchar *synopsis;
    gchar *option;
    GString *string;
  
    option = NULL;
  
    string = g_string_new (NULL);
  
    if (command == NULL)
      ;
  
    else if (strcmp (command, "help") == 0)
      {
        description = _("Print help");
        synopsis = _("[COMMAND]");
      }
  
    else if (strcmp (command, "sections") == 0)
      {
        description = _("List sections containing resources in an elf FILE");
        synopsis = _("FILE");
      }
  
    else if (strcmp (command, "list") == 0)
      {
        description = _("List resources\n"
                        "If SECTION is given, only list resources in this section\n"
                        "If PATH is given, only list matching resources");
        synopsis = _("FILE [PATH]");
        option = g_strdup_printf ("[--section %s]", _("SECTION"));
      }
  
    else if (strcmp (command, "details") == 0)
      {
        description = _("List resources with details\n"
                        "If SECTION is given, only list resources in this section\n"
                        "If PATH is given, only list matching resources\n"
                        "Details include the section, size and compression");
        synopsis = _("FILE [PATH]");
        option = g_strdup_printf ("[--section %s]", _("SECTION"));
      }
  
    else if (strcmp (command, "extract") == 0)
      {
        description = _("Extract a resource file to stdout");
        synopsis = _("FILE PATH");
        option = g_strdup_printf ("[--section %s]", _("SECTION"));
      }
  
    else
      {
        g_string_printf (string, _("Unknown command %s\n\n"), command);
        requested = FALSE;
        command = NULL;
      }
  
    if (command == NULL)
      {
        g_string_append (string,
        _("Usage:\n"
          "  gresource [--section SECTION] COMMAND [ARGS…]\n"
          "\n"
          "Commands:\n"
          "  help                      Show this information\n"
          "  sections                  List resource sections\n"
          "  list                      List resources\n"
          "  details                   List resources with details\n"
          "  extract                   Extract a resource\n"
          "\n"
          "Use “gresource help COMMAND” to get detailed help.\n\n"));
      }
    else
      {
        g_string_append_printf (string, _("Usage:\n  gresource %s%s%s %s\n\n%s\n\n"),
                                option ? option : "", option ? " " : "", command, synopsis[0] ? synopsis : "", description);
  
        g_string_append (string, _("Arguments:\n"));
  
        if (option)
          g_string_append (string,
                           _("  SECTION   An (optional) elf section name\n"));
  
        if (strstr (synopsis, _("[COMMAND]")))
          g_string_append (string,
                         _("  COMMAND   The (optional) command to explain\n"));
  
        if (strstr (synopsis, _("FILE")))
          {
            if (strcmp (command, "sections") == 0)
              g_string_append (string,
                               _("  FILE      An elf file (a binary or a shared library)\n"));
            else
              g_string_append (string,
                               _("  FILE      An elf file (a binary or a shared library)\n"
                                 "            or a compiled resource file\n"));
          }
  
        if (strstr (synopsis, _("[PATH]")))
          g_string_append (string,
                         _("  PATH      An (optional) resource path (may be partial)\n"));
        else if (strstr (synopsis, _("PATH")))
          g_string_append (string,
                         _("  PATH      A resource path\n"));
  
        g_string_append (string, "\n");
      }
  
    if (requested)
      g_print ("%s", string->str);
    else
      g_printerr ("%s\n", string->str);
  
    g_free (option);
    g_string_free (string, TRUE);
  
    return requested ? 0 : 1;
  }
  
  /* main {{{1 */
  
  int
  main (int argc, char *argv[])
  {
    gchar *section = NULL;
    gboolean details = FALSE;
    void (* function) (const gchar *, const gchar *, const gchar *, gboolean);
  
  #ifdef G_OS_WIN32
    gchar *tmp;
  #endif
  
    setlocale (LC_ALL, "");
    textdomain (GETTEXT_PACKAGE);
  
  #ifdef G_OS_WIN32
    tmp = _glib_get_locale_dir ();
    bindtextdomain (GETTEXT_PACKAGE, tmp);
    g_free (tmp);
  #else
    bindtextdomain (GETTEXT_PACKAGE, GLIB_LOCALE_DIR);
  #endif
  
  #ifdef HAVE_BIND_TEXTDOMAIN_CODESET
    bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
  #endif
  
    if (argc < 2)
      return cmd_help (FALSE, NULL);
  
    if (argc > 3 && strcmp (argv[1], "--section") == 0)
      {
        section = argv[2];
        argv = argv + 2;
        argc -= 2;
      }
  
    if (strcmp (argv[1], "help") == 0)
      return cmd_help (TRUE, argv[2]);
  
    else if (argc == 4 && strcmp (argv[1], "extract") == 0)
      function = cmd_extract;
  
    else if (argc == 3 && strcmp (argv[1], "sections") == 0)
      function = cmd_sections;
  
    else if ((argc == 3 || argc == 4) && strcmp (argv[1], "list") == 0)
      {
        function = cmd_list;
        details = FALSE;
      }
    else if ((argc == 3 || argc == 4) && strcmp (argv[1], "details") == 0)
      {
        function = cmd_list;
        details = TRUE;
      }
    else
      return cmd_help (FALSE, argv[1]);
  
    (* function) (argv[2], section, argc > 3 ? argv[3] : NULL, details);
  
    return 0;
  }
  
  /* vim:set foldmethod=marker: */
  