You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
arts/flow/gsl/gslmagic.c

712 lines
17 KiB

/* GSL - Generic Sound Layer
* Copyright (C) 2000-2002 Tim Janik
*
* 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 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., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "gslmagic.h"
#include "gslcommon.h"
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
/* --- defines --- */
#define BFILE_BSIZE (768) /* amount of buffering */
#define MAX_MAGIC_STRING (256) /* must be < BFILE_BSIZE / 2 */
/* --- typedefs & structures --- */
typedef struct _Magic Magic;
typedef struct _BFile BFile;
struct _BFile
{
gint fd;
guint file_size;
guint8 header[BFILE_BSIZE];
guint offset;
guint8 buffer[BFILE_BSIZE];
};
/* --- prototypes --- */
static Magic* magic_create (gchar *magic_string,
const gchar *original);
static gboolean magic_match_file (BFile *bfile,
Magic *magics);
static gboolean bfile_open (BFile *bfile,
const gchar *file_name);
static gboolean bfile_read (BFile *bfile,
guint offset,
void *mem,
guint n_bytes);
static void bfile_close (BFile *bfile);
static guint bfile_get_size (BFile *bfile);
/* --- functions --- */
GslMagic*
gsl_magic_list_match_file (GslRing *magic_list,
const gchar *file_name)
{
GslMagic *rmagic = NULL;
BFile bfile = { -1, };
g_return_val_if_fail (file_name != NULL, NULL);
if (bfile_open (&bfile, file_name))
{
gchar *extension = strrchr (file_name, '.');
gint rpriority = G_MAXINT;
GslRing *node;
/* we do a quick scan by extension first */
if (!rmagic && extension)
for (node = magic_list; node; node = gsl_ring_walk (magic_list, node))
{
GslMagic *magic = node->data;
if (!magic->extension
|| strcmp (magic->extension, extension) != 0
|| rpriority < magic->priority
|| (rmagic && rpriority == magic->priority))
continue;
if (magic_match_file (&bfile, magic->match_list))
{
rmagic = magic;
rpriority = rmagic->priority;
}
}
/* then we do a normal walk but skip extension matches */
if (!rmagic && extension)
for (node = magic_list; node; node = gsl_ring_walk (magic_list, node))
{
GslMagic *magic = node->data;
if ((magic->extension && strcmp (magic->extension, extension) == 0)
|| rpriority < magic->priority
|| (rmagic && rpriority == magic->priority))
continue;
if (magic_match_file (&bfile, magic->match_list))
{
rmagic = magic;
rpriority = rmagic->priority;
}
}
/* for no extension, we do a full walk */
if (!rmagic && !extension)
for (node = magic_list; node; node = gsl_ring_walk (magic_list, node))
{
GslMagic *magic = node->data;
if (rpriority < magic->priority ||
(rmagic && rpriority == magic->priority))
continue;
if (magic_match_file (&bfile, magic->match_list))
{
rmagic = magic;
rpriority = rmagic->priority;
}
}
bfile_close (&bfile);
}
return rmagic;
}
GslMagic*
gsl_magic_create (gpointer data,
gint priority,
const gchar *extension,
const gchar *magic_spec)
{
GslMagic *magic;
Magic *match_list;
gchar *magic_string;
g_return_val_if_fail (magic_spec != NULL, NULL);
magic_string = g_strdup (magic_spec);
match_list = magic_create (magic_string, magic_spec);
g_free (magic_string);
if (!match_list)
return NULL;
magic = g_new (GslMagic, 1);
magic->data = data;
magic->extension = g_strdup (extension);
magic->priority = priority;
magic->match_list = match_list;
return magic;
}
/* --- Magic creation/checking --- */
typedef enum
{
MAGIC_CHECK_ANY,
MAGIC_CHECK_INT_EQUAL,
MAGIC_CHECK_INT_GREATER,
MAGIC_CHECK_INT_SMALLER,
MAGIC_CHECK_UINT_GREATER,
MAGIC_CHECK_UINT_SMALLER,
MAGIC_CHECK_UINT_ZEROS,
MAGIC_CHECK_UINT_ONES,
MAGIC_CHECK_STRING_EQUAL,
MAGIC_CHECK_STRING_GREATER,
MAGIC_CHECK_STRING_SMALLER
} MagicCheckType;
typedef union
{
gint32 v_int32;
guint32 v_uint32;
gchar *v_string;
} MagicData;
struct _Magic
{
Magic *next;
gulong offset;
guint data_size;
MagicCheckType magic_check;
guint32 data_tqmask;
MagicData value;
guint read_string : 1;
guint read_size : 1;
guint need_swap : 1;
guint cmp_unsigned : 1;
};
static const gchar *magic_field_delims = " \t,";
static const gchar *magic_string_delims = " \t\n\r";
static gboolean
magic_parse_test (Magic *magic,
const gchar *string)
{
if (!magic->read_string)
{
gchar *f = NULL;
if (string[0] == '<' || string[0] == '>')
{
if (magic->cmp_unsigned)
magic->magic_check = string[0] == '<' ? MAGIC_CHECK_UINT_SMALLER : MAGIC_CHECK_UINT_GREATER;
else
magic->magic_check = string[0] == '<' ? MAGIC_CHECK_INT_SMALLER : MAGIC_CHECK_INT_GREATER;
string += 1;
}
else if (string[0] == '^' || string[0] == '&')
{
magic->magic_check = string[0] == '&' ? MAGIC_CHECK_UINT_ONES : MAGIC_CHECK_UINT_ZEROS;
string += 1;
}
else if (string[0] == 'x')
{
magic->magic_check = MAGIC_CHECK_ANY;
string += 1;
}
else
{
string += string[0] == '=';
magic->magic_check = MAGIC_CHECK_INT_EQUAL;
}
if (string[0] == '0')
magic->value.v_int32 = strtol (string, &f, string[1] == 'x' ? 16 : 8);
else
magic->value.v_int32 = strtol (string, &f, 10);
return *string == 0 || !f || *f == 0;
}
else
{
gchar tmp_string[MAX_MAGIC_STRING + 1];
guint n = 0;
if (string[0] == '<' || string[0] == '>')
{
magic->magic_check = string[0] == '<' ? MAGIC_CHECK_STRING_SMALLER : MAGIC_CHECK_STRING_GREATER;
string += 1;
}
else
{
string += string[0] == '=';
magic->magic_check = MAGIC_CHECK_STRING_EQUAL;
}
while (n < MAX_MAGIC_STRING && string[n] && !strchr (magic_string_delims, string[n]))
{
if (string[n] != '\\')
tmp_string[n] = string[n];
else switch ((++string)[n])
{
case '\\': tmp_string[n] = '\\'; break;
case 't': tmp_string[n] = '\t'; break;
case 'n': tmp_string[n] = '\n'; break;
case 'r': tmp_string[n] = '\r'; break;
case 'b': tmp_string[n] = '\b'; break;
case 'f': tmp_string[n] = '\f'; break;
case 's': tmp_string[n] = ' '; break;
case 'e': tmp_string[n] = 27; break;
default:
if (string[n] >= '0' && string[n] <= '7')
{
tmp_string[n] = string[n] - '0';
if (string[n + 1] >= '0' && string[n + 1] <= '7')
{
string += 1;
tmp_string[n] *= 8;
tmp_string[n] += string[n] - '0';
if (string[n + 1] >= '0' && string[n + 1] <= '7')
{
string += 1;
tmp_string[n] *= 8;
tmp_string[n] += string[n] - '0';
}
}
}
else
tmp_string[n] = string[n];
break;
}
n++;
}
tmp_string[n] = 0;
magic->data_size = n;
magic->value.v_string = g_strdup (tmp_string);
return TRUE;
}
}
static gboolean
magic_parse_type (Magic *magic,
const gchar *string)
{
gchar *f = NULL;
if (string[0] == 'u')
{
string += 1;
magic->cmp_unsigned = TRUE;
}
if (strncmp (string, "byte", 4) == 0)
{
string += 4;
magic->data_size = 1;
}
else if (strncmp (string, "short", 5) == 0)
{
string += 5;
magic->data_size = 2;
}
else if (strncmp (string, "leshort", 7) == 0)
{
string += 7;
magic->data_size = 2;
magic->need_swap = G_BYTE_ORDER != G_LITTLE_ENDIAN;
}
else if (strncmp (string, "beshort", 7) == 0)
{
string += 7;
magic->data_size = 2;
magic->need_swap = G_BYTE_ORDER != G_BIG_ENDIAN;
}
else if (strncmp (string, "long", 4) == 0)
{
string += 4;
magic->data_size = 4;
}
else if (strncmp (string, "lelong", 6) == 0)
{
string += 6;
magic->data_size = 4;
magic->need_swap = G_BYTE_ORDER != G_LITTLE_ENDIAN;
}
else if (strncmp (string, "belong", 6) == 0)
{
string += 6;
magic->data_size = 4;
magic->need_swap = G_BYTE_ORDER != G_BIG_ENDIAN;
}
#if 0
else if (strncmp (string, "size", 4) == 0)
{
string += 4;
magic->data_size = 4;
magic->read_size = TRUE;
magic->cmp_unsigned = TRUE;
}
#endif
else if (strncmp (string, "string", 6) == 0)
{
string += 6;
magic->data_size = 0;
magic->read_string = TRUE;
}
if (string[0] == '&')
{
string += 1;
if (string[0] == '0')
magic->data_tqmask = strtol (string, &f, string[1] == 'x' ? 16 : 8);
else
magic->data_tqmask = strtol (string, &f, 10);
if (f && *f != 0)
return FALSE;
while (*string)
string++;
}
else
magic->data_tqmask = 0xffffffff;
return *string == 0;
}
static gboolean
magic_parse_offset (Magic *magic,
gchar *string)
{
gchar *f = NULL;
if (string[0] == '0')
magic->offset = strtol (string, &f, string[1] == 'x' ? 16 : 8);
else
magic->offset = strtol (string, &f, 10);
return !f || *f == 0;
}
static Magic*
magic_create (gchar *magic_string,
const gchar *original)
#define SKIP_CLEAN(s) { while (*s && !strchr(magic_field_delims, *s)) s++; \
while (*s && strchr(magic_field_delims, *s)) \
*(s++)=0;}
{
Magic *magics = NULL;
gchar *p = magic_string;
while (p && *p)
{
gchar *next_line;
if (*p == '#' || *p == '\n')
{
next_line = strchr (p, '\n');
if (next_line)
next_line++;
}
else
{
Magic *tmp = magics;
magics = g_new0 (Magic, 1);
magics->next = tmp;
magic_string = p;
SKIP_CLEAN (p);
if (!magic_parse_offset (magics, magic_string))
{
g_warning ("unable to parse magic offset \"%s\" from \"%s\"", magic_string, original);
return NULL;
}
magic_string = p;
SKIP_CLEAN (p);
if (!magic_parse_type (magics, magic_string))
{
g_warning ("unable to parse magic type \"%s\" from \"%s\"", magic_string, original);
return NULL;
}
magic_string = p;
next_line = strchr (magic_string, '\n');
if (next_line)
*(next_line++) = 0;
if (!magics->read_string)
SKIP_CLEAN (p);
if (!magic_parse_test (magics, magic_string))
{
g_warning ("unable to parse magic test \"%s\" from \"%s\"", magic_string, original);
return NULL;
}
}
p = next_line;
}
return magics;
}
static gboolean
magic_check_data (Magic *magic,
MagicData *data)
{
gint cmp = 0;
switch (magic->magic_check)
{
guint l;
case MAGIC_CHECK_ANY:
cmp = 1;
break;
case MAGIC_CHECK_INT_EQUAL:
data->v_int32 &= magic->data_tqmask;
cmp = data->v_int32 == magic->value.v_int32;
break;
case MAGIC_CHECK_INT_GREATER:
data->v_int32 &= magic->data_tqmask;
cmp = data->v_int32 > magic->value.v_int32;
break;
case MAGIC_CHECK_INT_SMALLER:
data->v_int32 &= magic->data_tqmask;
cmp = data->v_int32 < magic->value.v_int32;
break;
case MAGIC_CHECK_UINT_GREATER:
data->v_uint32 &= magic->data_tqmask;
cmp = data->v_uint32 > magic->value.v_uint32;
break;
case MAGIC_CHECK_UINT_SMALLER:
data->v_uint32 &= magic->data_tqmask;
cmp = data->v_uint32 < magic->value.v_uint32;
break;
case MAGIC_CHECK_UINT_ZEROS:
data->v_uint32 &= magic->data_tqmask;
cmp = (data->v_int32 & magic->value.v_int32) == 0;
break;
case MAGIC_CHECK_UINT_ONES:
data->v_uint32 &= magic->data_tqmask;
cmp = (data->v_int32 & magic->value.v_int32) == magic->value.v_int32;
break;
case MAGIC_CHECK_STRING_EQUAL:
l = magic->data_size < 1 ? strlen (data->v_string) : magic->data_size;
cmp = strncmp (data->v_string, magic->value.v_string, l) == 0;
break;
case MAGIC_CHECK_STRING_GREATER:
l = magic->data_size < 1 ? strlen (data->v_string) : magic->data_size;
cmp = strncmp (data->v_string, magic->value.v_string, l) > 0;
break;
case MAGIC_CHECK_STRING_SMALLER:
l = magic->data_size < 1 ? strlen (data->v_string) : magic->data_size;
cmp = strncmp (data->v_string, magic->value.v_string, l) < 0;
break;
}
return cmp > 0;
}
static inline gboolean
magic_read_data (BFile *bfile,
Magic *magic,
MagicData *data)
{
guint file_size = bfile_get_size (bfile);
if (magic->read_size)
data->v_uint32 = file_size;
else if (magic->read_string)
{
guint l = magic->data_size;
if (l < 1 || l > MAX_MAGIC_STRING)
l = MIN (MAX_MAGIC_STRING, file_size - magic->offset);
if (!bfile_read (bfile, magic->offset, data->v_string, l))
return FALSE;
data->v_string[MAX (l, 0)] = 0;
}
else
{
if (magic->data_size == 4)
{
guint32 uint32 = 0;
if (!bfile_read (bfile, magic->offset, &uint32, 4))
return FALSE;
if (magic->need_swap)
data->v_uint32 = GUINT32_SWAP_LE_BE (uint32);
else
data->v_uint32 = uint32;
}
else if (magic->data_size == 2)
{
guint16 uint16 = 0;
if (!bfile_read (bfile, magic->offset, &uint16, 2))
return FALSE;
if (magic->need_swap)
uint16 = GUINT16_SWAP_LE_BE (uint16);
if (magic->cmp_unsigned)
data->v_uint32 = uint16;
else
data->v_int32 = (signed) uint16;
}
else if (magic->data_size == 1)
{
guint8 uint8;
if (!bfile_read (bfile, magic->offset, &uint8, 1))
return FALSE;
if (magic->cmp_unsigned)
data->v_uint32 = uint8;
else
data->v_int32 = (signed) uint8;
}
else
g_assert_not_reached ();
}
return TRUE;
}
static gboolean
magic_match_file (BFile *bfile,
Magic *magics)
{
g_return_val_if_fail (bfile != NULL, FALSE);
g_return_val_if_fail (magics != NULL, FALSE);
do
{
gchar data_string[MAX_MAGIC_STRING + 1];
MagicData data;
if (magics->read_string)
data.v_string = data_string;
else
data.v_uint32 = 0;
if (!magic_read_data (bfile, magics, &data) ||
!magic_check_data (magics, &data))
return FALSE;
magics = magics->next;
}
while (magics);
return TRUE;
}
/* --- buffered file, optimized for magic checks --- */
static gboolean
bfile_open (BFile *bfile,
const gchar *file_name)
{
struct stat buf = { 0, };
gint ret;
g_return_val_if_fail (bfile != NULL, FALSE);
g_return_val_if_fail (bfile->fd < 0, FALSE);
g_return_val_if_fail (file_name != NULL, FALSE);
bfile->fd = open (file_name, O_RDONLY);
if (bfile->fd < 0)
return FALSE;
do
ret = fstat (bfile->fd, &buf);
while (ret < 0 && errno == EINTR);
if (ret < 0)
{
bfile_close (bfile);
return FALSE;
}
bfile->file_size = buf.st_size;
do
ret = read (bfile->fd, bfile->header, BFILE_BSIZE);
while (ret < 0 && errno == EINTR);
if (ret < 0)
{
bfile_close (bfile);
return FALSE;
}
bfile->offset = 0;
memcpy (bfile->buffer, bfile->header, BFILE_BSIZE);
return TRUE;
}
static gboolean
bfile_read (BFile *bfile,
guint offset,
void *mem,
guint n_bytes)
{
guint end = offset + n_bytes;
gint ret;
g_return_val_if_fail (bfile != NULL, FALSE);
g_return_val_if_fail (n_bytes < BFILE_BSIZE / 2, FALSE);
if (end > bfile->file_size || bfile->fd < 0)
return FALSE;
if (end < BFILE_BSIZE)
{
memcpy (mem, bfile->header + offset, n_bytes);
return TRUE;
}
if (offset >= bfile->offset && end < bfile->offset + BFILE_BSIZE)
{
memcpy (mem, bfile->buffer + offset - bfile->offset, n_bytes);
return TRUE;
}
bfile->offset = offset - BFILE_BSIZE / 8;
do
ret = lseek (bfile->fd, bfile->offset, SEEK_SET);
while (ret < 0 && errno == EINTR);
if (ret < 0)
{
bfile_close (bfile);
return FALSE;
}
do
ret = read (bfile->fd, bfile->buffer, BFILE_BSIZE);
while (ret < 0 && errno == EINTR);
if (ret < 0)
{
bfile_close (bfile);
return FALSE;
}
if (offset >= bfile->offset && end < bfile->offset + BFILE_BSIZE)
{
memcpy (mem, bfile->buffer + offset - bfile->offset, n_bytes);
return TRUE;
}
return FALSE;
}
static guint
bfile_get_size (BFile *bfile)
{
g_return_val_if_fail (bfile != NULL, 0);
return bfile->fd >= 0 ? bfile->file_size : 0;
}
static void
bfile_close (BFile *bfile)
{
g_return_if_fail (bfile != NULL);
if (bfile->fd >= 0)
close (bfile->fd);
bfile->fd = -1;
}