1 | /* -*- mode: C; c-file-style: "linux" -*- */ |
2 | /* GdkPixbuf library |
3 | * queryloaders.c: |
4 | * |
5 | * Copyright (C) 2002 The Free Software Foundation |
6 | * |
7 | * Author: Matthias Clasen <maclas@gmx.de> |
8 | * |
9 | * This library is free software; you can redistribute it and/or |
10 | * modify it under the terms of the GNU Library General Public |
11 | * License as published by the Free Software Foundation; either |
12 | * version 2 of the License, or (at your option) any later version. |
13 | * |
14 | * This library is distributed in the hope that it will be useful, |
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
17 | * Library General Public License for more details. |
18 | * |
19 | * You should have received a copy of the GNU Library General Public |
20 | * License along with this library; if not, see <http://www.gnu.org/licenses/>. |
21 | */ |
22 | |
23 | #include "config.h" |
24 | |
25 | #include <glib.h> |
26 | #include <glib/gprintf.h> |
27 | #include <gmodule.h> |
28 | |
29 | #include <errno.h> |
30 | #include <string.h> |
31 | #ifdef HAVE_UNISTD_H |
32 | #include <unistd.h> |
33 | #endif |
34 | |
35 | #include "gdk-pixbuf/gdk-pixbuf.h" |
36 | #include "gdk-pixbuf/gdk-pixbuf-private.h" |
37 | |
38 | #ifdef USE_LA_MODULES |
39 | #define SOEXT ".la" |
40 | #else |
41 | #define SOEXT ("." G_MODULE_SUFFIX) |
42 | #endif |
43 | #define SOEXT_LEN (strlen (SOEXT)) |
44 | |
45 | #ifdef G_OS_WIN32 |
46 | #include <windows.h> |
47 | #endif |
48 | |
49 | #ifdef OS_DARWIN |
50 | #include <mach-o/dyld.h> |
51 | #endif |
52 | |
53 | static void |
54 | print_escaped (GString *contents, const char *str) |
55 | { |
56 | gchar *tmp = g_strescape (source: str, exceptions: "" ); |
57 | g_string_append_printf (string: contents, format: "\"%s\" " , tmp); |
58 | g_free (mem: tmp); |
59 | } |
60 | |
61 | static int |
62 | loader_sanity_check (const char *path, GdkPixbufFormat *info, GdkPixbufModule *vtable) |
63 | { |
64 | const GdkPixbufModulePattern *pattern; |
65 | const char *error = "" ; |
66 | |
67 | for (pattern = info->signature; pattern->prefix; pattern++) |
68 | { |
69 | int prefix_len = strlen (s: pattern->prefix); |
70 | if (prefix_len == 0) |
71 | { |
72 | error = "empty pattern" ; |
73 | |
74 | goto error; |
75 | } |
76 | if (pattern->mask) |
77 | { |
78 | int mask_len = strlen (s: pattern->mask); |
79 | if (mask_len != prefix_len) |
80 | { |
81 | error = "mask length mismatch" ; |
82 | |
83 | goto error; |
84 | } |
85 | if (strspn (s: pattern->mask, accept: " !xzn*" ) < mask_len) |
86 | { |
87 | error = "bad char in mask" ; |
88 | |
89 | goto error; |
90 | } |
91 | } |
92 | } |
93 | |
94 | if (!vtable->load && !vtable->begin_load && !vtable->load_animation) |
95 | { |
96 | error = "no load method implemented" ; |
97 | |
98 | goto error; |
99 | } |
100 | |
101 | if (vtable->begin_load && (!vtable->stop_load || !vtable->load_increment)) |
102 | { |
103 | error = "incremental loading support incomplete" ; |
104 | |
105 | goto error; |
106 | } |
107 | |
108 | if ((info->flags & GDK_PIXBUF_FORMAT_WRITABLE) && !(vtable->save || vtable->save_to_callback)) |
109 | { |
110 | error = "loader claims to support saving but doesn't implement save" ; |
111 | goto error; |
112 | } |
113 | |
114 | return 1; |
115 | |
116 | error: |
117 | g_fprintf (stderr, format: "Loader sanity check failed for %s: %s\n" , |
118 | path, error); |
119 | |
120 | return 0; |
121 | } |
122 | |
123 | #ifdef GDK_PIXBUF_RELOCATABLE |
124 | |
125 | /* Based on gdk_pixbuf_get_toplevel () */ |
126 | static gchar * |
127 | get_toplevel (void) |
128 | { |
129 | static gchar *toplevel = NULL; |
130 | |
131 | if (toplevel == NULL) { |
132 | #if defined (G_OS_WIN32) |
133 | toplevel = g_win32_get_package_installation_directory_of_module (NULL); |
134 | #elif defined(OS_DARWIN) |
135 | char pathbuf[PATH_MAX + 1]; |
136 | uint32_t bufsize = sizeof (pathbuf); |
137 | gchar *bin_dir; |
138 | |
139 | _NSGetExecutablePath (pathbuf, &bufsize); |
140 | bin_dir = g_dirname (pathbuf); |
141 | toplevel = g_build_path (G_DIR_SEPARATOR_S, bin_dir, ".." , NULL); |
142 | g_free (bin_dir); |
143 | #elif defined (OS_LINUX) || defined (__MINGW32__) |
144 | gchar *exe_path, *bin_dir; |
145 | |
146 | exe_path = g_file_read_link ("/proc/self/exe" , NULL); |
147 | bin_dir = g_dirname (exe_path); |
148 | toplevel = g_build_path (G_DIR_SEPARATOR_S, bin_dir, ".." , NULL); |
149 | g_free (exe_path); |
150 | g_free (bin_dir); |
151 | #else |
152 | #error "Relocations not supported for this platform" |
153 | #endif |
154 | } |
155 | return toplevel; |
156 | } |
157 | |
158 | /* Returns the relative path or NULL; transfer full */ |
159 | static gchar * |
160 | get_relative_path (const gchar *parent, const gchar *descendant) |
161 | { |
162 | GFile *parent_file, *descendant_file; |
163 | char *relative_path; |
164 | |
165 | parent_file = g_file_new_for_path (parent); |
166 | descendant_file = g_file_new_for_path (descendant); |
167 | relative_path = g_file_get_relative_path (parent_file, descendant_file); |
168 | g_object_unref (parent_file); |
169 | g_object_unref (descendant_file); |
170 | |
171 | return relative_path; |
172 | } |
173 | |
174 | #endif /* GDK_PIXBUF_RELOCATABLE */ |
175 | |
176 | static void |
177 | write_loader_info (GString *contents, const char *path, GdkPixbufFormat *info) |
178 | { |
179 | const GdkPixbufModulePattern *pattern; |
180 | char **mime; |
181 | char **ext; |
182 | gchar *module_path = NULL, *escaped_path; |
183 | |
184 | #ifdef GDK_PIXBUF_RELOCATABLE |
185 | module_path = get_relative_path (get_toplevel (), path); |
186 | #endif |
187 | |
188 | if (module_path == NULL) { |
189 | module_path = g_strdup (str: path); |
190 | } |
191 | |
192 | escaped_path = g_strescape (source: module_path, exceptions: "" ); |
193 | g_string_append_printf (string: contents, format: "\"%s\"\n" , escaped_path); |
194 | g_free (mem: module_path); |
195 | g_free (mem: escaped_path); |
196 | |
197 | g_string_append_printf (string: contents, format: "\"%s\" %u \"%s\" \"%s\" \"%s\"\n" , |
198 | info->name, |
199 | info->flags, |
200 | info->domain ? info->domain : GETTEXT_PACKAGE, |
201 | info->description, |
202 | info->license ? info->license : "" ); |
203 | for (mime = info->mime_types; *mime; mime++) { |
204 | g_string_append_printf (string: contents, format: "\"%s\" " , *mime); |
205 | } |
206 | g_string_append (string: contents, val: "\"\"\n" ); |
207 | for (ext = info->extensions; *ext; ext++) { |
208 | g_string_append_printf (string: contents, format: "\"%s\" " , *ext); |
209 | } |
210 | g_string_append (string: contents, val: "\"\"\n" ); |
211 | for (pattern = info->signature; pattern->prefix; pattern++) { |
212 | print_escaped (contents, str: pattern->prefix); |
213 | print_escaped (contents, str: pattern->mask ? (const char *)pattern->mask : "" ); |
214 | g_string_append_printf (string: contents, format: "%d\n" , pattern->relevance); |
215 | } |
216 | g_string_append_c (contents, '\n'); |
217 | } |
218 | |
219 | static void |
220 | query_module (GString *contents, const char *dir, const char *file) |
221 | { |
222 | char *path; |
223 | GModule *module; |
224 | void (*fill_info) (GdkPixbufFormat *info); |
225 | void (*fill_vtable) (GdkPixbufModule *module); |
226 | gpointer fill_info_ptr; |
227 | gpointer fill_vtable_ptr; |
228 | |
229 | if (g_path_is_absolute (file_name: file)) |
230 | path = g_strdup (str: file); |
231 | else |
232 | path = g_build_filename (first_element: dir, file, NULL); |
233 | |
234 | module = g_module_open (file_name: path, flags: 0); |
235 | if (module && |
236 | g_module_symbol (module, symbol_name: "fill_info" , symbol: &fill_info_ptr) && |
237 | g_module_symbol (module, symbol_name: "fill_vtable" , symbol: &fill_vtable_ptr)) { |
238 | GdkPixbufFormat *info; |
239 | GdkPixbufModule *vtable; |
240 | |
241 | #ifdef G_OS_WIN32 |
242 | /* Replace backslashes in path with forward slashes, so that |
243 | * it reads in without problems. |
244 | */ |
245 | { |
246 | char *p = path; |
247 | while (*p) { |
248 | if (*p == '\\') |
249 | *p = '/'; |
250 | p++; |
251 | } |
252 | } |
253 | #endif |
254 | info = g_new0 (GdkPixbufFormat, 1); |
255 | vtable = g_new0 (GdkPixbufModule, 1); |
256 | |
257 | vtable->module = module; |
258 | |
259 | fill_info = fill_info_ptr; |
260 | fill_vtable = fill_vtable_ptr; |
261 | |
262 | (*fill_info) (info); |
263 | (*fill_vtable) (vtable); |
264 | |
265 | if (loader_sanity_check (path, info, vtable)) |
266 | write_loader_info (contents, path, info); |
267 | |
268 | g_free (mem: info); |
269 | g_free (mem: vtable); |
270 | } |
271 | else { |
272 | if (module == NULL) |
273 | g_fprintf (stderr, format: "g_module_open() failed for %s: %s\n" , path, |
274 | g_module_error()); |
275 | else |
276 | g_fprintf (stderr, format: "Cannot load loader %s\n" , path); |
277 | } |
278 | if (module) |
279 | g_module_close (module); |
280 | g_free (mem: path); |
281 | } |
282 | |
283 | #if defined(G_OS_WIN32) && defined(GDK_PIXBUF_RELOCATABLE) |
284 | |
285 | static char * |
286 | get_libdir (void) |
287 | { |
288 | static char *libdir = NULL; |
289 | |
290 | if (libdir == NULL) |
291 | libdir = g_build_filename (get_toplevel (), "lib" , NULL); |
292 | |
293 | return libdir; |
294 | } |
295 | |
296 | #undef GDK_PIXBUF_LIBDIR |
297 | #define GDK_PIXBUF_LIBDIR get_libdir() |
298 | |
299 | #endif |
300 | |
301 | static gchar * |
302 | gdk_pixbuf_get_module_file (void) |
303 | { |
304 | gchar *result = g_strdup (str: g_getenv (variable: "GDK_PIXBUF_MODULE_FILE" )); |
305 | |
306 | if (!result) |
307 | result = g_build_filename (GDK_PIXBUF_LIBDIR, "gdk-pixbuf-2.0" , GDK_PIXBUF_BINARY_VERSION, "loaders.cache" , NULL); |
308 | |
309 | return result; |
310 | } |
311 | |
312 | int main (int argc, char **argv) |
313 | { |
314 | gint i; |
315 | const gchar *prgname; |
316 | GString *contents; |
317 | gchar *cache_file = NULL; |
318 | gint first_file = 1; |
319 | GFile *pixbuf_libdir_file; |
320 | gchar *pixbuf_libdir; |
321 | |
322 | #ifdef G_OS_WIN32 |
323 | gchar *libdir; |
324 | GFile *pixbuf_prefix_file; |
325 | gchar *pixbuf_prefix; |
326 | #endif |
327 | |
328 | /* An intermediate GFile here will convert all the path separators |
329 | * to the right one used by the platform |
330 | */ |
331 | pixbuf_libdir_file = g_file_new_for_path (PIXBUF_LIBDIR); |
332 | pixbuf_libdir = g_file_get_path (file: pixbuf_libdir_file); |
333 | g_object_unref (object: pixbuf_libdir_file); |
334 | |
335 | #ifdef G_OS_WIN32 |
336 | pixbuf_prefix_file = g_file_new_for_path (GDK_PIXBUF_PREFIX); |
337 | pixbuf_prefix = g_file_get_path (pixbuf_prefix_file); |
338 | g_object_unref (pixbuf_prefix_file); |
339 | |
340 | if (g_ascii_strncasecmp (pixbuf_libdir, pixbuf_prefix, strlen (pixbuf_prefix)) == 0 && |
341 | G_IS_DIR_SEPARATOR (pixbuf_libdir[strlen (pixbuf_prefix)])) { |
342 | gchar *runtime_prefix; |
343 | gchar *slash; |
344 | |
345 | /* pixbuf_prefix is a prefix of pixbuf_libdir, as it |
346 | * normally is. Replace that prefix in pixbuf_libdir |
347 | * with the installation directory on this machine. |
348 | * We assume this invokation of |
349 | * gdk-pixbuf-query-loaders is run from either a "bin" |
350 | * subdirectory of the installation directory, or in |
351 | * the installation directory itself. |
352 | */ |
353 | wchar_t fn[1000]; |
354 | GetModuleFileNameW (NULL, fn, G_N_ELEMENTS (fn)); |
355 | runtime_prefix = g_utf16_to_utf8 (fn, -1, NULL, NULL, NULL); |
356 | slash = strrchr (runtime_prefix, '\\'); |
357 | *slash = '\0'; |
358 | slash = strrchr (runtime_prefix, '\\'); |
359 | /* If running from some weird location, or from the |
360 | * build directory (either in the .libs folder where |
361 | * libtool places the real executable when using a |
362 | * wrapper, or directly from the gdk-pixbuf folder), |
363 | * use the compile-time libdir. |
364 | */ |
365 | if (slash == NULL || |
366 | g_ascii_strcasecmp (slash + 1, ".libs" ) == 0 || |
367 | g_ascii_strcasecmp (slash + 1, "gdk-pixbuf" ) == 0) { |
368 | libdir = NULL; |
369 | } |
370 | else { |
371 | if (slash != NULL && g_ascii_strcasecmp (slash + 1, "bin" ) == 0) { |
372 | *slash = '\0'; |
373 | } |
374 | |
375 | libdir = g_build_filename (runtime_prefix, |
376 | pixbuf_libdir + strlen (pixbuf_prefix) + 1, |
377 | NULL); |
378 | } |
379 | } |
380 | else { |
381 | libdir = NULL; |
382 | } |
383 | |
384 | g_free (pixbuf_prefix); |
385 | |
386 | if (libdir != NULL) { |
387 | g_free (pixbuf_libdir); |
388 | pixbuf_libdir = libdir; |
389 | } |
390 | #endif |
391 | |
392 | /* This call is necessary to ensure we actually link against libgobject; |
393 | * otherwise it may be stripped if -Wl,--as-needed is in use. |
394 | * |
395 | * The reason we need to link against libgobject is because it now has |
396 | * a global constructor. If the dynamically loaded modules happen |
397 | * to dlclose() libgobject, then reopen it again, we're in for trouble. |
398 | * |
399 | * See: https://bugzilla.gnome.org/show_bug.cgi?id=686822 |
400 | */ |
401 | g_type_ensure (G_TYPE_OBJECT); |
402 | |
403 | if (argc > 1 && strcmp (s1: argv[1], s2: "--update-cache" ) == 0) { |
404 | cache_file = gdk_pixbuf_get_module_file (); |
405 | first_file = 2; |
406 | } |
407 | |
408 | contents = g_string_new (init: "" ); |
409 | |
410 | prgname = g_get_prgname (); |
411 | g_string_append_printf (string: contents, |
412 | format: "# GdkPixbuf Image Loader Modules file\n" |
413 | "# Automatically generated file, do not edit\n" |
414 | "# Created by %s from gdk-pixbuf-%s\n" |
415 | "#\n" , |
416 | (prgname ? prgname : "gdk-pixbuf-query-loaders" ), |
417 | GDK_PIXBUF_VERSION); |
418 | |
419 | if (argc == first_file) { |
420 | #ifdef USE_GMODULE |
421 | char *moduledir; |
422 | GDir *dir; |
423 | GList *l, *modules; |
424 | |
425 | moduledir = g_strdup (str: g_getenv (variable: "GDK_PIXBUF_MODULEDIR" )); |
426 | #ifdef G_OS_WIN32 |
427 | if (moduledir != NULL && *moduledir != '\0') { |
428 | gchar *path; |
429 | |
430 | path = g_locale_to_utf8 (moduledir, -1, NULL, NULL, NULL); |
431 | g_free (moduledir); |
432 | moduledir = path; |
433 | } |
434 | #endif |
435 | if (moduledir == NULL || *moduledir == '\0') { |
436 | g_free (mem: moduledir); |
437 | moduledir = g_strdup (str: pixbuf_libdir); |
438 | } |
439 | |
440 | g_string_append_printf (string: contents, format: "# LoaderDir = %s\n#\n" , moduledir); |
441 | |
442 | modules = NULL; |
443 | dir = g_dir_open (path: moduledir, flags: 0, NULL); |
444 | if (dir) { |
445 | const char *dent; |
446 | |
447 | while ((dent = g_dir_read_name (dir))) { |
448 | gint len = strlen (s: dent); |
449 | if (len > SOEXT_LEN && |
450 | strcmp (s1: dent + len - SOEXT_LEN, SOEXT) == 0) { |
451 | modules = g_list_prepend (list: modules, |
452 | data: g_strdup (str: dent)); |
453 | } |
454 | } |
455 | g_dir_close (dir); |
456 | } |
457 | modules = g_list_sort (list: modules, compare_func: (GCompareFunc)strcmp); |
458 | for (l = modules; l != NULL; l = l->next) |
459 | query_module (contents, dir: moduledir, file: l->data); |
460 | g_list_free_full (list: modules, free_func: g_free); |
461 | g_free (mem: moduledir); |
462 | #else |
463 | g_string_append_printf (contents, "# dynamic loading of modules not supported\n" ); |
464 | #endif |
465 | } |
466 | else { |
467 | char *cwd = g_get_current_dir (); |
468 | |
469 | for (i = first_file; i < argc; i++) { |
470 | char *infilename = argv[i]; |
471 | #ifdef G_OS_WIN32 |
472 | infilename = g_locale_to_utf8 (infilename, |
473 | -1, NULL, NULL, NULL); |
474 | #endif |
475 | query_module (contents, dir: cwd, file: infilename); |
476 | } |
477 | g_free (mem: cwd); |
478 | } |
479 | |
480 | if (cache_file) { |
481 | GError *err; |
482 | |
483 | err = NULL; |
484 | if (!g_file_set_contents (filename: cache_file, contents: contents->str, length: -1, error: &err)) { |
485 | g_fprintf (stderr, format: "%s\n" , err->message); |
486 | } |
487 | } |
488 | else |
489 | g_print (format: "%s\n" , contents->str); |
490 | |
491 | g_free (mem: pixbuf_libdir); |
492 | |
493 | return 0; |
494 | } |
495 | |