From 716cf3558510be0a7ba7075a431ac2a5ad13b2bf Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sun, 22 Jan 2012 02:39:11 -0500 Subject: [PATCH] Add a resource tool This lets you poke at resources in elf files and standalone resource bundles. So far, only listing and extracting resources is supported. The support for elf files requires libelf. --- configure.ac | 11 + gio/Makefile.am | 13 +- gio/gresource-bash-completion.sh | 58 ++++ gio/gresource-tool.c | 622 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 703 insertions(+), 1 deletion(-) create mode 100644 gio/gresource-bash-completion.sh create mode 100644 gio/gresource-tool.c diff --git a/configure.ac b/configure.ac index 3c7f4ed..03c6931 100644 --- a/configure.ac +++ b/configure.ac @@ -1819,6 +1819,17 @@ dnl failure. try libattr fi AC_SUBST(XATTR_LIBS) +dnl ************************ +dnl *** check for libelf *** +dnl ************************ + +AC_CHECK_LIB([elf], [elf_begin], have_libelf=yes, have_libelf=no) +if test $have_libelf = yes; then + AC_DEFINE(HAVE_LIBELF, 1, [Define if libelf is available]) + ELF_LIBS=-lelf +fi +AC_SUBST(ELF_LIBS) + dnl **************************************** dnl *** platform dependent source checks *** dnl **************************************** diff --git a/gio/Makefile.am b/gio/Makefile.am index 0afd64e..815465e 100644 --- a/gio/Makefile.am +++ b/gio/Makefile.am @@ -693,10 +693,21 @@ gdbus_LDADD = libgio-2.0.la \ completiondir = $(sysconfdir)/bash_completion.d completion_DATA = \ gdbus-bash-completion.sh \ - gsettings-bash-completion.sh + gsettings-bash-completion.sh \ + gresource-bash-completion.sh EXTRA_DIST += $(completion_DATA) # ------------------------------------------------------------------------ +# gresource tool + +bin_PROGRAMS += gresource +gresource_SOURCES = gresource-tool.c +gresource_LDADD = libgio-2.0.la \ + $(top_builddir)/glib/libglib-2.0.la \ + $(top_builddir)/gobject/libgobject-2.0.la \ + $(ELF_LIBS) + +# ------------------------------------------------------------------------ dist-hook: $(BUILT_EXTRA_DIST) ../build/win32/vs9/gio.vcproj ../build/win32/vs10/gio.vcxproj ../build/win32/vs10/gio.vcxproj.filters files='$(BUILT_EXTRA_DIST)'; \ diff --git a/gio/gresource-bash-completion.sh b/gio/gresource-bash-completion.sh new file mode 100644 index 0000000..ef1145d --- /dev/null +++ b/gio/gresource-bash-completion.sh @@ -0,0 +1,58 @@ + +# Check for bash +[ -z "$BASH_VERSION" ] && return + +#################################################################################################### + +__gresource() { + local choices coffset section + + if [ ${COMP_CWORD} -gt 2 ]; then + if [ ${COMP_WORDS[1]} = --section ]; then + section=${COMP_WORDS[2]} + coffset=2 + else + coffset=0 + fi + else + coffset=0 + fi + + case "$((${COMP_CWORD}-$coffset))" in + 1) + choices=$'--section \nhelp \nsections \nlist \ndetails \nextract ' + ;; + + 2) + case "${COMP_WORDS[$(($coffset+1))]}" in + --section) + return 0 + ;; + + help) + choices=$'sections\nlist\ndetails\nextract' + ;; + + sections|list|details|extract) + COMPREPLY=($(compgen -f -- ${COMP_WORDS[${COMP_CWORD}]})) + return 0 + ;; + esac + ;; + + 3) + case "${COMP_WORDS[$(($coffset+1))]}" in + list|details|extract) + choices="$(gresource list ${COMP_WORDS[$(($coffset+2))]} 2> /dev/null | sed -e 's.$. .')" + ;; + esac + ;; + esac + + local IFS=$'\n' + COMPREPLY=($(compgen -W "${choices}" -- "${COMP_WORDS[${COMP_CWORD}]}")) +} + +#################################################################################################### + +complete -o nospace -F __gresource gresource diff --git a/gio/gresource-tool.c b/gio/gresource-tool.c new file mode 100644 index 0000000..3e0d95c --- /dev/null +++ b/gio/gresource-tool.c @@ -0,0 +1,622 @@ +/* + * 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 of the licence, 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, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Matthias Clasen + */ + +#include "config.h" + +#include +#include + +#include +#include +#include +#include +#include + +#ifdef HAVE_LIBELF +#include +#include +#endif + +#include +#include + +/* 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) + continue; + + if (g_resource_get_info (resource, child, 0, &size, &flags, NULL)) + { + if (details) + g_print ("%s%s%6ld %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); + } + else + { + g_printerr ("Can't find resource path %s\n", path); + } +} + +/* Elf functions {{{1 */ + +#ifdef HAVE_LIBELF + +static Elf * +get_elf (const gchar *file, + gint *fd) +{ + Elf *elf; + + if (elf_version (EV_CURRENT) == EV_NONE ) + return NULL; + + *fd = open (file, O_RDONLY); + if (*fd < 0) + return NULL; + + elf = elf_begin (*fd, ELF_C_READ, NULL); + if (elf == NULL) + return NULL; + + if (elf_kind (elf) != ELF_K_ELF) + 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; + + bytes = g_bytes_new_static (contents + page_offset, shdr->sh_size); + resource = g_resource_new_from_data (bytes, NULL); + g_bytes_unref (bytes); + } + 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); + + return FALSE; +} + +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 /* HAVE_LIBELF */ + + /* Toplevel commands {{{1 */ + +static void +cmd_sections (const gchar *file, + const gchar *section, + const gchar *path, + gboolean details) +{ + GResource *resource; + +#ifdef HAVE_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 HAVE_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 HAVE_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 HAVE_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 HAVE_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 HAVE_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); + + g_type_init (); + + 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: */ -- 2.7.4