1 | /* |
2 | * Copyright © 2012 Red Hat, Inc |
3 | * |
4 | * This library is free software; you can redistribute it and/or |
5 | * modify it under the terms of the GNU Lesser General Public |
6 | * License as published by the Free Software Foundation; either |
7 | * version 2.1 of the License, or (at your option) any later version. |
8 | * |
9 | * This library is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
12 | * Lesser General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU Lesser General Public |
15 | * License along with this library; if not, see <http://www.gnu.org/licenses/>. |
16 | * |
17 | * Author: Matthias Clasen |
18 | */ |
19 | |
20 | #include "config.h" |
21 | |
22 | #include <stdlib.h> |
23 | #include <stdio.h> |
24 | |
25 | #include <sys/types.h> |
26 | #include <sys/stat.h> |
27 | #include <fcntl.h> |
28 | #include <string.h> |
29 | #include <locale.h> |
30 | |
31 | #ifdef HAVE_LIBELF |
32 | #include <libelf.h> |
33 | #include <gelf.h> |
34 | #endif |
35 | |
36 | #ifdef HAVE_MMAP |
37 | #include <sys/mman.h> |
38 | #endif |
39 | |
40 | #include <gio/gio.h> |
41 | #include <glib/gstdio.h> |
42 | #include <gi18n.h> |
43 | |
44 | #include "glib/glib-private.h" |
45 | |
46 | #if defined(HAVE_LIBELF) && defined(HAVE_MMAP) |
47 | #define USE_LIBELF |
48 | #endif |
49 | |
50 | /* GResource functions {{{1 */ |
51 | static GResource * |
52 | get_resource (const gchar *file) |
53 | { |
54 | gchar *content; |
55 | gsize size; |
56 | GResource *resource; |
57 | GBytes *data; |
58 | |
59 | resource = NULL; |
60 | |
61 | if (g_file_get_contents (filename: file, contents: &content, length: &size, NULL)) |
62 | { |
63 | data = g_bytes_new_take (data: content, size); |
64 | resource = g_resource_new_from_data (data, NULL); |
65 | g_bytes_unref (bytes: data); |
66 | } |
67 | |
68 | return resource; |
69 | } |
70 | |
71 | static void |
72 | list_resource (GResource *resource, |
73 | const gchar *path, |
74 | const gchar *section, |
75 | const gchar *prefix, |
76 | gboolean details) |
77 | { |
78 | gchar **children; |
79 | gsize size; |
80 | guint32 flags; |
81 | gint i; |
82 | gchar *child; |
83 | GError *error = NULL; |
84 | gint len; |
85 | |
86 | children = g_resource_enumerate_children (resource, path, lookup_flags: 0, error: &error); |
87 | if (error) |
88 | { |
89 | g_printerr (format: "%s\n" , error->message); |
90 | g_error_free (error); |
91 | return; |
92 | } |
93 | for (i = 0; children[i]; i++) |
94 | { |
95 | child = g_strconcat (string1: path, children[i], NULL); |
96 | |
97 | len = MIN (strlen (child), strlen (prefix)); |
98 | if (strncmp (s1: child, s2: prefix, n: len) != 0) |
99 | { |
100 | g_free (mem: child); |
101 | continue; |
102 | } |
103 | |
104 | if (g_resource_get_info (resource, path: child, lookup_flags: 0, size: &size, flags: &flags, NULL)) |
105 | { |
106 | if (details) |
107 | g_print (format: "%s%s%6" G_GSIZE_FORMAT " %s %s\n" , section, section[0] ? " " : "" , size, (flags & G_RESOURCE_FLAGS_COMPRESSED) ? "c" : "u" , child); |
108 | else |
109 | g_print (format: "%s\n" , child); |
110 | } |
111 | else |
112 | list_resource (resource, path: child, section, prefix, details); |
113 | |
114 | g_free (mem: child); |
115 | } |
116 | g_strfreev (str_array: children); |
117 | } |
118 | |
119 | static void |
120 | (GResource *resource, |
121 | const gchar *path) |
122 | { |
123 | GBytes *bytes; |
124 | |
125 | bytes = g_resource_lookup_data (resource, path, lookup_flags: 0, NULL); |
126 | if (bytes != NULL) |
127 | { |
128 | gconstpointer data; |
129 | gsize size, written; |
130 | |
131 | data = g_bytes_get_data (bytes, size: &size); |
132 | written = fwrite (ptr: data, size: 1, n: size, stdout); |
133 | if (written < size) |
134 | g_printerr (format: "Data truncated\n" ); |
135 | g_bytes_unref (bytes); |
136 | } |
137 | } |
138 | |
139 | /* Elf functions {{{1 */ |
140 | |
141 | #ifdef USE_LIBELF |
142 | |
143 | static Elf * |
144 | get_elf (const gchar *file, |
145 | gint *fd) |
146 | { |
147 | Elf *elf; |
148 | |
149 | if (elf_version (EV_CURRENT) == EV_NONE ) |
150 | return NULL; |
151 | |
152 | *fd = g_open (file, O_RDONLY, 0); |
153 | if (*fd < 0) |
154 | return NULL; |
155 | |
156 | elf = elf_begin (*fd, ELF_C_READ, NULL); |
157 | if (elf == NULL) |
158 | { |
159 | g_close (*fd, NULL); |
160 | *fd = -1; |
161 | return NULL; |
162 | } |
163 | |
164 | if (elf_kind (elf) != ELF_K_ELF) |
165 | { |
166 | g_close (*fd, NULL); |
167 | *fd = -1; |
168 | return NULL; |
169 | } |
170 | |
171 | return elf; |
172 | } |
173 | |
174 | typedef gboolean (*SectionCallback) (GElf_Shdr *shdr, |
175 | const gchar *name, |
176 | gpointer data); |
177 | |
178 | static void |
179 | elf_foreach_resource_section (Elf *elf, |
180 | SectionCallback callback, |
181 | gpointer data) |
182 | { |
183 | size_t shstrndx, shnum; |
184 | size_t scnidx; |
185 | Elf_Scn *scn; |
186 | GElf_Shdr *shdr, shdr_mem; |
187 | const gchar *section_name; |
188 | |
189 | elf_getshdrstrndx (elf, &shstrndx); |
190 | g_assert (shstrndx >= 0); |
191 | |
192 | elf_getshdrnum (elf, &shnum); |
193 | g_assert (shnum >= 0); |
194 | |
195 | for (scnidx = 1; scnidx < shnum; scnidx++) |
196 | { |
197 | scn = elf_getscn (elf, scnidx); |
198 | if (scn == NULL) |
199 | continue; |
200 | |
201 | shdr = gelf_getshdr (scn, &shdr_mem); |
202 | if (shdr == NULL) |
203 | continue; |
204 | |
205 | if (shdr->sh_type != SHT_PROGBITS) |
206 | continue; |
207 | |
208 | section_name = elf_strptr (elf, shstrndx, shdr->sh_name); |
209 | if (section_name == NULL || |
210 | !g_str_has_prefix (section_name, ".gresource." )) |
211 | continue; |
212 | |
213 | if (!callback (shdr, section_name + strlen (".gresource." ), data)) |
214 | break; |
215 | } |
216 | } |
217 | |
218 | static GResource * |
219 | resource_from_section (GElf_Shdr *shdr, |
220 | int fd) |
221 | { |
222 | gsize page_size, page_offset; |
223 | char *contents; |
224 | GResource *resource; |
225 | |
226 | resource = NULL; |
227 | |
228 | page_size = sysconf(_SC_PAGE_SIZE); |
229 | page_offset = shdr->sh_offset % page_size; |
230 | contents = mmap (NULL, shdr->sh_size + page_offset, |
231 | PROT_READ, MAP_PRIVATE, fd, shdr->sh_offset - page_offset); |
232 | if (contents != MAP_FAILED) |
233 | { |
234 | GBytes *bytes; |
235 | GError *error = NULL; |
236 | |
237 | bytes = g_bytes_new_static (contents + page_offset, shdr->sh_size); |
238 | resource = g_resource_new_from_data (bytes, &error); |
239 | g_bytes_unref (bytes); |
240 | if (error) |
241 | { |
242 | g_printerr ("%s\n" , error->message); |
243 | g_error_free (error); |
244 | } |
245 | } |
246 | else |
247 | { |
248 | g_printerr ("Can't mmap resource section" ); |
249 | } |
250 | |
251 | return resource; |
252 | } |
253 | |
254 | typedef struct |
255 | { |
256 | int fd; |
257 | const gchar *section; |
258 | const gchar *path; |
259 | gboolean details; |
260 | gboolean found; |
261 | } CallbackData; |
262 | |
263 | static gboolean |
264 | list_resources_cb (GElf_Shdr *shdr, |
265 | const gchar *section, |
266 | gpointer data) |
267 | { |
268 | CallbackData *d = data; |
269 | GResource *resource; |
270 | |
271 | if (d->section && strcmp (section, d->section) != 0) |
272 | return TRUE; |
273 | |
274 | d->found = TRUE; |
275 | |
276 | resource = resource_from_section (shdr, d->fd); |
277 | list_resource (resource, "/" , |
278 | d->section ? "" : section, |
279 | d->path, |
280 | d->details); |
281 | g_resource_unref (resource); |
282 | |
283 | if (d->section) |
284 | return FALSE; |
285 | |
286 | return TRUE; |
287 | } |
288 | |
289 | static void |
290 | elf_list_resources (Elf *elf, |
291 | int fd, |
292 | const gchar *section, |
293 | const gchar *path, |
294 | gboolean details) |
295 | { |
296 | CallbackData data; |
297 | |
298 | data.fd = fd; |
299 | data.section = section; |
300 | data.path = path; |
301 | data.details = details; |
302 | data.found = FALSE; |
303 | |
304 | elf_foreach_resource_section (elf, list_resources_cb, &data); |
305 | |
306 | if (!data.found) |
307 | g_printerr ("Can't find resource section %s\n" , section); |
308 | } |
309 | |
310 | static gboolean |
311 | extract_resource_cb (GElf_Shdr *shdr, |
312 | const gchar *section, |
313 | gpointer data) |
314 | { |
315 | CallbackData *d = data; |
316 | GResource *resource; |
317 | |
318 | if (d->section && strcmp (section, d->section) != 0) |
319 | return TRUE; |
320 | |
321 | d->found = TRUE; |
322 | |
323 | resource = resource_from_section (shdr, d->fd); |
324 | extract_resource (resource, d->path); |
325 | g_resource_unref (resource); |
326 | |
327 | if (d->section) |
328 | return FALSE; |
329 | |
330 | return TRUE; |
331 | } |
332 | |
333 | static void |
334 | elf_extract_resource (Elf *elf, |
335 | int fd, |
336 | const gchar *section, |
337 | const gchar *path) |
338 | { |
339 | CallbackData data; |
340 | |
341 | data.fd = fd; |
342 | data.section = section; |
343 | data.path = path; |
344 | data.found = FALSE; |
345 | |
346 | elf_foreach_resource_section (elf, extract_resource_cb, &data); |
347 | |
348 | if (!data.found) |
349 | g_printerr ("Can't find resource section %s\n" , section); |
350 | } |
351 | |
352 | static gboolean |
353 | print_section_name (GElf_Shdr *shdr, |
354 | const gchar *name, |
355 | gpointer data) |
356 | { |
357 | g_print ("%s\n" , name); |
358 | return TRUE; |
359 | } |
360 | |
361 | #endif /* USE_LIBELF */ |
362 | |
363 | /* Toplevel commands {{{1 */ |
364 | |
365 | static void |
366 | cmd_sections (const gchar *file, |
367 | const gchar *section, |
368 | const gchar *path, |
369 | gboolean details) |
370 | { |
371 | GResource *resource; |
372 | |
373 | #ifdef USE_LIBELF |
374 | |
375 | Elf *elf; |
376 | gint fd; |
377 | |
378 | if ((elf = get_elf (file, &fd))) |
379 | { |
380 | elf_foreach_resource_section (elf, print_section_name, NULL); |
381 | elf_end (elf); |
382 | close (fd); |
383 | } |
384 | else |
385 | |
386 | #endif |
387 | |
388 | if ((resource = get_resource (file))) |
389 | { |
390 | /* No sections */ |
391 | g_resource_unref (resource); |
392 | } |
393 | else |
394 | { |
395 | g_printerr (format: "Don't know how to handle %s\n" , file); |
396 | #ifndef USE_LIBELF |
397 | g_printerr (format: "gresource is built without elf support\n" ); |
398 | #endif |
399 | } |
400 | } |
401 | |
402 | static void |
403 | cmd_list (const gchar *file, |
404 | const gchar *section, |
405 | const gchar *path, |
406 | gboolean details) |
407 | { |
408 | GResource *resource; |
409 | |
410 | #ifdef USE_LIBELF |
411 | Elf *elf; |
412 | int fd; |
413 | |
414 | if ((elf = get_elf (file, &fd))) |
415 | { |
416 | elf_list_resources (elf, fd, section, path ? path : "" , details); |
417 | elf_end (elf); |
418 | close (fd); |
419 | } |
420 | else |
421 | |
422 | #endif |
423 | |
424 | if ((resource = get_resource (file))) |
425 | { |
426 | list_resource (resource, path: "/" , section: "" , prefix: path ? path : "" , details); |
427 | g_resource_unref (resource); |
428 | } |
429 | else |
430 | { |
431 | g_printerr (format: "Don't know how to handle %s\n" , file); |
432 | #ifndef USE_LIBELF |
433 | g_printerr (format: "gresource is built without elf support\n" ); |
434 | #endif |
435 | } |
436 | } |
437 | |
438 | static void |
439 | (const gchar *file, |
440 | const gchar *section, |
441 | const gchar *path, |
442 | gboolean details) |
443 | { |
444 | GResource *resource; |
445 | |
446 | #ifdef USE_LIBELF |
447 | |
448 | Elf *elf; |
449 | int fd; |
450 | |
451 | if ((elf = get_elf (file, &fd))) |
452 | { |
453 | elf_extract_resource (elf, fd, section, path); |
454 | elf_end (elf); |
455 | close (fd); |
456 | } |
457 | else |
458 | |
459 | #endif |
460 | |
461 | if ((resource = get_resource (file))) |
462 | { |
463 | extract_resource (resource, path); |
464 | g_resource_unref (resource); |
465 | } |
466 | else |
467 | { |
468 | g_printerr (format: "Don't know how to handle %s\n" , file); |
469 | #ifndef USE_LIBELF |
470 | g_printerr (format: "gresource is built without elf support\n" ); |
471 | #endif |
472 | } |
473 | } |
474 | |
475 | static gint |
476 | cmd_help (gboolean requested, |
477 | const gchar *command) |
478 | { |
479 | const gchar *description; |
480 | const gchar *synopsis; |
481 | gchar *option; |
482 | GString *string; |
483 | |
484 | option = NULL; |
485 | |
486 | string = g_string_new (NULL); |
487 | |
488 | if (command == NULL) |
489 | ; |
490 | |
491 | else if (strcmp (s1: command, s2: "help" ) == 0) |
492 | { |
493 | description = _("Print help" ); |
494 | synopsis = _("[COMMAND]" ); |
495 | } |
496 | |
497 | else if (strcmp (s1: command, s2: "sections" ) == 0) |
498 | { |
499 | description = _("List sections containing resources in an elf FILE" ); |
500 | synopsis = _("FILE" ); |
501 | } |
502 | |
503 | else if (strcmp (s1: command, s2: "list" ) == 0) |
504 | { |
505 | description = _("List resources\n" |
506 | "If SECTION is given, only list resources in this section\n" |
507 | "If PATH is given, only list matching resources" ); |
508 | synopsis = _("FILE [PATH]" ); |
509 | option = g_strdup_printf (format: "[--section %s]" , _("SECTION" )); |
510 | } |
511 | |
512 | else if (strcmp (s1: command, s2: "details" ) == 0) |
513 | { |
514 | description = _("List resources with details\n" |
515 | "If SECTION is given, only list resources in this section\n" |
516 | "If PATH is given, only list matching resources\n" |
517 | "Details include the section, size and compression" ); |
518 | synopsis = _("FILE [PATH]" ); |
519 | option = g_strdup_printf (format: "[--section %s]" , _("SECTION" )); |
520 | } |
521 | |
522 | else if (strcmp (s1: command, s2: "extract" ) == 0) |
523 | { |
524 | description = _("Extract a resource file to stdout" ); |
525 | synopsis = _("FILE PATH" ); |
526 | option = g_strdup_printf (format: "[--section %s]" , _("SECTION" )); |
527 | } |
528 | |
529 | else |
530 | { |
531 | g_string_printf (string, _("Unknown command %s\n\n" ), command); |
532 | requested = FALSE; |
533 | command = NULL; |
534 | } |
535 | |
536 | if (command == NULL) |
537 | { |
538 | g_string_append (string, |
539 | _("Usage:\n" |
540 | " gresource [--section SECTION] COMMAND [ARGS…]\n" |
541 | "\n" |
542 | "Commands:\n" |
543 | " help Show this information\n" |
544 | " sections List resource sections\n" |
545 | " list List resources\n" |
546 | " details List resources with details\n" |
547 | " extract Extract a resource\n" |
548 | "\n" |
549 | "Use “gresource help COMMAND” to get detailed help.\n\n" )); |
550 | } |
551 | else |
552 | { |
553 | g_string_append_printf (string, _("Usage:\n gresource %s%s%s %s\n\n%s\n\n" ), |
554 | option ? option : "" , option ? " " : "" , command, synopsis[0] ? synopsis : "" , description); |
555 | |
556 | g_string_append (string, _("Arguments:\n" )); |
557 | |
558 | if (option) |
559 | g_string_append (string, |
560 | _(" SECTION An (optional) elf section name\n" )); |
561 | |
562 | if (strstr (haystack: synopsis, _("[COMMAND]" ))) |
563 | g_string_append (string, |
564 | _(" COMMAND The (optional) command to explain\n" )); |
565 | |
566 | if (strstr (haystack: synopsis, _("FILE" ))) |
567 | { |
568 | if (strcmp (s1: command, s2: "sections" ) == 0) |
569 | g_string_append (string, |
570 | _(" FILE An elf file (a binary or a shared library)\n" )); |
571 | else |
572 | g_string_append (string, |
573 | _(" FILE An elf file (a binary or a shared library)\n" |
574 | " or a compiled resource file\n" )); |
575 | } |
576 | |
577 | if (strstr (haystack: synopsis, _("[PATH]" ))) |
578 | g_string_append (string, |
579 | _(" PATH An (optional) resource path (may be partial)\n" )); |
580 | else if (strstr (haystack: synopsis, _("PATH" ))) |
581 | g_string_append (string, |
582 | _(" PATH A resource path\n" )); |
583 | |
584 | g_string_append (string, val: "\n" ); |
585 | } |
586 | |
587 | if (requested) |
588 | g_print (format: "%s" , string->str); |
589 | else |
590 | g_printerr (format: "%s\n" , string->str); |
591 | |
592 | g_free (mem: option); |
593 | g_string_free (string, TRUE); |
594 | |
595 | return requested ? 0 : 1; |
596 | } |
597 | |
598 | /* main {{{1 */ |
599 | |
600 | int |
601 | main (int argc, char *argv[]) |
602 | { |
603 | gchar *section = NULL; |
604 | gboolean details = FALSE; |
605 | void (* function) (const gchar *, const gchar *, const gchar *, gboolean); |
606 | |
607 | #ifdef G_OS_WIN32 |
608 | gchar *tmp; |
609 | #endif |
610 | |
611 | setlocale (LC_ALL, GLIB_DEFAULT_LOCALE); |
612 | textdomain (GETTEXT_PACKAGE); |
613 | |
614 | #ifdef G_OS_WIN32 |
615 | tmp = _glib_get_locale_dir (); |
616 | bindtextdomain (GETTEXT_PACKAGE, tmp); |
617 | g_free (tmp); |
618 | #else |
619 | bindtextdomain (GETTEXT_PACKAGE, GLIB_LOCALE_DIR); |
620 | #endif |
621 | |
622 | #ifdef HAVE_BIND_TEXTDOMAIN_CODESET |
623 | bind_textdomain_codeset (GETTEXT_PACKAGE, codeset: "UTF-8" ); |
624 | #endif |
625 | |
626 | if (argc < 2) |
627 | return cmd_help (FALSE, NULL); |
628 | |
629 | if (argc > 3 && strcmp (s1: argv[1], s2: "--section" ) == 0) |
630 | { |
631 | section = argv[2]; |
632 | argv = argv + 2; |
633 | argc -= 2; |
634 | } |
635 | |
636 | if (strcmp (s1: argv[1], s2: "help" ) == 0) |
637 | return cmd_help (TRUE, command: argv[2]); |
638 | |
639 | else if (argc == 4 && strcmp (s1: argv[1], s2: "extract" ) == 0) |
640 | function = cmd_extract; |
641 | |
642 | else if (argc == 3 && strcmp (s1: argv[1], s2: "sections" ) == 0) |
643 | function = cmd_sections; |
644 | |
645 | else if ((argc == 3 || argc == 4) && strcmp (s1: argv[1], s2: "list" ) == 0) |
646 | { |
647 | function = cmd_list; |
648 | details = FALSE; |
649 | } |
650 | else if ((argc == 3 || argc == 4) && strcmp (s1: argv[1], s2: "details" ) == 0) |
651 | { |
652 | function = cmd_list; |
653 | details = TRUE; |
654 | } |
655 | else |
656 | return cmd_help (FALSE, command: argv[1]); |
657 | |
658 | (* function) (argv[2], section, argc > 3 ? argv[3] : NULL, details); |
659 | |
660 | return 0; |
661 | } |
662 | |
663 | /* vim:set foldmethod=marker: */ |
664 | |