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.
465 lines
10 KiB
465 lines
10 KiB
/* GSL - Generic Sound Layer
|
|
* Copyright (C) 2002 Tim Janik
|
|
*
|
|
* This library is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU 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 General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
#include "gslfilehash.h"
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
|
|
|
|
/* macros */
|
|
#if (GLIB_SIZEOF_LONG > 4)
|
|
#define HASH_LONG(l) (l + (l >> 32))
|
|
#else
|
|
#define HASH_LONG(l) (l)
|
|
#endif
|
|
|
|
|
|
/* --- variables --- */
|
|
static GslMutex fdpool_mutex = { 0, };
|
|
static GHashTable *hfile_ht = NULL;
|
|
|
|
|
|
/* --- functions --- */
|
|
static guint
|
|
hfile_hash (gconstpointer key)
|
|
{
|
|
const GslHFile *hfile = key;
|
|
guint h;
|
|
|
|
h = HASH_LONG (hfile->mtime);
|
|
h ^= g_str_hash (hfile->file_name);
|
|
h ^= HASH_LONG (hfile->n_bytes);
|
|
|
|
return h;
|
|
}
|
|
|
|
static gboolean
|
|
hfile_equals (gconstpointer key1,
|
|
gconstpointer key2)
|
|
{
|
|
const GslHFile *hfile1 = key1;
|
|
const GslHFile *hfile2 = key2;
|
|
|
|
return (hfile1->mtime == hfile2->mtime &&
|
|
hfile1->n_bytes == hfile2->n_bytes &&
|
|
strcmp (hfile1->file_name, hfile2->file_name) == 0);
|
|
}
|
|
|
|
void
|
|
_gsl_init_fd_pool (void)
|
|
{
|
|
g_assert (hfile_ht == NULL);
|
|
|
|
gsl_mutex_init (&fdpool_mutex);
|
|
hfile_ht = g_hash_table_new (hfile_hash, hfile_equals);
|
|
}
|
|
|
|
static gboolean
|
|
stat_fd (gint fd,
|
|
GTime *mtime,
|
|
GslLong *n_bytes)
|
|
{
|
|
struct stat statbuf = { 0, };
|
|
|
|
if (fstat (fd, &statbuf) < 0)
|
|
return FALSE; /* have errno */
|
|
if (mtime)
|
|
*mtime = statbuf.st_mtime;
|
|
if (n_bytes)
|
|
*n_bytes = statbuf.st_size;
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
stat_file (const gchar *file_name,
|
|
GTime *mtime,
|
|
GslLong *n_bytes)
|
|
{
|
|
struct stat statbuf = { 0, };
|
|
|
|
if (stat (file_name, &statbuf) < 0)
|
|
return FALSE; /* have errno */
|
|
if (mtime)
|
|
*mtime = statbuf.st_mtime;
|
|
if (n_bytes)
|
|
*n_bytes = statbuf.st_size;
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* gsl_hfile_open
|
|
* @file_name: name of the file to open
|
|
* @RETURNS: a new opened #GslHFile or NULL if an error occoured (errno set)
|
|
*
|
|
* Open a file for reading and return the associated GSL hashed file.
|
|
* The motivation for using a #GslHFile over normal unix file
|
|
* descriptors is to reduce the amount of opened unix file descriptors and
|
|
* to ensure thread safety upon reading offset relative byte blocks.
|
|
* Multiple open #GslHFiles with equal file names will share a
|
|
* single unix file descriptor as long as the file wasn't modified meanwhile.
|
|
* This function is MT-safe and may be called from any thread.
|
|
*/
|
|
GslHFile*
|
|
gsl_hfile_open (const gchar *file_name)
|
|
{
|
|
GslHFile key, *hfile;
|
|
gint ret_errno;
|
|
|
|
errno = EFAULT;
|
|
g_return_val_if_fail (file_name != NULL, NULL);
|
|
|
|
key.file_name = (gchar*) file_name;
|
|
if (!stat_file (file_name, &key.mtime, &key.n_bytes))
|
|
return NULL; /* errno from stat() */
|
|
|
|
GSL_SYNC_LOCK (&fdpool_mutex);
|
|
hfile = g_hash_table_lookup (hfile_ht, &key);
|
|
if (hfile)
|
|
{
|
|
GSL_SYNC_LOCK (&hfile->mutex);
|
|
hfile->ocount++;
|
|
GSL_SYNC_UNLOCK (&hfile->mutex);
|
|
ret_errno = 0;
|
|
}
|
|
else
|
|
{
|
|
gint fd;
|
|
|
|
fd = open (file_name, O_RDONLY | O_NOCTTY, 0);
|
|
if (fd >= 0)
|
|
{
|
|
hfile = gsl_new_struct0 (GslHFile, 1);
|
|
hfile->file_name = g_strdup (file_name);
|
|
hfile->mtime = key.mtime;
|
|
hfile->n_bytes = key.n_bytes;
|
|
hfile->cpos = 0;
|
|
hfile->fd = fd;
|
|
hfile->ocount = 1;
|
|
gsl_mutex_init (&hfile->mutex);
|
|
g_hash_table_insert (hfile_ht, hfile, hfile);
|
|
ret_errno = 0;
|
|
}
|
|
else
|
|
ret_errno = errno;
|
|
}
|
|
GSL_SYNC_UNLOCK (&fdpool_mutex);
|
|
|
|
errno = ret_errno;
|
|
return hfile;
|
|
}
|
|
|
|
/**
|
|
* gsl_hfile_close
|
|
* @hfile: valid #GslHFile
|
|
*
|
|
* Close and destroy a #GslHFile.
|
|
* This function is MT-safe and may be called from any thread.
|
|
*/
|
|
void
|
|
gsl_hfile_close (GslHFile *hfile)
|
|
{
|
|
gboolean destroy = FALSE;
|
|
|
|
g_return_if_fail (hfile != NULL);
|
|
g_return_if_fail (hfile->ocount > 0);
|
|
|
|
GSL_SYNC_LOCK (&fdpool_mutex);
|
|
GSL_SYNC_LOCK (&hfile->mutex);
|
|
if (hfile->ocount > 1)
|
|
hfile->ocount--;
|
|
else
|
|
{
|
|
if (!g_hash_table_remove (hfile_ht, hfile))
|
|
g_warning ("%s: failed to unlink hashed file (%p)",
|
|
G_STRLOC, hfile);
|
|
else
|
|
{
|
|
hfile->ocount = 0;
|
|
destroy = TRUE;
|
|
}
|
|
}
|
|
GSL_SYNC_UNLOCK (&hfile->mutex);
|
|
GSL_SYNC_UNLOCK (&fdpool_mutex);
|
|
|
|
if (destroy)
|
|
{
|
|
gsl_mutex_destroy (&hfile->mutex);
|
|
close (hfile->fd);
|
|
g_free (hfile->file_name);
|
|
gsl_delete_struct (GslHFile, hfile);
|
|
}
|
|
errno = 0;
|
|
}
|
|
|
|
/**
|
|
* gsl_hfile_pread
|
|
* @hfile: valid GslHFile
|
|
* @offset: offset in bytes within 0 and file end
|
|
* @n_bytes: number of bytes to read
|
|
* @bytes: buffer to store read bytes
|
|
* @error_p: pointer to GslErrorType location
|
|
* @RETURNS: amount of bytes read or -1 if an error occoured (errno set)
|
|
*
|
|
* Read a block of bytes from a GslHFile.
|
|
* This function is MT-safe and may be called from any thread.
|
|
*/
|
|
GslLong
|
|
gsl_hfile_pread (GslHFile *hfile,
|
|
GslLong offset,
|
|
GslLong n_bytes,
|
|
gpointer bytes)
|
|
{
|
|
GslLong ret_bytes = -1;
|
|
gint ret_errno;
|
|
|
|
errno = EFAULT;
|
|
g_return_val_if_fail (hfile != NULL, -1);
|
|
g_return_val_if_fail (hfile->ocount > 0, -1);
|
|
g_return_val_if_fail (offset >= 0, -1);
|
|
if (offset >= hfile->n_bytes || n_bytes < 1)
|
|
{
|
|
errno = 0;
|
|
return 0;
|
|
}
|
|
g_return_val_if_fail (bytes != NULL, -1);
|
|
|
|
GSL_SYNC_LOCK (&hfile->mutex);
|
|
if (hfile->ocount)
|
|
{
|
|
if (hfile->cpos != offset)
|
|
{
|
|
hfile->cpos = lseek (hfile->fd, offset, SEEK_SET);
|
|
if (hfile->cpos < 0 && errno != EINVAL)
|
|
{
|
|
ret_errno = errno;
|
|
GSL_SYNC_UNLOCK (&hfile->mutex);
|
|
errno = ret_errno;
|
|
return -1;
|
|
}
|
|
}
|
|
if (hfile->cpos == offset)
|
|
{
|
|
do
|
|
ret_bytes = read (hfile->fd, bytes, n_bytes);
|
|
while (ret_bytes < 0 && errno == EINTR);
|
|
if (ret_bytes < 0)
|
|
{
|
|
ret_errno = errno;
|
|
ret_bytes = -1;
|
|
}
|
|
else
|
|
{
|
|
ret_errno = 0;
|
|
hfile->cpos += ret_bytes;
|
|
}
|
|
}
|
|
else /* this should only happen if the file changed since open() */
|
|
{
|
|
hfile->cpos = -1;
|
|
if (offset + n_bytes > hfile->n_bytes)
|
|
n_bytes = hfile->n_bytes - offset;
|
|
memset (bytes, 0, n_bytes);
|
|
ret_bytes = n_bytes;
|
|
ret_errno = 0;
|
|
}
|
|
}
|
|
else
|
|
ret_errno = EFAULT;
|
|
GSL_SYNC_UNLOCK (&hfile->mutex);
|
|
|
|
errno = ret_errno;
|
|
return ret_bytes;
|
|
}
|
|
|
|
/**
|
|
* gsl_rfile_open
|
|
* @file_name: name of the file to open
|
|
* @RETURNS: a new opened #GslRFile or NULL if an error occoured (errno set)
|
|
*
|
|
* Open a file for reading and create a GSL read only file handle for it.
|
|
* The motivation for using a #GslRFile over normal unix files
|
|
* is to reduce the amount of opened unix file descriptors by using
|
|
* a #GslHFile for the actual IO.
|
|
*/
|
|
GslRFile*
|
|
gsl_rfile_open (const gchar *file_name)
|
|
{
|
|
GslHFile *hfile = gsl_hfile_open (file_name);
|
|
GslRFile *rfile;
|
|
|
|
if (!hfile)
|
|
rfile = NULL;
|
|
else
|
|
{
|
|
rfile = gsl_new_struct0 (GslRFile, 1);
|
|
rfile->hfile = hfile;
|
|
rfile->offset = 0;
|
|
}
|
|
return rfile;
|
|
}
|
|
|
|
/**
|
|
* gsl_rfile_name
|
|
* @rfile: valid #GslRFile
|
|
* @RETURNS: the file name used to open this file
|
|
*
|
|
* Retrive the file name used to open @rfile.
|
|
*/
|
|
gchar*
|
|
gsl_rfile_name (GslRFile *rfile)
|
|
{
|
|
errno = EFAULT;
|
|
g_return_val_if_fail (rfile != NULL, NULL);
|
|
|
|
errno = 0;
|
|
return rfile->hfile->file_name;
|
|
}
|
|
|
|
/**
|
|
* gsl_rfile_seek_set
|
|
* @rfile: valid #GslRFile
|
|
* @offset: new seek position within 0 and gsl_rfile_length()+1
|
|
* @RETURNS: resulting position within 0 and gsl_rfile_length()+1
|
|
*
|
|
* Set the current #GslRFile seek position.
|
|
*/
|
|
GslLong
|
|
gsl_rfile_seek_set (GslRFile *rfile,
|
|
GslLong offset)
|
|
{
|
|
GslLong l;
|
|
|
|
errno = EFAULT;
|
|
g_return_val_if_fail (rfile != NULL, 0);
|
|
|
|
l = rfile->hfile->n_bytes;
|
|
rfile->offset = CLAMP (offset, 0, l);
|
|
|
|
errno = 0;
|
|
return rfile->offset;
|
|
}
|
|
|
|
/**
|
|
* gsl_rfile_position
|
|
* @rfile: valid #GslRFile
|
|
* @RETURNS: current position within 0 and gsl_rfile_length()
|
|
*
|
|
* Retrive the current #GslRFile seek position.
|
|
*/
|
|
GslLong
|
|
gsl_rfile_position (GslRFile *rfile)
|
|
{
|
|
errno = EFAULT;
|
|
g_return_val_if_fail (rfile != NULL, 0);
|
|
|
|
errno = 0;
|
|
return rfile->offset;
|
|
}
|
|
|
|
/**
|
|
* gsl_rfile_length
|
|
* @rfile: valid #GslRFile
|
|
* @RETURNS: total length of the #GslRFile in bytes
|
|
*
|
|
* Retrive the file length of @rfile in bytes.
|
|
*/
|
|
GslLong
|
|
gsl_rfile_length (GslRFile *rfile)
|
|
{
|
|
GslLong l;
|
|
|
|
errno = EFAULT;
|
|
g_return_val_if_fail (rfile != NULL, 0);
|
|
|
|
l = rfile->hfile->n_bytes;
|
|
|
|
errno = 0;
|
|
return l;
|
|
}
|
|
|
|
/**
|
|
* gsl_rfile_pread
|
|
* @rfile: valid GslRFile
|
|
* @offset: offset in bytes within 0 and gsl_rfile_length()
|
|
* @n_bytes: number of bytes to read
|
|
* @bytes: buffer to store read bytes
|
|
* @error_p: pointer to GslErrorType location
|
|
* @RETURNS: amount of bytes read or -1 if an error occoured (errno set)
|
|
*
|
|
* Read a block of bytes from a GslRFile at a specified position.
|
|
*/
|
|
GslLong
|
|
gsl_rfile_pread (GslRFile *rfile,
|
|
GslLong offset,
|
|
GslLong n_bytes,
|
|
gpointer bytes)
|
|
{
|
|
errno = EFAULT;
|
|
g_return_val_if_fail (rfile != NULL, -1);
|
|
|
|
return gsl_hfile_pread (rfile->hfile, offset, n_bytes, bytes);
|
|
}
|
|
|
|
/**
|
|
* gsl_rfile_read
|
|
* @rfile: valid GslRFile
|
|
* @n_bytes: number of bytes to read
|
|
* @bytes: buffer to store read bytes
|
|
* @error_p: pointer to GslErrorType location
|
|
* @RETURNS: amount of bytes read or -1 if an error occoured (errno set)
|
|
*
|
|
* Read a block of bytes from a GslRFile from the current seek position
|
|
* and advance the seek position.
|
|
*/
|
|
GslLong
|
|
gsl_rfile_read (GslRFile *rfile,
|
|
GslLong n_bytes,
|
|
gpointer bytes)
|
|
{
|
|
GslLong l;
|
|
|
|
errno = EFAULT;
|
|
g_return_val_if_fail (rfile != NULL, -1);
|
|
|
|
l = gsl_hfile_pread (rfile->hfile, rfile->offset, n_bytes, bytes);
|
|
if (l > 0)
|
|
rfile->offset += l;
|
|
return l;
|
|
}
|
|
|
|
/**
|
|
* gsl_rfile_close
|
|
* @rfile: valid #GslRFile
|
|
*
|
|
* Close and destroy a #GslRFile.
|
|
*/
|
|
void
|
|
gsl_rfile_close (GslRFile *rfile)
|
|
{
|
|
errno = EFAULT;
|
|
g_return_if_fail (rfile != NULL);
|
|
|
|
gsl_hfile_close (rfile->hfile);
|
|
gsl_delete_struct (GslRFile, rfile);
|
|
errno = 0;
|
|
}
|