1 /* LIBGIMP - The GIMP Library
2 * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
3 *
4 * gimputils.c
5 * Copyright (C) 2003 Sven Neumann <sven@gimp.org>
6 *
7 * This library is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 3 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library. If not, see
19 * <https://www.gnu.org/licenses/>.
20 */
21
22 #include "config.h"
23
24 #include <stdlib.h>
25 #include <string.h>
26 #include <stdio.h>
27
28 #ifdef PLATFORM_OSX
29 #include <AppKit/AppKit.h>
30 #endif
31
32 #ifdef HAVE_EXECINFO_H
33 /* Allowing backtrace() API. */
34 #include <execinfo.h>
35 #endif
36
37 #include <gio/gio.h>
38 #include <glib/gprintf.h>
39
40 #if defined(G_OS_WIN32)
41 # include <windows.h>
42 # include <shlobj.h>
43
44 #else /* G_OS_WIN32 */
45
46 /* For waitpid() */
47 #include <sys/wait.h>
48 #include <unistd.h>
49 #include <errno.h>
50
51 /* For thread IDs. */
52 #include <sys/types.h>
53 #include <sys/syscall.h>
54
55 #ifdef HAVE_SYS_PRCTL_H
56 #include <sys/prctl.h>
57 #endif
58
59 #ifdef HAVE_SYS_THR_H
60 #include <sys/thr.h>
61 #endif
62
63 #endif /* G_OS_WIN32 */
64
65 #include "gimpbasetypes.h"
66 #include "gimputils.h"
67
68 #include "libgimp/libgimp-intl.h"
69
70
71 /**
72 * SECTION: gimputils
73 * @title: gimputils
74 * @short_description: Utilities of general interest
75 *
76 * Utilities of general interest
77 **/
78
79 static gboolean gimp_utils_generic_available (const gchar *program,
80 gint major,
81 gint minor);
82 static gboolean gimp_utils_gdb_available (gint major,
83 gint minor);
84
85 /**
86 * gimp_utf8_strtrim:
87 * @str: an UTF-8 encoded string (or %NULL)
88 * @max_chars: the maximum number of characters before the string get
89 * trimmed
90 *
91 * Creates a (possibly trimmed) copy of @str. The string is cut if it
92 * exceeds @max_chars characters or on the first newline. The fact
93 * that the string was trimmed is indicated by appending an ellipsis.
94 *
95 * Returns: A (possibly trimmed) copy of @str which should be freed
96 * using g_free() when it is not needed any longer.
97 **/
98 gchar *
gimp_utf8_strtrim(const gchar * str,gint max_chars)99 gimp_utf8_strtrim (const gchar *str,
100 gint max_chars)
101 {
102 /* FIXME: should we make this translatable? */
103 const gchar ellipsis[] = "...";
104 const gint e_len = strlen (ellipsis);
105
106 if (str)
107 {
108 const gchar *p;
109 const gchar *newline = NULL;
110 gint chars = 0;
111 gunichar unichar;
112
113 for (p = str; *p; p = g_utf8_next_char (p))
114 {
115 if (++chars > max_chars)
116 break;
117
118 unichar = g_utf8_get_char (p);
119
120 switch (g_unichar_break_type (unichar))
121 {
122 case G_UNICODE_BREAK_MANDATORY:
123 case G_UNICODE_BREAK_LINE_FEED:
124 newline = p;
125 break;
126 default:
127 continue;
128 }
129
130 break;
131 }
132
133 if (*p)
134 {
135 gsize len = p - str;
136 gchar *trimmed = g_new (gchar, len + e_len + 2);
137
138 memcpy (trimmed, str, len);
139 if (newline)
140 trimmed[len++] = ' ';
141
142 g_strlcpy (trimmed + len, ellipsis, e_len + 1);
143
144 return trimmed;
145 }
146
147 return g_strdup (str);
148 }
149
150 return NULL;
151 }
152
153 /**
154 * gimp_any_to_utf8:
155 * @str: The string to be converted to UTF-8.
156 * @len: The length of the string, or -1 if the string
157 * is nul-terminated.
158 * @warning_format: The message format for the warning message if conversion
159 * to UTF-8 fails. See the <function>printf()</function>
160 * documentation.
161 * @...: The parameters to insert into the format string.
162 *
163 * This function takes any string (UTF-8 or not) and always returns a valid
164 * UTF-8 string.
165 *
166 * If @str is valid UTF-8, a copy of the string is returned.
167 *
168 * If UTF-8 validation fails, g_locale_to_utf8() is tried and if it
169 * succeeds the resulting string is returned.
170 *
171 * Otherwise, the portion of @str that is UTF-8, concatenated
172 * with "(invalid UTF-8 string)" is returned. If not even the start
173 * of @str is valid UTF-8, only "(invalid UTF-8 string)" is returned.
174 *
175 * Return value: The UTF-8 string as described above.
176 **/
177 gchar *
gimp_any_to_utf8(const gchar * str,gssize len,const gchar * warning_format,...)178 gimp_any_to_utf8 (const gchar *str,
179 gssize len,
180 const gchar *warning_format,
181 ...)
182 {
183 const gchar *start_invalid;
184 gchar *utf8;
185
186 g_return_val_if_fail (str != NULL, NULL);
187
188 if (g_utf8_validate (str, len, &start_invalid))
189 {
190 if (len < 0)
191 utf8 = g_strdup (str);
192 else
193 utf8 = g_strndup (str, len);
194 }
195 else
196 {
197 utf8 = g_locale_to_utf8 (str, len, NULL, NULL, NULL);
198 }
199
200 if (! utf8)
201 {
202 if (warning_format)
203 {
204 va_list warning_args;
205
206 va_start (warning_args, warning_format);
207
208 g_logv (G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE,
209 warning_format, warning_args);
210
211 va_end (warning_args);
212 }
213
214 if (start_invalid > str)
215 {
216 gchar *tmp;
217
218 tmp = g_strndup (str, start_invalid - str);
219 utf8 = g_strconcat (tmp, " ", _("(invalid UTF-8 string)"), NULL);
220 g_free (tmp);
221 }
222 else
223 {
224 utf8 = g_strdup (_("(invalid UTF-8 string)"));
225 }
226 }
227
228 return utf8;
229 }
230
231 /**
232 * gimp_filename_to_utf8:
233 * @filename: The filename to be converted to UTF-8.
234 *
235 * Convert a filename in the filesystem's encoding to UTF-8
236 * temporarily. The return value is a pointer to a string that is
237 * guaranteed to be valid only during the current iteration of the
238 * main loop or until the next call to gimp_filename_to_utf8().
239 *
240 * The only purpose of this function is to provide an easy way to pass
241 * a filename in the filesystem encoding to a function that expects an
242 * UTF-8 encoded filename.
243 *
244 * Return value: A temporarily valid UTF-8 representation of @filename.
245 * This string must not be changed or freed.
246 **/
247 const gchar *
gimp_filename_to_utf8(const gchar * filename)248 gimp_filename_to_utf8 (const gchar *filename)
249 {
250 /* Simpleminded implementation, but at least allocates just one copy
251 * of each translation. Could check if already UTF-8, and if so
252 * return filename as is. Could perhaps (re)use a suitably large
253 * cyclic buffer, but then would have to verify that all calls
254 * really need the return value just for a "short" time.
255 */
256
257 static GHashTable *ht = NULL;
258 gchar *filename_utf8;
259
260 if (! filename)
261 return NULL;
262
263 if (! ht)
264 ht = g_hash_table_new (g_str_hash, g_str_equal);
265
266 filename_utf8 = g_hash_table_lookup (ht, filename);
267
268 if (! filename_utf8)
269 {
270 filename_utf8 = g_filename_display_name (filename);
271 g_hash_table_insert (ht, g_strdup (filename), filename_utf8);
272 }
273
274 return filename_utf8;
275 }
276
277 /**
278 * gimp_file_get_utf8_name:
279 * @file: a #GFile
280 *
281 * This function works like gimp_filename_to_utf8() and returns
282 * a UTF-8 encoded string that does not need to be freed.
283 *
284 * It converts a #GFile's path or uri to UTF-8 temporarily. The
285 * return value is a pointer to a string that is guaranteed to be
286 * valid only during the current iteration of the main loop or until
287 * the next call to gimp_file_get_utf8_name().
288 *
289 * The only purpose of this function is to provide an easy way to pass
290 * a #GFile's name to a function that expects an UTF-8 encoded string.
291 *
292 * See g_file_get_parse_name().
293 *
294 * Since: 2.10
295 *
296 * Return value: A temporarily valid UTF-8 representation of @file's name.
297 * This string must not be changed or freed.
298 **/
299 const gchar *
gimp_file_get_utf8_name(GFile * file)300 gimp_file_get_utf8_name (GFile *file)
301 {
302 gchar *name;
303
304 g_return_val_if_fail (G_IS_FILE (file), NULL);
305
306 name = g_file_get_parse_name (file);
307
308 g_object_set_data_full (G_OBJECT (file), "gimp-parse-name", name,
309 (GDestroyNotify) g_free);
310
311 return name;
312 }
313
314 /**
315 * gimp_file_has_extension:
316 * @file: a #GFile
317 * @extension: an ASCII extension
318 *
319 * This function checks if @file's URI ends with @extension. It behaves
320 * like g_str_has_suffix() on g_file_get_uri(), except that the string
321 * comparison is done case-insensitively using g_ascii_strcasecmp().
322 *
323 * Since: 2.10
324 *
325 * Return value: %TRUE if @file's URI ends with @extension,
326 * %FALSE otherwise.
327 **/
328 gboolean
gimp_file_has_extension(GFile * file,const gchar * extension)329 gimp_file_has_extension (GFile *file,
330 const gchar *extension)
331 {
332 gchar *uri;
333 gint uri_len;
334 gint ext_len;
335 gboolean result = FALSE;
336
337 g_return_val_if_fail (G_IS_FILE (file), FALSE);
338 g_return_val_if_fail (extension != NULL, FALSE);
339
340 uri = g_file_get_uri (file);
341
342 uri_len = strlen (uri);
343 ext_len = strlen (extension);
344
345 if (uri_len && ext_len && (uri_len > ext_len))
346 {
347 if (g_ascii_strcasecmp (uri + uri_len - ext_len, extension) == 0)
348 result = TRUE;
349 }
350
351 g_free (uri);
352
353 return result;
354 }
355
356 /**
357 * gimp_file_show_in_file_manager:
358 * @file: a #GFile
359 * @error: return location for a #GError
360 *
361 * Shows @file in the system file manager.
362 *
363 * Since: 2.10
364 *
365 * Return value: %TRUE on success, %FALSE otherwise. On %FALSE, @error
366 * is set.
367 **/
368 gboolean
gimp_file_show_in_file_manager(GFile * file,GError ** error)369 gimp_file_show_in_file_manager (GFile *file,
370 GError **error)
371 {
372 g_return_val_if_fail (G_IS_FILE (file), FALSE);
373 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
374
375 #if defined(G_OS_WIN32)
376
377 {
378 gboolean ret;
379 char *filename;
380 int n;
381 LPWSTR w_filename = NULL;
382 ITEMIDLIST *pidl = NULL;
383
384 ret = FALSE;
385
386 /* Calling this function multiple times should do no harm, but it is
387 easier to put this here as it needs linking against ole32. */
388 CoInitialize (NULL);
389
390 filename = g_file_get_path (file);
391 if (!filename)
392 {
393 g_set_error_literal (error, G_FILE_ERROR, 0,
394 _("File path is NULL"));
395 goto out;
396 }
397
398 n = MultiByteToWideChar (CP_UTF8, MB_ERR_INVALID_CHARS,
399 filename, -1, NULL, 0);
400 if (n == 0)
401 {
402 g_set_error_literal (error, G_FILE_ERROR, 0,
403 _("Error converting UTF-8 filename to wide char"));
404 goto out;
405 }
406
407 w_filename = g_malloc_n (n + 1, sizeof (wchar_t));
408 n = MultiByteToWideChar (CP_UTF8, MB_ERR_INVALID_CHARS,
409 filename, -1,
410 w_filename, (n + 1) * sizeof (wchar_t));
411 if (n == 0)
412 {
413 g_set_error_literal (error, G_FILE_ERROR, 0,
414 _("Error converting UTF-8 filename to wide char"));
415 goto out;
416 }
417
418 pidl = ILCreateFromPathW (w_filename);
419 if (!pidl)
420 {
421 g_set_error_literal (error, G_FILE_ERROR, 0,
422 _("ILCreateFromPath() failed"));
423 goto out;
424 }
425
426 SHOpenFolderAndSelectItems (pidl, 0, NULL, 0);
427 ret = TRUE;
428
429 out:
430 if (pidl)
431 ILFree (pidl);
432 g_free (w_filename);
433 g_free (filename);
434
435 return ret;
436 }
437
438 #elif defined(PLATFORM_OSX)
439
440 {
441 gchar *uri;
442 NSString *filename;
443 NSURL *url;
444 gboolean retval = TRUE;
445
446 uri = g_file_get_uri (file);
447 filename = [NSString stringWithUTF8String:uri];
448
449 url = [NSURL URLWithString:filename];
450 if (url)
451 {
452 NSArray *url_array = [NSArray arrayWithObject:url];
453
454 [[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs:url_array];
455 }
456 else
457 {
458 g_set_error (error, G_FILE_ERROR, 0,
459 _("Cannot convert '%s' into a valid NSURL."), uri);
460 retval = FALSE;
461 }
462
463 g_free (uri);
464
465 return retval;
466 }
467
468 #else /* UNIX */
469
470 {
471 GDBusProxy *proxy;
472 GVariant *retval;
473 GVariantBuilder *builder;
474 gchar *uri;
475
476 proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
477 G_DBUS_PROXY_FLAGS_NONE,
478 NULL,
479 "org.freedesktop.FileManager1",
480 "/org/freedesktop/FileManager1",
481 "org.freedesktop.FileManager1",
482 NULL, error);
483
484 if (! proxy)
485 {
486 g_prefix_error (error,
487 _("Connecting to org.freedesktop.FileManager1 failed: "));
488 return FALSE;
489 }
490
491 uri = g_file_get_uri (file);
492
493 builder = g_variant_builder_new (G_VARIANT_TYPE ("as"));
494 g_variant_builder_add (builder, "s", uri);
495
496 g_free (uri);
497
498 retval = g_dbus_proxy_call_sync (proxy,
499 "ShowItems",
500 g_variant_new ("(ass)",
501 builder,
502 ""),
503 G_DBUS_CALL_FLAGS_NONE,
504 -1, NULL, error);
505
506 g_variant_builder_unref (builder);
507 g_object_unref (proxy);
508
509 if (! retval)
510 {
511 g_prefix_error (error, _("Calling ShowItems failed: "));
512 return FALSE;
513 }
514
515 g_variant_unref (retval);
516
517 return TRUE;
518 }
519
520 #endif
521 }
522
523 /**
524 * gimp_strip_uline:
525 * @str: underline infested string (or %NULL)
526 *
527 * This function returns a copy of @str stripped of underline
528 * characters. This comes in handy when needing to strip mnemonics
529 * from menu paths etc.
530 *
531 * In some languages, mnemonics are handled by adding the mnemonic
532 * character in brackets (like "File (_F)"). This function recognizes
533 * this construct and removes the whole bracket construction to get
534 * rid of the mnemonic (see bug 157561).
535 *
536 * Return value: A (possibly stripped) copy of @str which should be
537 * freed using g_free() when it is not needed any longer.
538 **/
539 gchar *
gimp_strip_uline(const gchar * str)540 gimp_strip_uline (const gchar *str)
541 {
542 gchar *escaped;
543 gchar *p;
544 gboolean past_bracket = FALSE;
545
546 if (! str)
547 return NULL;
548
549 p = escaped = g_strdup (str);
550
551 while (*str)
552 {
553 if (*str == '_')
554 {
555 /* "__" means a literal "_" in the menu path */
556 if (str[1] == '_')
557 {
558 *p++ = *str++;
559 str++;
560 continue;
561 }
562
563 /* find the "(_X)" construct and remove it entirely */
564 if (past_bracket && str[1] && *(g_utf8_next_char (str + 1)) == ')')
565 {
566 str = g_utf8_next_char (str + 1) + 1;
567 p--;
568 }
569 else
570 {
571 str++;
572 }
573 }
574 else
575 {
576 past_bracket = (*str == '(');
577
578 *p++ = *str++;
579 }
580 }
581
582 *p = '\0';
583
584 return escaped;
585 }
586
587 /**
588 * gimp_escape_uline:
589 * @str: Underline infested string (or %NULL)
590 *
591 * This function returns a copy of @str with all underline converted
592 * to two adjacent underlines. This comes in handy when needing to display
593 * strings with underlines (like filenames) in a place that would convert
594 * them to mnemonics.
595 *
596 * Return value: A (possibly escaped) copy of @str which should be
597 * freed using g_free() when it is not needed any longer.
598 *
599 * Since: 2.2
600 **/
601 gchar *
gimp_escape_uline(const gchar * str)602 gimp_escape_uline (const gchar *str)
603 {
604 gchar *escaped;
605 gchar *p;
606 gint n_ulines = 0;
607
608 if (! str)
609 return NULL;
610
611 for (p = (gchar *) str; *p; p++)
612 if (*p == '_')
613 n_ulines++;
614
615 p = escaped = g_malloc (strlen (str) + n_ulines + 1);
616
617 while (*str)
618 {
619 if (*str == '_')
620 *p++ = '_';
621
622 *p++ = *str++;
623 }
624
625 *p = '\0';
626
627 return escaped;
628 }
629
630 /**
631 * gimp_canonicalize_identifier:
632 * @identifier: The identifier string to canonicalize.
633 *
634 * Turns any input string into a canonicalized string.
635 *
636 * Canonical identifiers are e.g. expected by the PDB for procedure
637 * and parameter names. Every character of the input string that is
638 * not either '-', 'a-z', 'A-Z' or '0-9' will be replaced by a '-'.
639 *
640 * Return value: The canonicalized identifier. This is a newly
641 * allocated string that should be freed with g_free()
642 * when no longer needed.
643 *
644 * Since: 2.4
645 **/
646 gchar *
gimp_canonicalize_identifier(const gchar * identifier)647 gimp_canonicalize_identifier (const gchar *identifier)
648 {
649 gchar *canonicalized = NULL;
650
651 if (identifier)
652 {
653 gchar *p;
654
655 canonicalized = g_strdup (identifier);
656
657 for (p = canonicalized; *p != 0; p++)
658 {
659 gchar c = *p;
660
661 if (c != '-' &&
662 (c < '0' || c > '9') &&
663 (c < 'A' || c > 'Z') &&
664 (c < 'a' || c > 'z'))
665 *p = '-';
666 }
667 }
668
669 return canonicalized;
670 }
671
672 /**
673 * gimp_enum_get_desc:
674 * @enum_class: a #GEnumClass
675 * @value: a value from @enum_class
676 *
677 * Retrieves #GimpEnumDesc associated with the given value, or %NULL.
678 *
679 * Return value: the value's #GimpEnumDesc.
680 *
681 * Since: 2.2
682 **/
683 GimpEnumDesc *
gimp_enum_get_desc(GEnumClass * enum_class,gint value)684 gimp_enum_get_desc (GEnumClass *enum_class,
685 gint value)
686 {
687 const GimpEnumDesc *value_desc;
688
689 g_return_val_if_fail (G_IS_ENUM_CLASS (enum_class), NULL);
690
691 value_desc =
692 gimp_enum_get_value_descriptions (G_TYPE_FROM_CLASS (enum_class));
693
694 if (value_desc)
695 {
696 while (value_desc->value_desc)
697 {
698 if (value_desc->value == value)
699 return (GimpEnumDesc *) value_desc;
700
701 value_desc++;
702 }
703 }
704
705 return NULL;
706 }
707
708 /**
709 * gimp_enum_get_value:
710 * @enum_type: the #GType of a registered enum
711 * @value: an integer value
712 * @value_name: return location for the value's name (or %NULL)
713 * @value_nick: return location for the value's nick (or %NULL)
714 * @value_desc: return location for the value's translated description (or %NULL)
715 * @value_help: return location for the value's translated help (or %NULL)
716 *
717 * Checks if @value is valid for the enum registered as @enum_type.
718 * If the value exists in that enum, its name, nick and its translated
719 * description and help are returned (if @value_name, @value_nick,
720 * @value_desc and @value_help are not %NULL).
721 *
722 * Return value: %TRUE if @value is valid for the @enum_type,
723 * %FALSE otherwise
724 *
725 * Since: 2.2
726 **/
727 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)728 gimp_enum_get_value (GType enum_type,
729 gint value,
730 const gchar **value_name,
731 const gchar **value_nick,
732 const gchar **value_desc,
733 const gchar **value_help)
734 {
735 GEnumClass *enum_class;
736 GEnumValue *enum_value;
737 gboolean success = FALSE;
738
739 g_return_val_if_fail (G_TYPE_IS_ENUM (enum_type), FALSE);
740
741 enum_class = g_type_class_ref (enum_type);
742 enum_value = g_enum_get_value (enum_class, value);
743
744 if (enum_value)
745 {
746 if (value_name)
747 *value_name = enum_value->value_name;
748
749 if (value_nick)
750 *value_nick = enum_value->value_nick;
751
752 if (value_desc || value_help)
753 {
754 GimpEnumDesc *enum_desc;
755
756 enum_desc = gimp_enum_get_desc (enum_class, value);
757
758 if (value_desc)
759 {
760 if (enum_desc && enum_desc->value_desc)
761 {
762 const gchar *context;
763
764 context = gimp_type_get_translation_context (enum_type);
765
766 if (context) /* the new way, using NC_() */
767 *value_desc = g_dpgettext2 (gimp_type_get_translation_domain (enum_type),
768 context,
769 enum_desc->value_desc);
770 else /* for backward compatibility */
771 *value_desc = g_strip_context (enum_desc->value_desc,
772 dgettext (gimp_type_get_translation_domain (enum_type),
773 enum_desc->value_desc));
774 }
775 else
776 {
777 *value_desc = NULL;
778 }
779 }
780
781 if (value_help)
782 {
783 *value_help = ((enum_desc && enum_desc->value_help) ?
784 dgettext (gimp_type_get_translation_domain (enum_type),
785 enum_desc->value_help) :
786 NULL);
787 }
788 }
789
790 success = TRUE;
791 }
792
793 g_type_class_unref (enum_class);
794
795 return success;
796 }
797
798 /**
799 * gimp_enum_value_get_desc:
800 * @enum_class: a #GEnumClass
801 * @enum_value: a #GEnumValue from @enum_class
802 *
803 * Retrieves the translated description for a given @enum_value.
804 *
805 * Return value: the translated description of the enum value
806 *
807 * Since: 2.2
808 **/
809 const gchar *
gimp_enum_value_get_desc(GEnumClass * enum_class,GEnumValue * enum_value)810 gimp_enum_value_get_desc (GEnumClass *enum_class,
811 GEnumValue *enum_value)
812 {
813 GType type = G_TYPE_FROM_CLASS (enum_class);
814 GimpEnumDesc *enum_desc;
815
816 enum_desc = gimp_enum_get_desc (enum_class, enum_value->value);
817
818 if (enum_desc && enum_desc->value_desc)
819 {
820 const gchar *context;
821
822 context = gimp_type_get_translation_context (type);
823
824 if (context) /* the new way, using NC_() */
825 return g_dpgettext2 (gimp_type_get_translation_domain (type),
826 context,
827 enum_desc->value_desc);
828 else /* for backward compatibility */
829 return g_strip_context (enum_desc->value_desc,
830 dgettext (gimp_type_get_translation_domain (type),
831 enum_desc->value_desc));
832 }
833
834 return enum_value->value_name;
835 }
836
837 /**
838 * gimp_enum_value_get_help:
839 * @enum_class: a #GEnumClass
840 * @enum_value: a #GEnumValue from @enum_class
841 *
842 * Retrieves the translated help for a given @enum_value.
843 *
844 * Return value: the translated help of the enum value
845 *
846 * Since: 2.2
847 **/
848 const gchar *
gimp_enum_value_get_help(GEnumClass * enum_class,GEnumValue * enum_value)849 gimp_enum_value_get_help (GEnumClass *enum_class,
850 GEnumValue *enum_value)
851 {
852 GType type = G_TYPE_FROM_CLASS (enum_class);
853 GimpEnumDesc *enum_desc;
854
855 enum_desc = gimp_enum_get_desc (enum_class, enum_value->value);
856
857 if (enum_desc && enum_desc->value_help)
858 return dgettext (gimp_type_get_translation_domain (type),
859 enum_desc->value_help);
860
861 return NULL;
862 }
863
864 /**
865 * gimp_enum_value_get_abbrev:
866 * @enum_class: a #GEnumClass
867 * @enum_value: a #GEnumValue from @enum_class
868 *
869 * Retrieves the translated abbreviation for a given @enum_value.
870 *
871 * Return value: the translated abbreviation of the enum value
872 *
873 * Since: 2.10
874 **/
875 const gchar *
gimp_enum_value_get_abbrev(GEnumClass * enum_class,GEnumValue * enum_value)876 gimp_enum_value_get_abbrev (GEnumClass *enum_class,
877 GEnumValue *enum_value)
878 {
879 GType type = G_TYPE_FROM_CLASS (enum_class);
880 GimpEnumDesc *enum_desc;
881
882 enum_desc = gimp_enum_get_desc (enum_class, enum_value->value);
883
884 if (enum_desc &&
885 enum_desc[1].value == enum_desc->value &&
886 enum_desc[1].value_desc)
887 {
888 return g_dpgettext2 (gimp_type_get_translation_domain (type),
889 gimp_type_get_translation_context (type),
890 enum_desc[1].value_desc);
891 }
892
893 return NULL;
894 }
895
896 /**
897 * gimp_flags_get_first_desc:
898 * @flags_class: a #GFlagsClass
899 * @value: a value from @flags_class
900 *
901 * Retrieves the first #GimpFlagsDesc that matches the given value, or %NULL.
902 *
903 * Return value: the value's #GimpFlagsDesc.
904 *
905 * Since: 2.2
906 **/
907 GimpFlagsDesc *
gimp_flags_get_first_desc(GFlagsClass * flags_class,guint value)908 gimp_flags_get_first_desc (GFlagsClass *flags_class,
909 guint value)
910 {
911 const GimpFlagsDesc *value_desc;
912
913 g_return_val_if_fail (G_IS_FLAGS_CLASS (flags_class), NULL);
914
915 value_desc =
916 gimp_flags_get_value_descriptions (G_TYPE_FROM_CLASS (flags_class));
917
918 if (value_desc)
919 {
920 while (value_desc->value_desc)
921 {
922 if ((value_desc->value & value) == value_desc->value)
923 return (GimpFlagsDesc *) value_desc;
924
925 value_desc++;
926 }
927 }
928
929 return NULL;
930 }
931
932 /**
933 * gimp_flags_get_first_value:
934 * @flags_type: the #GType of registered flags
935 * @value: an integer value
936 * @value_name: return location for the value's name (or %NULL)
937 * @value_nick: return location for the value's nick (or %NULL)
938 * @value_desc: return location for the value's translated description (or %NULL)
939 * @value_help: return location for the value's translated help (or %NULL)
940 *
941 * Checks if @value is valid for the flags registered as @flags_type.
942 * If the value exists in that flags, its name, nick and its
943 * translated description and help are returned (if @value_name,
944 * @value_nick, @value_desc and @value_help are not %NULL).
945 *
946 * Return value: %TRUE if @value is valid for the @flags_type,
947 * %FALSE otherwise
948 *
949 * Since: 2.2
950 **/
951 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)952 gimp_flags_get_first_value (GType flags_type,
953 guint value,
954 const gchar **value_name,
955 const gchar **value_nick,
956 const gchar **value_desc,
957 const gchar **value_help)
958 {
959 GFlagsClass *flags_class;
960 GFlagsValue *flags_value;
961
962 g_return_val_if_fail (G_TYPE_IS_FLAGS (flags_type), FALSE);
963
964 flags_class = g_type_class_peek (flags_type);
965 flags_value = g_flags_get_first_value (flags_class, value);
966
967 if (flags_value)
968 {
969 if (value_name)
970 *value_name = flags_value->value_name;
971
972 if (value_nick)
973 *value_nick = flags_value->value_nick;
974
975 if (value_desc || value_help)
976 {
977 GimpFlagsDesc *flags_desc;
978
979 flags_desc = gimp_flags_get_first_desc (flags_class, value);
980
981 if (value_desc)
982 *value_desc = ((flags_desc && flags_desc->value_desc) ?
983 dgettext (gimp_type_get_translation_domain (flags_type),
984 flags_desc->value_desc) :
985 NULL);
986
987 if (value_help)
988 *value_help = ((flags_desc && flags_desc->value_desc) ?
989 dgettext (gimp_type_get_translation_domain (flags_type),
990 flags_desc->value_help) :
991 NULL);
992 }
993
994 return TRUE;
995 }
996
997 return FALSE;
998 }
999
1000 /**
1001 * gimp_flags_value_get_desc:
1002 * @flags_class: a #GFlagsClass
1003 * @flags_value: a #GFlagsValue from @flags_class
1004 *
1005 * Retrieves the translated description for a given @flags_value.
1006 *
1007 * Return value: the translated description of the flags value
1008 *
1009 * Since: 2.2
1010 **/
1011 const gchar *
gimp_flags_value_get_desc(GFlagsClass * flags_class,GFlagsValue * flags_value)1012 gimp_flags_value_get_desc (GFlagsClass *flags_class,
1013 GFlagsValue *flags_value)
1014 {
1015 GType type = G_TYPE_FROM_CLASS (flags_class);
1016 GimpFlagsDesc *flags_desc;
1017
1018 flags_desc = gimp_flags_get_first_desc (flags_class, flags_value->value);
1019
1020 if (flags_desc->value_desc)
1021 {
1022 const gchar *context;
1023
1024 context = gimp_type_get_translation_context (type);
1025
1026 if (context) /* the new way, using NC_() */
1027 return g_dpgettext2 (gimp_type_get_translation_domain (type),
1028 context,
1029 flags_desc->value_desc);
1030 else /* for backward compatibility */
1031 return g_strip_context (flags_desc->value_desc,
1032 dgettext (gimp_type_get_translation_domain (type),
1033 flags_desc->value_desc));
1034 }
1035
1036 return flags_value->value_name;
1037 }
1038
1039 /**
1040 * gimp_flags_value_get_help:
1041 * @flags_class: a #GFlagsClass
1042 * @flags_value: a #GFlagsValue from @flags_class
1043 *
1044 * Retrieves the translated help for a given @flags_value.
1045 *
1046 * Return value: the translated help of the flags value
1047 *
1048 * Since: 2.2
1049 **/
1050 const gchar *
gimp_flags_value_get_help(GFlagsClass * flags_class,GFlagsValue * flags_value)1051 gimp_flags_value_get_help (GFlagsClass *flags_class,
1052 GFlagsValue *flags_value)
1053 {
1054 GType type = G_TYPE_FROM_CLASS (flags_class);
1055 GimpFlagsDesc *flags_desc;
1056
1057 flags_desc = gimp_flags_get_first_desc (flags_class, flags_value->value);
1058
1059 if (flags_desc->value_help)
1060 return dgettext (gimp_type_get_translation_domain (type),
1061 flags_desc->value_help);
1062
1063 return NULL;
1064 }
1065
1066 /**
1067 * gimp_flags_value_get_abbrev:
1068 * @flags_class: a #GFlagsClass
1069 * @flags_value: a #GFlagsValue from @flags_class
1070 *
1071 * Retrieves the translated abbreviation for a given @flags_value.
1072 *
1073 * Return value: the translated abbreviation of the flags value
1074 *
1075 * Since: 2.10
1076 **/
1077 const gchar *
gimp_flags_value_get_abbrev(GFlagsClass * flags_class,GFlagsValue * flags_value)1078 gimp_flags_value_get_abbrev (GFlagsClass *flags_class,
1079 GFlagsValue *flags_value)
1080 {
1081 GType type = G_TYPE_FROM_CLASS (flags_class);
1082 GimpFlagsDesc *flags_desc;
1083
1084 flags_desc = gimp_flags_get_first_desc (flags_class, flags_value->value);
1085
1086 if (flags_desc &&
1087 flags_desc[1].value == flags_desc->value &&
1088 flags_desc[1].value_desc)
1089 {
1090 return g_dpgettext2 (gimp_type_get_translation_domain (type),
1091 gimp_type_get_translation_context (type),
1092 flags_desc[1].value_desc);
1093 }
1094
1095 return NULL;
1096 }
1097
1098 /**
1099 * gimp_stack_trace_available:
1100 * @optimal: whether we get optimal traces.
1101 *
1102 * Returns #TRUE if we have dependencies to generate backtraces. If
1103 * @optimal is #TRUE, the function will return #TRUE only when we
1104 * are able to generate optimal traces (i.e. with GDB or LLDB);
1105 * otherwise we return #TRUE even if only backtrace() API is available.
1106 *
1107 * On Win32, we return TRUE if Dr. Mingw is built-in, FALSE otherwise.
1108 *
1109 * Note: this function is not crash-safe, i.e. you should not try to use
1110 * it in a callback when the program is already crashing. In such a
1111 * case, call gimp_stack_trace_print() or gimp_stack_trace_query()
1112 * directly.
1113 *
1114 * Since: 2.10
1115 **/
1116 gboolean
gimp_stack_trace_available(gboolean optimal)1117 gimp_stack_trace_available (gboolean optimal)
1118 {
1119 #ifndef G_OS_WIN32
1120 gchar *lld_path = NULL;
1121 gboolean has_lldb = FALSE;
1122
1123 /* Similarly to gdb, we could check for lldb by calling:
1124 * gimp_utils_generic_available ("lldb", major, minor).
1125 * We don't do so on purpose because on macOS, when lldb is absent, it
1126 * triggers a popup asking to install Xcode. So instead, we just
1127 * search for the executable in path.
1128 * This is the reason why this function is not crash-safe, since
1129 * g_find_program_in_path() allocates memory.
1130 * See issue #1999.
1131 */
1132 lld_path = g_find_program_in_path ("lldb");
1133 if (lld_path)
1134 {
1135 has_lldb = TRUE;
1136 g_free (lld_path);
1137 }
1138
1139 if (gimp_utils_gdb_available (7, 0) || has_lldb)
1140 return TRUE;
1141 #ifdef HAVE_EXECINFO_H
1142 if (! optimal)
1143 return TRUE;
1144 #endif
1145 #else /* G_OS_WIN32 */
1146 #ifdef HAVE_EXCHNDL
1147 return TRUE;
1148 #endif
1149 #endif /* G_OS_WIN32 */
1150 return FALSE;
1151 }
1152
1153 /**
1154 * gimp_stack_trace_print:
1155 * @prog_name: the program to attach to.
1156 * @stream: a #FILE * stream.
1157 * @trace: location to store a newly allocated string of the trace.
1158 *
1159 * Attempts to generate a stack trace at current code position in
1160 * @prog_name. @prog_name is mostly a helper and can be set to NULL.
1161 * Nevertheless if set, it has to be the current program name (argv[0]).
1162 * This function is not meant to generate stack trace for third-party
1163 * programs, and will attach the current process id only.
1164 * Internally, this function uses `gdb` or `lldb` if they are available,
1165 * or the stacktrace() API on platforms where it is available. It always
1166 * fails on Win32.
1167 *
1168 * The stack trace, once generated, will either be printed to @stream or
1169 * returned as a newly allocated string in @trace, if not #NULL.
1170 *
1171 * In some error cases (e.g. segmentation fault), trying to allocate
1172 * more memory will trigger more segmentation faults and therefore loop
1173 * our error handling (which is just wrong). Therefore printing to a
1174 * file description is an implementation without any memory allocation.
1175
1176 * Return value: #TRUE if a stack trace could be generated, #FALSE
1177 * otherwise.
1178 *
1179 * Since: 2.10
1180 **/
1181 gboolean
gimp_stack_trace_print(const gchar * prog_name,gpointer stream,gchar ** trace)1182 gimp_stack_trace_print (const gchar *prog_name,
1183 gpointer stream,
1184 gchar **trace)
1185 {
1186 gboolean stack_printed = FALSE;
1187
1188 /* This works only on UNIX systems. */
1189 #ifndef G_OS_WIN32
1190 GString *gtrace = NULL;
1191 gchar gimp_pid[16];
1192 gchar buffer[256];
1193 ssize_t read_n;
1194 int sync_fd[2];
1195 int out_fd[2];
1196 pid_t fork_pid;
1197 pid_t pid = getpid();
1198 gint eintr_count = 0;
1199 #if defined(G_OS_WIN32)
1200 DWORD tid = GetCurrentThreadId ();
1201 #elif defined(PLATFORM_OSX)
1202 uint64 tid64;
1203 long tid;
1204
1205 pthread_threadid_np (NULL, &tid64);
1206 tid = (long) tid64;
1207 #elif defined(SYS_gettid)
1208 long tid = syscall (SYS_gettid);
1209 #elif defined(HAVE_THR_SELF)
1210 long tid = 0;
1211 thr_self (&tid);
1212 #endif
1213
1214 g_snprintf (gimp_pid, 16, "%u", (guint) pid);
1215
1216 if (pipe (sync_fd) == -1)
1217 {
1218 return FALSE;
1219 }
1220
1221 if (pipe (out_fd) == -1)
1222 {
1223 close (sync_fd[0]);
1224 close (sync_fd[1]);
1225
1226 return FALSE;
1227 }
1228
1229 fork_pid = fork ();
1230 if (fork_pid == 0)
1231 {
1232 /* Child process. */
1233 gchar *args[9] = { "gdb", "-batch",
1234 "-ex", "info threads",
1235 "-ex", "thread apply all backtrace full",
1236 (gchar *) prog_name, NULL, NULL };
1237
1238 if (prog_name == NULL)
1239 args[6] = "-p";
1240
1241 args[7] = gimp_pid;
1242
1243 /* Wait until the parent enabled us to ptrace it. */
1244 {
1245 gchar dummy;
1246
1247 close (sync_fd[1]);
1248 while (read (sync_fd[0], &dummy, 1) < 0 && errno == EINTR);
1249 close (sync_fd[0]);
1250 }
1251
1252 /* Redirect the debugger output. */
1253 dup2 (out_fd[1], STDOUT_FILENO);
1254 close (out_fd[0]);
1255 close (out_fd[1]);
1256
1257 /* Run GDB if version 7.0 or over. Why I do such a check is that
1258 * it turns out older versions may not only fail, but also have
1259 * very undesirable side effects like terminating the debugged
1260 * program, at least on FreeBSD where GDB 6.1 is apparently
1261 * installed by default on the stable release at day of writing.
1262 * See bug 793514. */
1263 if (! gimp_utils_gdb_available (7, 0) ||
1264 execvp (args[0], args) == -1)
1265 {
1266 /* LLDB as alternative if the GDB call failed or if it was in
1267 * a too-old version. */
1268 gchar *args_lldb[15] = { "lldb", "--attach-pid", NULL, "--batch",
1269 "--one-line", "thread list",
1270 "--one-line", "thread backtrace all",
1271 "--one-line", "bt all",
1272 "--one-line-on-crash", "bt",
1273 "--one-line-on-crash", "quit", NULL };
1274
1275 args_lldb[2] = gimp_pid;
1276
1277 execvp (args_lldb[0], args_lldb);
1278 }
1279
1280 _exit (0);
1281 }
1282 else if (fork_pid > 0)
1283 {
1284 /* Main process */
1285 int status;
1286
1287 /* Allow the child to ptrace us, and signal it to start. */
1288 close (sync_fd[0]);
1289 #ifdef PR_SET_PTRACER
1290 prctl (PR_SET_PTRACER, fork_pid, 0, 0, 0);
1291 #endif
1292 close (sync_fd[1]);
1293
1294 /* It is important to close the writing side of the pipe, otherwise
1295 * the read() will wait forever without getting the information that
1296 * writing is finished.
1297 */
1298 close (out_fd[1]);
1299
1300 while ((read_n = read (out_fd[0], buffer, 256)) != 0)
1301 {
1302 if (read_n < 0)
1303 {
1304 /* LLDB on macOS seems to trigger a few EINTR error (see
1305 * !13), though read() finally ends up working later. So
1306 * let's not make this error fatal, and instead try again.
1307 * Yet to avoid infinite loop (in case the error really
1308 * happens at every call), we abandon after a few
1309 * consecutive errors.
1310 */
1311 if (errno == EINTR && eintr_count <= 5)
1312 {
1313 eintr_count++;
1314 continue;
1315 }
1316 break;
1317 }
1318 eintr_count = 0;
1319 if (! stack_printed)
1320 {
1321 #if defined(PLATFORM_OSX)
1322 if (stream)
1323 g_fprintf (stream,
1324 "\n# Stack traces obtained from PID %d - Thread 0x%lx #\n\n",
1325 pid, tid);
1326 #elif defined(G_OS_WIN32) || defined(SYS_gettid) || defined(HAVE_THR_SELF)
1327 if (stream)
1328 g_fprintf (stream,
1329 "\n# Stack traces obtained from PID %d - Thread %lu #\n\n",
1330 pid, tid);
1331 #endif
1332 if (trace)
1333 {
1334 gtrace = g_string_new (NULL);
1335 #if defined(PLATFORM_OSX)
1336 g_string_printf (gtrace,
1337 "\n# Stack traces obtained from PID %d - Thread 0x%lx #\n\n",
1338 pid, tid);
1339 #elif defined(G_OS_WIN32) || defined(SYS_gettid) || defined(HAVE_THR_SELF)
1340 g_string_printf (gtrace,
1341 "\n# Stack traces obtained from PID %d - Thread %lu #\n\n",
1342 pid, tid);
1343 #endif
1344 }
1345 }
1346 /* It's hard to know if the debugger was found since it
1347 * happened in the child. Let's just assume that any output
1348 * means it succeeded.
1349 */
1350 stack_printed = TRUE;
1351
1352 buffer[read_n] = '\0';
1353 if (stream)
1354 g_fprintf (stream, "%s", buffer);
1355 if (trace)
1356 g_string_append (gtrace, (const gchar *) buffer);
1357 }
1358 close (out_fd[0]);
1359
1360 #ifdef PR_SET_PTRACER
1361 /* Clear ptrace permission set above */
1362 prctl (PR_SET_PTRACER, 0, 0, 0, 0);
1363 #endif
1364
1365 waitpid (fork_pid, &status, 0);
1366 }
1367 /* else if (fork_pid == (pid_t) -1)
1368 * Fork failed!
1369 * Just continue, maybe the backtrace() API will succeed.
1370 */
1371
1372 #ifdef HAVE_EXECINFO_H
1373 if (! stack_printed)
1374 {
1375 /* As a last resort, try using the backtrace() Linux API. It is a bit
1376 * less fancy than gdb or lldb, which is why it is not given priority.
1377 */
1378 void *bt_buf[100];
1379 int n_symbols;
1380
1381 n_symbols = backtrace (bt_buf, 100);
1382 if (trace && n_symbols)
1383 {
1384 char **symbols;
1385 int i;
1386
1387 symbols = backtrace_symbols (bt_buf, n_symbols);
1388 if (symbols)
1389 {
1390 for (i = 0; i < n_symbols; i++)
1391 {
1392 if (stream)
1393 g_fprintf (stream, "%s\n", (const gchar *) symbols[i]);
1394 if (trace)
1395 {
1396 if (! gtrace)
1397 gtrace = g_string_new (NULL);
1398 g_string_append (gtrace,
1399 (const gchar *) symbols[i]);
1400 g_string_append_c (gtrace, '\n');
1401 }
1402 }
1403 free (symbols);
1404 }
1405 }
1406 else if (n_symbols)
1407 {
1408 /* This allows to generate traces without memory allocation.
1409 * In some cases, this is necessary, especially during
1410 * segfault-type crashes.
1411 */
1412 backtrace_symbols_fd (bt_buf, n_symbols, fileno ((FILE *) stream));
1413 }
1414 stack_printed = (n_symbols > 0);
1415 }
1416 #endif /* HAVE_EXECINFO_H */
1417
1418 if (trace)
1419 {
1420 if (gtrace)
1421 *trace = g_string_free (gtrace, FALSE);
1422 else
1423 *trace = NULL;
1424 }
1425 #endif /* G_OS_WIN32 */
1426
1427 return stack_printed;
1428 }
1429
1430 /**
1431 * gimp_stack_trace_query:
1432 * @prog_name: the program to attach to.
1433 *
1434 * This is mostly the same as g_on_error_query() except that we use our
1435 * own backtrace function, much more complete.
1436 * @prog_name must be the current program name (argv[0]).
1437 * It does nothing on Win32.
1438 *
1439 * Since: 2.10
1440 **/
1441 void
gimp_stack_trace_query(const gchar * prog_name)1442 gimp_stack_trace_query (const gchar *prog_name)
1443 {
1444 #ifndef G_OS_WIN32
1445 gchar buf[16];
1446
1447 retry:
1448
1449 g_fprintf (stdout,
1450 "%s (pid:%u): %s: ",
1451 prog_name,
1452 (guint) getpid (),
1453 "[E]xit, show [S]tack trace or [P]roceed");
1454 fflush (stdout);
1455
1456 if (isatty(0) && isatty(1))
1457 fgets (buf, 8, stdin);
1458 else
1459 strcpy (buf, "E\n");
1460
1461 if ((buf[0] == 'E' || buf[0] == 'e')
1462 && buf[1] == '\n')
1463 _exit (0);
1464 else if ((buf[0] == 'P' || buf[0] == 'p')
1465 && buf[1] == '\n')
1466 return;
1467 else if ((buf[0] == 'S' || buf[0] == 's')
1468 && buf[1] == '\n')
1469 {
1470 if (! gimp_stack_trace_print (prog_name, stdout, NULL))
1471 g_fprintf (stderr, "%s\n", "Stack trace not available on your system.");
1472 goto retry;
1473 }
1474 else
1475 goto retry;
1476 #endif
1477 }
1478
1479
1480 /* Private functions. */
1481
1482 static gboolean
gimp_utils_generic_available(const gchar * program,gint major,gint minor)1483 gimp_utils_generic_available (const gchar *program,
1484 gint major,
1485 gint minor)
1486 {
1487 #ifndef G_OS_WIN32
1488 pid_t pid;
1489 int out_fd[2];
1490
1491 if (pipe (out_fd) == -1)
1492 {
1493 return FALSE;
1494 }
1495
1496 /* XXX: I don't use g_spawn_sync() or similar glib functions because
1497 * to read the contents of the stdout, these functions would allocate
1498 * memory dynamically. As we know, when debugging crashes, this is a
1499 * definite blocker. So instead I simply use a buffer on the stack
1500 * with a lower level fork() call.
1501 */
1502 pid = fork ();
1503 if (pid == 0)
1504 {
1505 /* Child process. */
1506 gchar *args[3] = { (gchar *) program, "--version", NULL };
1507
1508 /* Redirect the debugger output. */
1509 dup2 (out_fd[1], STDOUT_FILENO);
1510 close (out_fd[0]);
1511 close (out_fd[1]);
1512
1513 /* Run version check. */
1514 execvp (args[0], args);
1515 _exit (-1);
1516 }
1517 else if (pid > 0)
1518 {
1519 /* Main process */
1520 gchar buffer[256];
1521 ssize_t read_n;
1522 int status;
1523 gint installed_major = 0;
1524 gint installed_minor = 0;
1525 gboolean major_reading = FALSE;
1526 gboolean minor_reading = FALSE;
1527 gint i;
1528 gchar c;
1529
1530 waitpid (pid, &status, 0);
1531
1532 if (! WIFEXITED (status) || WEXITSTATUS (status) != 0)
1533 return FALSE;
1534
1535 /* It is important to close the writing side of the pipe, otherwise
1536 * the read() will wait forever without getting the information that
1537 * writing is finished.
1538 */
1539 close (out_fd[1]);
1540
1541 /* I could loop forever until EOL, but I am pretty sure the
1542 * version information is stored on the first line and one call to
1543 * read() with 256 characters should be more than enough.
1544 */
1545 read_n = read (out_fd[0], buffer, 256);
1546
1547 /* This is quite a very stupid parser. I only look for the first
1548 * numbers and consider them as version information. This works
1549 * fine for both GDB and LLDB as far as I can see for the output
1550 * of `${program} --version` but this should obviously not be
1551 * considered as a *really* generic version test.
1552 */
1553 for (i = 0; i < read_n; i++)
1554 {
1555 c = buffer[i];
1556 if (c >= '0' && c <= '9')
1557 {
1558 if (minor_reading)
1559 {
1560 installed_minor = 10 * installed_minor + (c - '0');
1561 }
1562 else
1563 {
1564 major_reading = TRUE;
1565 installed_major = 10 * installed_major + (c - '0');
1566 }
1567 }
1568 else if (c == '.')
1569 {
1570 if (major_reading)
1571 {
1572 minor_reading = TRUE;
1573 major_reading = FALSE;
1574 }
1575 else if (minor_reading)
1576 {
1577 break;
1578 }
1579 }
1580 else if (c == '\n')
1581 {
1582 /* Version information should be in the first line. */
1583 break;
1584 }
1585 }
1586 close (out_fd[0]);
1587
1588 return (installed_major > 0 &&
1589 (installed_major > major ||
1590 (installed_major == major && installed_minor >= minor)));
1591 }
1592 #endif
1593
1594 /* Fork failed, or Win32. */
1595 return FALSE;
1596 }
1597
1598 static gboolean
gimp_utils_gdb_available(gint major,gint minor)1599 gimp_utils_gdb_available (gint major,
1600 gint minor)
1601 {
1602 return gimp_utils_generic_available ("gdb", major, minor);
1603 }
1604