No issues found
1 /*
2 * Copyright (C) 2006, Jamie McCracken <jamiemcc@gnome.org>
3 * Copyright (C) 2008, Nokia <ivan.frade@nokia.com>
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 */
20
21 #include "config.h"
22
23 #ifndef _GNU_SOURCE
24 #define _GNU_SOURCE
25 #endif
26
27 #include <string.h>
28 #include <unistd.h>
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <sys/statvfs.h>
32 #include <sys/file.h>
33 #include <fcntl.h>
34 #include <limits.h>
35 #include <errno.h>
36
37 #ifdef __linux__
38 #include <sys/statfs.h>
39 #endif
40
41 #include <glib.h>
42 #include <gio/gio.h>
43
44 #include "tracker-log.h"
45 #include "tracker-os-dependant.h"
46 #include "tracker-file-utils.h"
47 #include "tracker-type-utils.h"
48
49 #define TEXT_SNIFF_SIZE 4096
50
51 static GHashTable *file_locks = NULL;
52
53 #ifndef LOCK_EX
54
55 /* Required on Solaris */
56 #define LOCK_EX 1
57 #define LOCK_SH 2
58 #define LOCK_UN 3
59 #define LOCK_NB 4
60
61 static int flock(int fd, int op)
62 {
63 int rc = 0;
64
65 #if defined(F_SETLK) && defined(F_SETLKW)
66 struct flock fl = {0};
67
68 switch (op & (LOCK_EX|LOCK_SH|LOCK_UN)) {
69 case LOCK_EX:
70 fl.l_type = F_WRLCK;
71 break;
72
73 case LOCK_SH:
74 fl.l_type = F_RDLCK;
75 break;
76
77 case LOCK_UN:
78 fl.l_type = F_UNLCK;
79 break;
80
81 default:
82 errno = EINVAL;
83 return -1;
84 }
85
86 fl.l_whence = SEEK_SET;
87 rc = fcntl (fd, op & LOCK_NB ? F_SETLK : F_SETLKW, &fl);
88
89 if (rc && (errno == EAGAIN))
90 errno = EWOULDBLOCK;
91 #endif /* defined(F_SETLK) && defined(F_SETLKW) */
92
93 return rc;
94 }
95
96 #endif /* LOCK_EX */
97
98 int
99 tracker_file_open_fd (const gchar *path)
100 {
101 int fd;
102
103 g_return_val_if_fail (path != NULL, -1);
104
105 #if defined(__linux__)
106 fd = g_open (path, O_RDONLY | O_NOATIME, 0);
107 if (fd == -1 && errno == EPERM) {
108 fd = g_open (path, O_RDONLY, 0);
109 }
110 #else
111 fd = g_open (path, O_RDONLY, 0);
112 #endif
113
114 return fd;
115 }
116
117 FILE *
118 tracker_file_open (const gchar *path)
119 {
120 FILE *file;
121 int fd;
122
123 g_return_val_if_fail (path != NULL, NULL);
124
125 fd = tracker_file_open_fd (path);
126
127 if (fd == -1) {
128 return NULL;
129 }
130
131 file = fdopen (fd, "r");
132
133 if (!file) {
134 return NULL;
135 }
136
137 return file;
138 }
139
140 void
141 tracker_file_close (FILE *file,
142 gboolean need_again_soon)
143 {
144 g_return_if_fail (file != NULL);
145
146 #ifdef HAVE_POSIX_FADVISE
147 if (!need_again_soon) {
148 posix_fadvise (fileno (file), 0, 0, POSIX_FADV_DONTNEED);
149 }
150 #endif /* HAVE_POSIX_FADVISE */
151
152 fclose (file);
153 }
154
155 goffset
156 tracker_file_get_size (const gchar *path)
157 {
158 GFileInfo *info;
159 GFile *file;
160 GError *error = NULL;
161 goffset size;
162
163 g_return_val_if_fail (path != NULL, 0);
164
165 file = g_file_new_for_path (path);
166 info = g_file_query_info (file,
167 G_FILE_ATTRIBUTE_STANDARD_SIZE,
168 G_FILE_QUERY_INFO_NONE,
169 NULL,
170 &error);
171
172 if (G_UNLIKELY (error)) {
173 gchar *uri;
174
175 uri = g_file_get_uri (file);
176 g_message ("Could not get size for '%s', %s",
177 uri,
178 error->message);
179 g_free (uri);
180 g_error_free (error);
181 size = 0;
182 } else {
183 size = g_file_info_get_size (info);
184 g_object_unref (info);
185 }
186
187 g_object_unref (file);
188
189 return size;
190 }
191
192 static
193 guint64
194 file_get_mtime (GFile *file)
195 {
196 GFileInfo *info;
197 GError *error = NULL;
198 guint64 mtime;
199
200 info = g_file_query_info (file,
201 G_FILE_ATTRIBUTE_TIME_MODIFIED,
202 G_FILE_QUERY_INFO_NONE,
203 NULL,
204 &error);
205
206 if (G_UNLIKELY (error)) {
207 gchar *uri;
208
209 uri = g_file_get_uri (file);
210 g_message ("Could not get mtime for '%s': %s",
211 uri,
212 error->message);
213 g_free (uri);
214 g_error_free (error);
215 mtime = 0;
216 } else {
217 mtime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
218 g_object_unref (info);
219 }
220
221 return mtime;
222 }
223
224 guint64
225 tracker_file_get_mtime (const gchar *path)
226 {
227 GFile *file;
228 guint64 mtime;
229
230 g_return_val_if_fail (path != NULL, 0);
231
232 file = g_file_new_for_path (path);
233
234 mtime = file_get_mtime (file);
235
236 g_object_unref (file);
237
238 return mtime;
239 }
240
241
242 guint64
243 tracker_file_get_mtime_uri (const gchar *uri)
244 {
245 GFile *file;
246 guint64 mtime;
247
248 g_return_val_if_fail (uri != NULL, 0);
249
250 file = g_file_new_for_uri (uri);
251
252 mtime = file_get_mtime (file);
253
254 g_object_unref (file);
255
256 return mtime;
257 }
258
259 gchar *
260 tracker_file_get_mime_type (GFile *file)
261 {
262 GFileInfo *info;
263 GError *error = NULL;
264 gchar *content_type;
265
266 g_return_val_if_fail (G_IS_FILE (file), NULL);
267
268 info = g_file_query_info (file,
269 G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
270 G_FILE_QUERY_INFO_NONE,
271 NULL,
272 &error);
273
274 if (G_UNLIKELY (error)) {
275 gchar *uri;
276
277 uri = g_file_get_uri (file);
278 g_message ("Could not guess mimetype for '%s', %s",
279 uri,
280 error->message);
281 g_free (uri);
282 g_error_free (error);
283 content_type = NULL;
284 } else {
285 content_type = g_strdup (g_file_info_get_content_type (info));
286 g_object_unref (info);
287 }
288
289 return content_type ? content_type : g_strdup ("unknown");
290 }
291
292 #ifdef __linux__
293
294 #ifdef __USE_LARGEFILE64
295 #define __statvfs statfs64
296 #else
297 #define __statvfs statfs
298 #endif
299
300 #else /* __linux__ */
301
302 #if HAVE_STATVFS64
303 #define __statvfs statvfs64
304 #else
305 #define __statvfs statvfs
306 #endif
307
308 #endif /* __linux__ */
309
310 guint64
311 tracker_file_system_get_remaining_space (const gchar *path)
312 {
313 guint64 remaining;
314 struct __statvfs st;
315
316 //LCOV_EXCL_START
317 if (__statvfs (path, &st) == -1) {
318 remaining = 0;
319 g_critical ("Could not statvfs() '%s': %s",
320 path,
321 g_strerror (errno));
322 //LCOV_EXCL_STOP
323 } else {
324 remaining = st.f_bsize * st.f_bavail;
325 }
326
327 return remaining;
328 }
329
330 gdouble
331 tracker_file_system_get_remaining_space_percentage (const gchar *path)
332 {
333 gdouble remaining;
334 struct __statvfs st;
335
336 //LCOV_EXCL_START
337 if (__statvfs (path, &st) == -1) {
338 remaining = 0.0;
339 g_critical ("Could not statvfs() '%s': %s",
340 path,
341 g_strerror (errno));
342 //LCOV_EXCL_STOP
343 } else {
344 remaining = (st.f_bavail * 100.0 / st.f_blocks);
345 }
346
347 return remaining;
348 }
349
350 gboolean
351 tracker_file_system_has_enough_space (const gchar *path,
352 gulong required_bytes,
353 gboolean creating_db)
354 {
355 gchar *str1;
356 gchar *str2;
357 gboolean enough;
358 guint64 remaining;
359
360 g_return_val_if_fail (path != NULL, FALSE);
361
362 remaining = tracker_file_system_get_remaining_space (path);
363 enough = (remaining >= required_bytes);
364
365 if (creating_db) {
366
367 #if GLIB_CHECK_VERSION (2,30,0)
368 str1 = g_format_size (required_bytes);
369 str2 = g_format_size (remaining);
370 #else
371 str1 = g_format_size_for_display (required_bytes);
372 str2 = g_format_size_for_display (remaining);
373 #endif
374
375 if (!enough) {
376 g_critical ("Not enough disk space to create databases, "
377 "%s remaining, %s required as a minimum",
378 str2,
379 str1);
380 } else {
381 g_message ("Checking for adequate disk space to create databases, "
382 "%s remaining, %s required as a minimum",
383 str2,
384 str1);
385 }
386
387 g_free (str2);
388 g_free (str1);
389 }
390
391 return enough;
392 }
393
394 gboolean
395 tracker_path_is_in_path (const gchar *path,
396 const gchar *in_path)
397 {
398 gchar *new_path;
399 gchar *new_in_path;
400 gboolean is_in_path = FALSE;
401
402 g_return_val_if_fail (path != NULL, FALSE);
403 g_return_val_if_fail (in_path != NULL, FALSE);
404
405 if (!g_str_has_suffix (path, G_DIR_SEPARATOR_S)) {
406 new_path = g_strconcat (path, G_DIR_SEPARATOR_S, NULL);
407 } else {
408 new_path = g_strdup (path);
409 }
410
411 if (!g_str_has_suffix (in_path, G_DIR_SEPARATOR_S)) {
412 new_in_path = g_strconcat (in_path, G_DIR_SEPARATOR_S, NULL);
413 } else {
414 new_in_path = g_strdup (in_path);
415 }
416
417 if (g_str_has_prefix (new_path, new_in_path)) {
418 is_in_path = TRUE;
419 }
420
421 g_free (new_in_path);
422 g_free (new_path);
423
424 return is_in_path;
425 }
426
427 GSList *
428 tracker_path_list_filter_duplicates (GSList *roots,
429 const gchar *basename_exception_prefix,
430 gboolean is_recursive)
431 {
432 GSList *l1, *l2;
433 GSList *new_list;
434
435 new_list = tracker_gslist_copy_with_string_data (roots);
436 l1 = new_list;
437
438 while (l1) {
439 const gchar *path;
440 gchar *p;
441 gboolean reset = FALSE;
442
443 path = l1->data;
444
445 l2 = new_list;
446
447 while (l2 && !reset) {
448 const gchar *in_path;
449
450 in_path = l2->data;
451
452 if (path == in_path) {
453 /* Do nothing */
454 l2 = l2->next;
455 continue;
456 }
457
458 if (basename_exception_prefix) {
459 gchar *lbasename;
460 gboolean has_prefix = FALSE;
461
462 lbasename = g_path_get_basename (path);
463 if (!g_str_has_prefix (lbasename, basename_exception_prefix)) {
464 g_free (lbasename);
465
466 lbasename = g_path_get_basename (in_path);
467 if (g_str_has_prefix (lbasename, basename_exception_prefix)) {
468 has_prefix = TRUE;
469 }
470 } else {
471 has_prefix = TRUE;
472 }
473
474 g_free (lbasename);
475
476 /* This is so we can ignore this check
477 * on files which prefix with ".".
478 */
479 if (has_prefix) {
480 l2 = l2->next;
481 continue;
482 }
483 }
484
485 if (is_recursive && tracker_path_is_in_path (path, in_path)) {
486 g_debug ("Removing path:'%s', it is in path:'%s'",
487 path, in_path);
488
489 g_free (l1->data);
490 new_list = g_slist_delete_link (new_list, l1);
491 l1 = new_list;
492
493 reset = TRUE;
494
495 continue;
496 }
497 else if (is_recursive && tracker_path_is_in_path (in_path, path)) {
498 g_debug ("Removing path:'%s', it is in path:'%s'",
499 in_path, path);
500
501 g_free (l2->data);
502 new_list = g_slist_delete_link (new_list, l2);
503 l1 = new_list;
504
505 reset = TRUE;
506
507 continue;
508 }
509
510 l2 = l2->next;
511 }
512
513 if (G_LIKELY (!reset)) {
514 p = strrchr (path, G_DIR_SEPARATOR);
515
516 /* Make sure the path doesn't have the '/' suffix. */
517 if (p && !p[1]) {
518 *p = '\0';
519 }
520
521 l1 = l1->next;
522 }
523 }
524
525 #ifdef TESTING
526 g_debug ("GSList paths were filtered down to:");
527
528 if (TRUE) {
529 GSList *l;
530
531 for (l = new_list; l; l = l->next) {
532 g_debug (" %s", (gchar*) l->data);
533 }
534 }
535 #endif /* TESTING */
536
537 return new_list;
538 }
539
540 gchar *
541 tracker_path_evaluate_name (const gchar *path)
542 {
543 gchar *final_path;
544 gchar **tokens;
545 gchar **token;
546 gchar *start;
547 gchar *end;
548 const gchar *env;
549 gchar *expanded;
550
551 if (!path || path[0] == '\0') {
552 return NULL;
553 }
554
555 /* First check the simple case of using tilder */
556 if (path[0] == '~') {
557 const gchar *home;
558
559 home = g_getenv ("HOME");
560 if (! home) {
561 home = g_get_home_dir ();
562 }
563
564 if (!home || home[0] == '\0') {
565 return NULL;
566 }
567
568 return g_build_path (G_DIR_SEPARATOR_S,
569 home,
570 path + 1,
571 NULL);
572 }
573
574 /* Second try to find any environment variables and expand
575 * them, like $HOME or ${FOO}
576 */
577 tokens = g_strsplit (path, G_DIR_SEPARATOR_S, -1);
578
579 for (token = tokens; *token; token++) {
580 if (**token != '$') {
581 continue;
582 }
583
584 start = *token + 1;
585
586 if (*start == '{') {
587 start++;
588 end = start + (strlen (start)) - 1;
589 *end='\0';
590 }
591
592 env = g_getenv (start);
593 g_free (*token);
594
595 /* Don't do g_strdup (s?s1:s2) as that doesn't work
596 * with certain gcc 2.96 versions.
597 */
598 *token = env ? g_strdup (env) : g_strdup ("");
599 }
600
601 /* Third get the real path removing any "../" and other
602 * symbolic links to other places, returning only the REAL
603 * location.
604 */
605 if (tokens) {
606 expanded = g_strjoinv (G_DIR_SEPARATOR_S, tokens);
607 g_strfreev (tokens);
608 } else {
609 expanded = g_strdup (path);
610 }
611
612 /* Only resolve relative paths if there is a directory
613 * separator in the path, otherwise it is just a name.
614 */
615 if (strchr (expanded, G_DIR_SEPARATOR)) {
616 GFile *file;
617
618 file = g_file_new_for_commandline_arg (expanded);
619 final_path = g_file_get_path (file);
620 g_object_unref (file);
621 g_free (expanded);
622 } else {
623 final_path = expanded;
624 }
625
626 return final_path;
627 }
628
629 static gboolean
630 path_has_write_access (const gchar *path,
631 gboolean *exists)
632 {
633 GFile *file;
634 GFileInfo *info;
635 GError *error = NULL;
636 gboolean writable;
637
638 g_return_val_if_fail (path != NULL, FALSE);
639 g_return_val_if_fail (path[0] != '\0', FALSE);
640
641 file = g_file_new_for_path (path);
642 info = g_file_query_info (file,
643 G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE,
644 0,
645 NULL,
646 &error);
647
648 if (G_UNLIKELY (error)) {
649 if (error->code == G_IO_ERROR_NOT_FOUND) {
650 if (exists) {
651 *exists = FALSE;
652 }
653 } else {
654 gchar *uri;
655
656 uri = g_file_get_uri (file);
657 g_warning ("Could not check if we have write access for "
658 "'%s': %s",
659 uri,
660 error->message);
661 g_free (uri);
662 }
663
664 g_error_free (error);
665
666 writable = FALSE;
667 } else {
668 if (exists) {
669 *exists = TRUE;
670 }
671
672 writable = g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE);
673
674 g_object_unref (info);
675 }
676
677 g_object_unref (file);
678
679 return writable;
680 }
681
682 gboolean
683 tracker_path_has_write_access_or_was_created (const gchar *path)
684 {
685 gboolean writable;
686 gboolean exists = FALSE;
687
688 writable = path_has_write_access (path, &exists);
689 if (exists) {
690 if (writable) {
691 g_message (" Path is OK");
692 return TRUE;
693 }
694
695 g_message (" Path can not be written to");
696 } else {
697 g_message (" Path does not exist, attempting to create...");
698
699 if (g_mkdir_with_parents (path, 0700) == 0) {
700 g_message (" Path was created");
701 return TRUE;
702 }
703
704 g_message (" Path could not be created");
705 }
706
707 return FALSE;
708 }
709
710 gboolean
711 tracker_file_lock (GFile *file)
712 {
713 gint fd, retval;
714 gchar *path;
715
716 g_return_val_if_fail (G_IS_FILE (file), FALSE);
717
718 if (G_UNLIKELY (!file_locks)) {
719 file_locks = g_hash_table_new_full ((GHashFunc) g_file_hash,
720 (GEqualFunc) g_file_equal,
721 (GDestroyNotify) g_object_unref,
722 NULL);
723 }
724
725 /* Don't try to lock twice */
726 if (g_hash_table_lookup (file_locks, file) != NULL) {
727 return TRUE;
728 }
729
730 if (!g_file_is_native (file)) {
731 return FALSE;
732 }
733
734 path = g_file_get_path (file);
735
736 if (!path) {
737 return FALSE;
738 }
739
740 fd = open (path, O_RDONLY);
741
742 if (fd < 0) {
743 //LCOV_EXCL_START
744 gchar *uri;
745
746 uri = g_file_get_uri (file);
747 g_warning ("Could not open '%s'", uri);
748 g_free (uri);
749 g_free (path);
750
751 return FALSE;
752 //LCOV_EXCL_STOP
753 }
754
755 retval = flock (fd, LOCK_EX);
756
757 if (retval == 0) {
758 g_hash_table_insert (file_locks,
759 g_object_ref (file),
760 GINT_TO_POINTER (fd));
761 } else {
762 //LCOV_EXCL_START
763 gchar *uri;
764
765 uri = g_file_get_uri (file);
766 g_warning ("Could not lock file '%s'", uri);
767 g_free (uri);
768 close (fd);
769 //LCOV_EXCL_STOP
770 }
771
772 g_free (path);
773
774 return (retval == 0);
775 }
776
777 gboolean
778 tracker_file_unlock (GFile *file)
779 {
780 gint retval, fd;
781
782 g_return_val_if_fail (G_IS_FILE (file), TRUE);
783
784 if (!file_locks) {
785 return TRUE;
786 }
787
788 fd = GPOINTER_TO_INT (g_hash_table_lookup (file_locks, file));
789
790 if (fd == 0) {
791 /* File wasn't actually locked */
792 return TRUE;
793 }
794
795 retval = flock (fd, LOCK_UN);
796
797 if (retval < 0) {
798 //LCOV_EXCL_START
799 gchar *uri;
800
801 uri = g_file_get_uri (file);
802 g_warning ("Could not unlock file '%s'", uri);
803 g_free (uri);
804
805 return FALSE;
806 //LCOV_EXCL_STOP
807 }
808
809 g_hash_table_remove (file_locks, file);
810 close (fd);
811
812 return TRUE;
813 }
814
815 gboolean
816 tracker_file_is_locked (GFile *file)
817 {
818 GFileInfo *file_info;
819 gboolean retval = FALSE;
820 gchar *path;
821 gint fd;
822
823 g_return_val_if_fail (G_IS_FILE (file), FALSE);
824
825 if (!g_file_is_native (file)) {
826 return FALSE;
827 }
828
829 /* Handle regular files; skip pipes and alike */
830 file_info = g_file_query_info (file,
831 G_FILE_ATTRIBUTE_STANDARD_TYPE,
832 G_FILE_QUERY_INFO_NONE,
833 NULL,
834 NULL);
835
836 if (!file_info) {
837 return FALSE;
838 }
839
840 if (g_file_info_get_file_type (file_info) != G_FILE_TYPE_REGULAR) {
841 g_object_unref (file_info);
842 return FALSE;
843 }
844
845 g_object_unref (file_info);
846
847 path = g_file_get_path (file);
848
849 if (!path) {
850 return FALSE;
851 }
852
853 fd = open (path, O_RDONLY);
854
855 if (fd < 0) {
856 gchar *uri;
857
858 uri = g_file_get_uri (file);
859 g_warning ("Could not open '%s'", uri);
860 g_free (uri);
861 g_free (path);
862
863 return FALSE;
864 }
865
866 /* Check for locks */
867 retval = flock (fd, LOCK_SH | LOCK_NB);
868
869 if (retval < 0) {
870 if (errno == EWOULDBLOCK) {
871 retval = TRUE;
872 }
873 } else {
874 /* Oops, call was successful, unlock again the file */
875 flock (fd, LOCK_UN);
876 }
877
878 close (fd);
879 g_free (path);
880
881 return retval;
882 }
883
884 gboolean
885 tracker_file_is_hidden (GFile *file)
886 {
887 GFileInfo *file_info;
888 gboolean is_hidden = FALSE;
889
890 file_info = g_file_query_info (file,
891 G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN,
892 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
893 NULL, NULL);
894 if (file_info) {
895 /* Check if GIO says the file is hidden */
896 is_hidden = g_file_info_get_is_hidden (file_info);
897 g_object_unref (file_info);
898 }
899
900 return is_hidden;
901 }
902
903 gint
904 tracker_file_cmp (GFile *file_a,
905 GFile *file_b)
906 {
907 /* Returns 0 if files are equal.
908 * Useful to be used in g_list_find_custom() or g_queue_find_custom() */
909 return !g_file_equal (file_a, file_b);
910 }
911
912 /**
913 * tracker_filename_casecmp_without_extension:
914 * @a: a string containing a file name
915 * @b: filename to be compared with @a
916 *
917 * This function performs a case-insensitive comparison of @a and @b.
918 * Additionally, text beyond the last '.' in a string is not considered
919 * part of the match, so for example given the inputs "file.mp3" and
920 * "file.wav" this function will return %TRUE.
921 *
922 * Internally, the g_ascii_tolower() function is used - this means that
923 * @a and @b must be in an encoding in which ASCII characters always
924 * represent themselves, such as UTF-8 or the ISO-8859-* charsets.
925 *
926 * Returns: %TRUE if the two file names match.
927 **/
928 gboolean
929 tracker_filename_casecmp_without_extension (const gchar *a,
930 const gchar *b)
931 {
932 gchar *pa;
933 gchar *pb;
934 gint len_a;
935 gint len_b;
936
937 g_return_val_if_fail (a != NULL, FALSE);
938 g_return_val_if_fail (b != NULL, FALSE);
939
940 pa = strrchr (a, '.');
941 pb = strrchr (b, '.');
942
943 /* Did we find a "." */
944 if (pa) {
945 len_a = pa - a;
946 } else {
947 len_a = -1;
948 }
949
950 if (pb) {
951 len_b = pb - b;
952 } else {
953 len_b = -1;
954 }
955
956 /* If one has a "." and the other doesn't, we do length
957 * comparison with strlen() which is less optimal but this is
958 * not a case we consider common operation.
959 */
960 if (len_a == -1 && len_b > -1) {
961 len_a = strlen (a);
962 } else if (len_b == -1 && len_a > -1) {
963 len_b = strlen (b);
964 }
965
966 /* If we have length for both and it's different then these
967 * strings are not the same. If we have no length for the
968 * strings then it's a simple -1 != -1 comparison.
969 */
970 if (len_a != len_b) {
971 return FALSE;
972 }
973
974 /* Now we know we either have the same length string or no
975 * extension in a and b, meaning it's a strcmp() of the
976 * string only. We test only len_a or len_b here for that:
977 */
978 if (G_UNLIKELY (len_a == -1)) {
979 return g_ascii_strcasecmp (a, b) == 0;
980 }
981
982 return g_ascii_strncasecmp (a, b, len_a) == 0;
983 }