/* $Id: thunar-vfs-mime-cache.c 23535 2006-10-28 09:26:54Z benny $ */
/*-
* Copyright (c) 2005-2006 Benedikt Meurer <benny@xfce.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
* Based on code initially written by Matthias Clasen <mclasen@redhat.com>
* for the xdgmime library.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_SYS_MMAN_H
#include <sys/mman.h>
#endif
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#ifdef HAVE_FNMATCH_H
#include <fnmatch.h>
#endif
#ifdef HAVE_MEMORY_H
#include <memory.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <thunar-vfs/thunar-vfs-mime-cache.h>
#include <thunar-vfs/thunar-vfs-private.h>
#include <thunar-vfs/thunar-vfs-alias.h>
/* use g_open() on win32 */
#if GLIB_CHECK_VERSION(2,6,0) && defined(G_OS_WIN32)
#include <glib/gstdio.h>
#else
#define g_open(path, flags, mode) (open ((path), (flags), (mode)))
#endif
#define CACHE_MAJOR_VERSION (1)
#define CACHE_MINOR_VERSION (0)
#define CACHE_READ16(cache, offset) (GUINT16_FROM_BE (*((guint16 *) ((cache) + (offset)))))
#define CACHE_READ32(cache, offset) (GUINT32_FROM_BE (*((guint32 *) ((cache) + (offset)))))
static void thunar_vfs_mime_cache_class_init (ThunarVfsMimeCacheClass *klass);
static void thunar_vfs_mime_cache_finalize (GObject *object);
static const gchar *thunar_vfs_mime_cache_lookup_data (ThunarVfsMimeProvider *provider,
gconstpointer data,
gsize length,
gint *priority);
static const gchar *thunar_vfs_mime_cache_lookup_literal (ThunarVfsMimeProvider *provider,
const gchar *filename);
static const gchar *thunar_vfs_mime_cache_lookup_suffix (ThunarVfsMimeProvider *provider,
const gchar *suffix,
gboolean ignore_case);
static const gchar *thunar_vfs_mime_cache_lookup_glob (ThunarVfsMimeProvider *provider,
const gchar *filename);
static const gchar *thunar_vfs_mime_cache_lookup_alias (ThunarVfsMimeProvider *provider,
const gchar *alias);
static guint thunar_vfs_mime_cache_lookup_parents (ThunarVfsMimeProvider *provider,
const gchar *mime_type,
gchar **parents,
guint max_parents);
static GList *thunar_vfs_mime_cache_get_stop_characters (ThunarVfsMimeProvider *provider);
static gsize thunar_vfs_mime_cache_get_max_buffer_extents (ThunarVfsMimeProvider *provider);
struct _ThunarVfsMimeCacheClass
{
ThunarVfsMimeProviderClass __parent__;
};
struct _ThunarVfsMimeCache
{
ThunarVfsMimeProvider __parent__;
gchar *buffer;
gsize bufsize;
};
static GObjectClass *thunar_vfs_mime_cache_parent_class;
GType
thunar_vfs_mime_cache_get_type (void)
{
static GType type = G_TYPE_INVALID;
if (G_UNLIKELY (type == G_TYPE_INVALID))
{
type = _thunar_vfs_g_type_register_simple (THUNAR_VFS_TYPE_MIME_PROVIDER,
"ThunarVfsMimeCache",
sizeof (ThunarVfsMimeCacheClass),
thunar_vfs_mime_cache_class_init,
sizeof (ThunarVfsMimeCache),
NULL,
0);
}
return type;
}
static void
thunar_vfs_mime_cache_class_init (ThunarVfsMimeCacheClass *klass)
{
ThunarVfsMimeProviderClass *thunarvfs_mime_provider_class;
GObjectClass *gobject_class;
thunar_vfs_mime_cache_parent_class = g_type_class_peek_parent (klass);
gobject_class = G_OBJECT_CLASS (klass);
gobject_class->finalize = thunar_vfs_mime_cache_finalize;
thunarvfs_mime_provider_class = THUNAR_VFS_MIME_PROVIDER_CLASS (klass);
thunarvfs_mime_provider_class->lookup_data = thunar_vfs_mime_cache_lookup_data;
thunarvfs_mime_provider_class->lookup_literal = thunar_vfs_mime_cache_lookup_literal;
thunarvfs_mime_provider_class->lookup_suffix = thunar_vfs_mime_cache_lookup_suffix;
thunarvfs_mime_provider_class->lookup_glob = thunar_vfs_mime_cache_lookup_glob;
thunarvfs_mime_provider_class->lookup_alias = thunar_vfs_mime_cache_lookup_alias;
thunarvfs_mime_provider_class->lookup_parents = thunar_vfs_mime_cache_lookup_parents;
thunarvfs_mime_provider_class->get_stop_characters = thunar_vfs_mime_cache_get_stop_characters;
thunarvfs_mime_provider_class->get_max_buffer_extents = thunar_vfs_mime_cache_get_max_buffer_extents;
}
static void
thunar_vfs_mime_cache_finalize (GObject *object)
{
#ifdef HAVE_MMAP
ThunarVfsMimeCache *cache = THUNAR_VFS_MIME_CACHE (object);
if (G_LIKELY (cache->buffer != NULL))
munmap (cache->buffer, cache->bufsize);
#endif
(*G_OBJECT_CLASS (thunar_vfs_mime_cache_parent_class)->finalize) (object);
}
static gboolean
cache_magic_matchlet_compare_to_data (const gchar *buffer,
guint32 offset,
gconstpointer data,
gsize length)
{
gboolean valid_matchlet;
guint32 range_start = CACHE_READ32 (buffer, offset);
guint32 range_length = CACHE_READ32 (buffer, offset + 4);
guint32 data_length = CACHE_READ32 (buffer, offset + 12);
guint32 data_offset = CACHE_READ32 (buffer, offset + 16);
guint32 mask_offset = CACHE_READ32 (buffer, offset + 20);
guint32 i, j;
for (i = range_start; i <= range_start + range_length; i++)
{
valid_matchlet = TRUE;
if (i + data_length > length)
return FALSE;
if (mask_offset)
{
for (j = 0; j < data_length; j++)
if ((buffer[data_offset + j] & buffer[mask_offset + j]) != ((((gchar *) data)[j + i]) & buffer[mask_offset + j]))
{
valid_matchlet = FALSE;
break;
}
}
else
{
for (j = 0; j < data_length; j++)
if (buffer[data_offset + j] != ((gchar *) data)[j + i])
{
valid_matchlet = FALSE;
break;
}
}
if (valid_matchlet)
return TRUE;
}
return FALSE;
}
static gboolean
cache_magic_matchlet_compare (const gchar *buffer,
guint32 offset,
gconstpointer data,
gsize length)
{
guint32 n_children = CACHE_READ32 (buffer, offset + 24);
guint32 child_offset = CACHE_READ32 (buffer, offset + 28);
guint32 i;
if (cache_magic_matchlet_compare_to_data (buffer, offset, data, length))
{
if (n_children == 0)
return TRUE;
for (i = 0; i < n_children; i++)
if (cache_magic_matchlet_compare (buffer, child_offset + 32 * i, data, length))
return TRUE;
}
return FALSE;
}
static const gchar*
thunar_vfs_mime_cache_lookup_data (ThunarVfsMimeProvider *provider,
gconstpointer data,
gsize length,
gint *priority)
{
const gchar *buffer = THUNAR_VFS_MIME_CACHE (provider)->buffer;
guint32 matchlet_offset;
guint32 offset;
guint32 n, m;
offset = CACHE_READ32 (buffer, 24);
n = CACHE_READ32 (buffer, offset);
offset = CACHE_READ32 (buffer, offset + 8);
for (; n-- > 0; offset += 16)
{
matchlet_offset = CACHE_READ32 (buffer, offset + 12);
for (m = CACHE_READ32 (buffer, offset + 8); m-- > 0; matchlet_offset += 32)
if (cache_magic_matchlet_compare (buffer, matchlet_offset, data, length))
{
if (G_LIKELY (priority != NULL))
*priority = (gint) CACHE_READ32 (buffer, offset);
return buffer + CACHE_READ32 (buffer, offset + 4);
}
}
return NULL;
}
static const gchar*
thunar_vfs_mime_cache_lookup_literal (ThunarVfsMimeProvider *provider,
const gchar *filename)
{
const gchar *buffer = THUNAR_VFS_MIME_CACHE (provider)->buffer;
guint32 list_offset = CACHE_READ32 (buffer, 12);
guint32 n_entries = CACHE_READ32 (buffer, list_offset);
guint32 offset;
gint min;
gint mid;
gint max;
gint cmp;
for (min = 0, max = (gint) n_entries - 1; max >= min; )
{
mid = (min + max) / 2;
offset = CACHE_READ32 (buffer, list_offset + 4 + 8 * mid);
cmp = strcmp (buffer + offset, filename);
if (cmp < 0)
min = mid + 1;
else if (cmp > 0)
max = mid - 1;
else
return buffer + CACHE_READ32 (buffer, list_offset + 4 + 8 * mid + 4);
}
return NULL;
}
static const gchar*
cache_node_lookup_suffix (const gchar *buffer,
guint32 n_entries,
guint32 offset,
const gchar *suffix,
gboolean ignore_case)
{
const gchar *type;
gunichar character;
gunichar match_char;
gint min, max, mid;
next:
character = g_utf8_get_char (suffix);
if (ignore_case)
character = g_unichar_tolower (character);
for (min = 0, max = (gint) n_entries - 1; max >= min; )
{
mid = (min + max) / 2;
match_char = (gunichar) CACHE_READ32 (buffer, offset + 16 * mid);
if (match_char < character)
min = mid + 1;
else if (match_char > character)
max = mid - 1;
else
{
suffix = g_utf8_next_char (suffix);
if (*suffix == '\0')
{
/* need to verify the type here, as the stopchars may be
* misleading, which in turn may lead to the problem of
* returning '' here, which in turn will cause the mime
* database to return application/octet-stream for those
* files!
*/
type = buffer + CACHE_READ32 (buffer, offset + 16 * mid + 4);
if (G_LIKELY (*type != '\0'))
return type;
}
else
{
/* We emulate a recursive call to cache_node_lookup_suffix()
* here. This optimization works because the algorithm is
* tail-recursive. The goto is not necessarily nice, but it
* works for our purpose and doesn't decrease readability.
* If we'd use a recursive call here, the code would look
* like this:
*
* return cache_node_lookup_suffix (buffer, CACHE_READ32 (buffer, offset + 16 * mid + 8),
* CACHE_READ32 (buffer, offset + 16 * mid + 12),
* suffix, ignore_case);
*/
n_entries = CACHE_READ32 (buffer, offset + 16 * mid + 8);
offset = CACHE_READ32 (buffer, offset + 16 * mid + 12);
goto next;
}
}
}
return NULL;
}
static const gchar*
thunar_vfs_mime_cache_lookup_suffix (ThunarVfsMimeProvider *provider,
const gchar *suffix,
gboolean ignore_case)
{
const gchar *buffer = THUNAR_VFS_MIME_CACHE (provider)->buffer;
guint32 offset = CACHE_READ32 (buffer, 16);
g_return_val_if_fail (g_utf8_validate (suffix, -1, NULL), NULL);
return cache_node_lookup_suffix (buffer, CACHE_READ32 (buffer, offset),
CACHE_READ32 (buffer, offset + 4),
suffix, ignore_case);
}
static const gchar*
thunar_vfs_mime_cache_lookup_glob (ThunarVfsMimeProvider *provider,
const gchar *filename)
{
const gchar *buffer = THUNAR_VFS_MIME_CACHE (provider)->buffer;
guint32 list_offset = CACHE_READ32 (buffer, 20);
guint32 n_entries = CACHE_READ32 (buffer, list_offset);
guint32 n;
for (n = 0; n < n_entries; ++n)
if (fnmatch (buffer + CACHE_READ32 (buffer, list_offset + 4 + 8 * n), filename, 0) == 0)
return buffer + CACHE_READ32 (buffer, list_offset + 4 + 8 * n + 4);
return NULL;
}
static const gchar*
thunar_vfs_mime_cache_lookup_alias (ThunarVfsMimeProvider *provider,
const gchar *alias)
{
const gchar *buffer = THUNAR_VFS_MIME_CACHE (provider)->buffer;
guint32 list_offset = CACHE_READ32 (buffer, 4);
gint max;
gint mid;
gint min;
gint n;
for (max = ((gint) CACHE_READ32 (buffer, list_offset)) - 1, min = 0; max >= min; )
{
mid = (min + max) / 2;
n = strcmp (buffer + CACHE_READ32 (buffer, list_offset + 4 + 8 * mid), alias);
if (n < 0)
min = mid + 1;
else if (n > 0)
max = mid - 1;
else
return buffer + CACHE_READ32 (buffer, list_offset + 4 + 8 * mid + 4);
}
return NULL;
}
static guint
thunar_vfs_mime_cache_lookup_parents (ThunarVfsMimeProvider *provider,
const gchar *mime_type,
gchar **parents,
guint max_parents)
{
const gchar *buffer = THUNAR_VFS_MIME_CACHE (provider)->buffer;
guint32 parents_offset;
guint32 list_offset = CACHE_READ32 (buffer, 8);
guint32 n_entries = CACHE_READ32 (buffer, list_offset);
guint32 n_parents;
guint32 i, j, l;
for (i = j = 0; i < n_entries && j < max_parents; ++i)
if (strcmp (buffer + CACHE_READ32 (buffer, list_offset + 4 + 8 * i), mime_type) == 0)
{
parents_offset = CACHE_READ32 (buffer, list_offset + 4 + 8 * i + 4);
n_parents = CACHE_READ32 (buffer, parents_offset);
for (l = 0; l < n_parents && j < max_parents; ++l, ++j)
parents[j] = (gchar *) (buffer + CACHE_READ32 (buffer, parents_offset + 4 + 4 * l));
}
return j;
}
static GList*
thunar_vfs_mime_cache_get_stop_characters (ThunarVfsMimeProvider *provider)
{
const gchar *buffer = THUNAR_VFS_MIME_CACHE (provider)->buffer;
gunichar character;
guint32 offset = CACHE_READ32 (buffer, 16);
guint32 n = CACHE_READ32 (buffer, offset);
GList *list = NULL;
for (offset = CACHE_READ32 (buffer, offset + 4); n-- > 0; offset += 16)
{
/* query the suffix start character */
character = (gunichar) CACHE_READ32 (buffer, offset);
if (G_UNLIKELY (character >= 128))
continue;
/* prepend the character to the list (if not already present) */
if (G_UNLIKELY (g_list_find (list, GUINT_TO_POINTER (character)) == NULL))
list = g_list_prepend (list, GUINT_TO_POINTER (character));
}
return list;
}
static gsize
thunar_vfs_mime_cache_get_max_buffer_extents (ThunarVfsMimeProvider *provider)
{
const gchar *buffer = THUNAR_VFS_MIME_CACHE (provider)->buffer;
/* get the MAX_EXTENTS entry from the MagicList */
return CACHE_READ32 (buffer, CACHE_READ32 (buffer, 24) + 4);
}
/**
* thunar_vfs_mime_cache_new:
* @directory : the mime base directory.
*
* Creates a new #ThunarVfsMimeCache for @directory. Returns
* %NULL if for some reason, @directory could not be opened
* as a #ThunarVfsMimeCache.
*
* The caller is responsible to call g_object_unref()
* on the returned instance.
*
* Return value: a #ThunarVfsMimeCache for @directory or %NULL.
**/
ThunarVfsMimeProvider*
thunar_vfs_mime_cache_new (const gchar *directory)
{
ThunarVfsMimeCache *cache = NULL;
#ifdef HAVE_MMAP
struct stat stat;
gchar *buffer;
gchar *path;
gint fd;
/* try to open the mime.cache file */
path = g_build_filename (directory, "mime.cache", NULL);
fd = g_open (path, O_RDONLY, 0);
g_free (path);
if (G_UNLIKELY (fd < 0))
return NULL;
/* stat the file to get proper size info */
if (fstat (fd, &stat) < 0 || stat.st_size < 4)
goto done;
/* try to map the file into memory */
buffer = (gchar *) mmap (NULL, stat.st_size, PROT_READ, MAP_SHARED, fd, 0);
if (G_UNLIKELY (buffer == MAP_FAILED))
goto done;
/* check that we actually support the file version */
if (CACHE_READ16 (buffer, 0) != CACHE_MAJOR_VERSION || CACHE_READ16 (buffer, 2) != CACHE_MINOR_VERSION)
{
munmap (buffer, stat.st_size);
goto done;
}
/* allocate a new cache provider */
cache = g_object_new (THUNAR_VFS_TYPE_MIME_CACHE, NULL);
cache->buffer = buffer;
cache->bufsize = stat.st_size;
/* tell the system that we'll use this buffer quite often */
#ifdef HAVE_POSIX_MADVISE
posix_madvise (buffer, stat.st_size, POSIX_MADV_WILLNEED);
#endif
/* cleanup */
done:
if (G_LIKELY (fd >= 0))
close (fd);
#endif /* !HAVE_MMAP */
return (ThunarVfsMimeProvider *) cache;
}
#define __THUNAR_VFS_MIME_CACHE_C__
#include <thunar-vfs/thunar-vfs-aliasdef.c>