622 lines
16 KiB
C
Raw Normal View History

/*
* MIO, an I/O abstraction layer replicating C file I/O API.
* Copyright (C) 2010 Colomban Wendling <ban@herbesfolles.org>
*
* This program 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 program 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 "mio.h"
#include "mio-file.c"
#include "mio-memory.c"
#include <glib.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
/**
* mio_new_file_full:
* @filename: Filename to open, passed as-is to @open_func as the first argument
* @mode: Mode in which open the file, passed as-is to @open_func as the second
* argument
* @open_func: A function with the fopen() semantic to use to open the file
* @close_func: A function with the fclose() semantic to close the file when
* the #MIO object is destroyed, or %NULL not to close the #FILE
* object
*
* Creates a new #MIO object working on a file, from a filename and an opening
* function. See also mio_new_file().
*
* This function is generally overkill and mio_new_file() should often be used
* instead, but it allows to specify a custom function to open a file, as well
* as a close function. The former is useful e.g. if you need to wrap fopen()
* for some reason (like filename encoding conversion for example), and the
* latter allows you both to match your custom open function and to choose
* whether the underlying #FILE object should or not be closed when mio_free()
* is called on the returned object.
*
* Free-function: mio_free()
*
* Returns: A new #MIO on success, or %NULL on failure.
*/
MIO *
mio_new_file_full (const gchar *filename,
const gchar *mode,
MIOFOpenFunc open_func,
MIOFCloseFunc close_func)
{
MIO *mio;
/* we need to create the MIO object first, because we may not be able to close
* the opened file if the user passed NULL as the close function, which means
* that everything must succeed if we've opened the file successfully */
mio = g_slice_alloc (sizeof *mio);
if (mio) {
FILE *fp = open_func (filename, mode);
if (! fp) {
g_slice_free1 (sizeof *mio, mio);
mio = NULL;
} else {
mio->type = MIO_TYPE_FILE;
mio->impl.file.fp = fp;
mio->impl.file.close_func = close_func;
/* function table filling */
FILE_SET_VTABLE (mio);
}
}
return mio;
}
/**
* mio_new_file:
* @filename: Filename to open, same as the fopen()'s first argument
* @mode: Mode in which open the file, fopen()'s second argument
*
* Creates a new #MIO object working on a file from a filename; wrapping
* fopen().
* This function simply calls mio_new_file_full() with the libc's fopen() and
* fclose() functions.
*
* Free-function: mio_free()
*
* Returns: A new #MIO on success, or %NULL on failure.
*/
MIO *
mio_new_file (const gchar *filename,
const gchar *mode)
{
return mio_new_file_full (filename, mode, fopen, fclose);
}
/**
* mio_new_fp:
* @fp: An opened #FILE object
* @close_func: (allow-none): Function used to close @fp when the #MIO object
* gets destroyed, or %NULL not to close the #FILE object
*
* Creates a new #MIO object working on a file, from an already opened #FILE
* object.
*
* <example>
* <title>Typical use of this function</title>
* <programlisting>
* MIO *mio = mio_new_fp (fp, fclose);
* </programlisting>
* </example>
*
* Free-function: mio_free()
*
* Returns: A new #MIO on success or %NULL on failure.
*/
MIO *
mio_new_fp (FILE *fp,
MIOFCloseFunc close_func)
{
MIO *mio;
mio = g_slice_alloc (sizeof *mio);
if (mio) {
mio->type = MIO_TYPE_FILE;
mio->impl.file.fp = fp;
mio->impl.file.close_func = close_func;
/* function table filling */
FILE_SET_VTABLE (mio);
}
return mio;
}
/**
* mio_new_memory:
* @data: Initial data (may be %NULL)
* @size: Length of @data in bytes
* @realloc_func: A function with the realloc() semantic used to grow the
* buffer, or %NULL to disable buffer growing
* @free_func: A function with the free() semantic to destroy the data together
* with the object, or %NULL not to destroy the data
*
* Creates a new #MIO object working on memory.
*
* To allow the buffer to grow, you must provide a @realloc_func, otherwise
* trying to write after the end of the current data will fail.
*
* If you want the buffer to be freed together with the #MIO object, you must
* give a @free_func; otherwise the data will still live after #MIO object
* termination.
*
* <example>
* <title>Basic creation of a non-growable, freeable #MIO object</title>
* <programlisting>
* MIO *mio = mio_new_memory (data, size, NULL, g_free);
* </programlisting>
* </example>
*
* <example>
* <title>Basic creation of an empty growable and freeable #MIO object</title>
* <programlisting>
* MIO *mio = mio_new_memory (NULL, 0, g_try_realloc, g_free);
* </programlisting>
* </example>
*
* Free-function: mio_free()
*
* Returns: A new #MIO on success, or %NULL on failure.
*/
MIO *
mio_new_memory (guchar *data,
gsize size,
MIOReallocFunc realloc_func,
GDestroyNotify free_func)
{
MIO *mio;
mio = g_slice_alloc (sizeof *mio);
if (mio) {
mio->type = MIO_TYPE_MEMORY;
mio->impl.mem.buf = data;
mio->impl.mem.ungetch = EOF;
mio->impl.mem.pos = 0;
mio->impl.mem.size = size;
mio->impl.mem.allocated_size = size;
mio->impl.mem.realloc_func = realloc_func;
mio->impl.mem.free_func = free_func;
mio->impl.mem.eof = FALSE;
mio->impl.mem.error = FALSE;
/* function table filling */
MEM_SET_VTABLE (mio);
}
return mio;
}
/**
* mio_file_get_fp:
* @mio: A #MIO object
*
* Gets the underlying #FILE object associated with a #MIO file stream.
*
* <warning><para>The returned object may become invalid after a call to
* mio_free() if the stream was configured to close the file when
* destroyed.</para></warning>
*
* Returns: The underlying #FILE object of the given stream, or %NULL if the
* stream is not a file stream.
*/
FILE *
mio_file_get_fp (MIO *mio)
{
FILE *fp = NULL;
if (mio->type == MIO_TYPE_FILE) {
fp = mio->impl.file.fp;
}
return fp;
}
/**
* mio_memory_get_data:
* @mio: A #MIO object
* @size: (allow-none) (out): Return location for the length of the returned
* memory, or %NULL
*
* Gets the underlying memory buffer associated with a #MIO memory stream.
*
* <warning><para>The returned pointer and size may become invalid after a
* successful write on the stream or after a call to mio_free() if the stream
* was configured to free the memory when destroyed.</para></warning>
*
* Returns: The memory buffer of the given #MIO stream, or %NULL if the stream
* is not a memory stream.
*/
guchar *
mio_memory_get_data (MIO *mio,
gsize *size)
{
guchar *ptr = NULL;
if (mio->type == MIO_TYPE_MEMORY) {
ptr = mio->impl.mem.buf;
if (size) *size = mio->impl.mem.size;
}
return ptr;
}
/**
* mio_free:
* @mio: A #MIO object
*
* Destroys a #MIO object.
*/
void
mio_free (MIO *mio)
{
if (mio) {
mio->v_free (mio);
g_slice_free1 (sizeof *mio, mio);
}
}
/**
* mio_read:
* @mio: A #MIO object
* @ptr: Pointer to the memory to fill with the read data
* @size: Size of each block to read
* @nmemb: Number o blocks to read
*
* Reads raw data from a #MIO stream. This function behave the same as fread().
*
* Returns: The number of actually read blocks. If an error occurs or if the
* end of the stream is reached, the return value may be smaller than
* the requested block count, or even 0. This function doesn't
* distinguish between end-of-stream and an error, you should then use
* mio_eof() and mio_error() to determine which occurred.
*/
gsize
mio_read (MIO *mio,
void *ptr,
gsize size,
gsize nmemb)
{
return mio->v_read (mio, ptr, size, nmemb);
}
/**
* mio_write:
* @mio: A #MIO object
* @ptr: Pointer to the memory to write on the stream
* @size: Size of each block to write
* @nmemb: Number of block to write
*
* Writes raw data to a #MIO stream. This function behaves the same as fwrite().
*
* Returns: The number of blocks actually written to the stream. This might be
* smaller than the requested count if a write error occurs.
*/
gsize
mio_write (MIO *mio,
const void *ptr,
gsize size,
gsize nmemb)
{
return mio->v_write (mio, ptr, size, nmemb);
}
/**
* mio_putc:
* @mio: A #MIO object
* @c: The character to write
*
* Writes a character to a #MIO stream. This function behaves the same as
* fputc().
*
* Returns: The written wharacter, or %EOF on error.
*/
gint
mio_putc (MIO *mio,
gint c)
{
return mio->v_putc (mio, c);
}
/**
* mio_puts:
* @mio: A #MIO object
* @s: The string to write
*
* Writes a string to a #MIO object. This function behaves the same as fputs().
*
* Returns: A non-negative integer on success or %EOF on failure.
*/
gint
mio_puts (MIO *mio,
const gchar *s)
{
return mio->v_puts (mio, s);
}
/**
* mio_vprintf:
* @mio: A #MIO object
* @format: A printf fomrat string
* @ap: The variadic argument list for the format
*
* Writes a formatted string into a #MIO stream. This function behaves the same
* as vfprintf().
*
* Returns: The number of bytes written in the stream, or a negative value on
* failure.
*/
gint
mio_vprintf (MIO *mio,
const gchar *format,
va_list ap)
{
return mio->v_vprintf (mio, format, ap);
}
/**
* mio_printf:
* @mio: A #MIO object
* @format: A print format string
* @...: Arguments of the format
*
* Writes a formatted string to a #MIO stream. This function behaves the same as
* fprintf().
*
* Returns: The number of bytes written to the stream, or a negative value on
* failure.
*/
gint
mio_printf (MIO *mio,
const gchar *format,
...)
{
gint rv;
va_list ap;
va_start (ap, format);
rv = mio->v_vprintf (mio, format, ap);
va_end (ap);
return rv;
}
/**
* mio_getc:
* @mio: A #MIO object
*
* Gets the current character from a #MIO stream. This function behaves the same
* as fgetc().
*
* Returns: The read character as a #gint, or %EOF on error.
*/
gint
mio_getc (MIO *mio)
{
return mio->v_getc (mio);
}
/**
* mio_ungetc:
* @mio: A #MIO object
* @ch: Character to put back in the stream
*
* Puts a character back in a #MIO stream. This function behaves the sames as
* ungetc().
*
* <warning><para>It is only guaranteed that one character can be but back at a
* time, even if the implementation may allow more.</para></warning>
* <warning><para>Using this function while the stream cursor is at offset 0 is
* not guaranteed to function properly. As the C99 standard says, it is "an
* obsolescent feature".</para></warning>
*
* Returns: The character put back, or %EOF on error.
*/
gint
mio_ungetc (MIO *mio,
gint ch)
{
return mio->v_ungetc (mio, ch);
}
/**
* mio_gets:
* @mio: A #MIO object
* @s: A string to fill with the read data
* @size: The maximum number of bytes to read
*
* Reads a string from a #MIO stream, stopping after the first new-line
* character or at the end of the stream. This function behaves the same as
* fgets().
*
* Returns: @s on success, %NULL otherwise.
*/
gchar *
mio_gets (MIO *mio,
gchar *s,
gsize size)
{
return mio->v_gets (mio, s, size);
}
/**
* mio_clearerr:
* @mio: A #MIO object
*
* Clears the error and end-of-stream indicators of a #MIO stream. This function
* behaves the same as clearerr().
*/
void
mio_clearerr (MIO *mio)
{
mio->v_clearerr (mio);
}
/**
* mio_eof:
* @mio: A #MIO object
*
* Checks whether the end-of-stream indicator of a #MIO stream is set. This
* function behaves the same as feof().
*
* Returns: A non-null value if the stream reached its end, 0 otherwise.
*/
gint
mio_eof (MIO *mio)
{
return mio->v_eof (mio);
}
/**
* mio_error:
* @mio: A #MIO object
*
* Checks whether the error indicator of a #MIO stream is set. This function
* behaves the same as ferror().
*
* Returns: A non-null value if the stream have an error set, 0 otherwise.
*/
gint
mio_error (MIO *mio)
{
return mio->v_error (mio);
}
/**
* mio_seek:
* @mio: A #MIO object
* @offset: Offset of the new place, from @whence
* @whence: Move origin. SEEK_SET moves relative to the start of the stream,
* SEEK_CUR from the current position and SEEK_SET from the end of the
* stream.
*
* Sets the curosr position on a #MIO stream. This functions behaves the same as
* fseek(). See also mio_tell() and mio_setpos().
*
* Returns: 0 on success, -1 otherwise, in which case errno should be set to
* indicate the error.
*/
gint
mio_seek (MIO *mio,
glong offset,
gint whence)
{
return mio->v_seek (mio, offset, whence);
}
/**
* mio_tell:
* @mio: A #MIO object
*
* Gets the current cursor position of a #MIO stream. This function behaves the
* same as ftell().
*
* Returns: The current offset from the start of the stream, or -1 or error, in
* which case errno is set to indicate the error.
*/
glong
mio_tell (MIO *mio)
{
return mio->v_tell (mio);
}
/**
* mio_rewind:
* @mio: A #MIO object
*
* Resets the cursor position to 0, and also the end-of-stream and the error
* indicators of a #MIO stream.
* See also mio_seek() and mio_clearerr().
*/
void
mio_rewind (MIO *mio)
{
mio->v_rewind (mio);
}
/**
* mio_getpos:
* @mio: A #MIO stream
* @pos: (out): A #MIOPos object to fill-in
*
* Stores the current position (and maybe other informations about the stream
* state) of a #MIO stream in order to restore it later with mio_setpos(). This
* function behaves the same as fgetpos().
*
* Returns: 0 on success, -1 otherwise, in which case errno is set to indicate
* the error.
*/
gint
mio_getpos (MIO *mio,
MIOPos *pos)
{
gint rv = -1;
pos->type = mio->type;
rv = mio->v_getpos (mio, pos);
#ifdef MIO_DEBUG
if (rv != -1) {
pos->tag = mio;
}
#endif /* MIO_DEBUG */
return rv;
}
/**
* mio_setpos:
* @mio: A #MIO object
* @pos: (in): A #MIOPos object filled-in by a previous call of mio_getpos() on
* the same stream
*
* Restores the position and state indicators of a #MIO stream previously saved
* by mio_getpos().
*
* <warning><para>The #MIOPos object must have been initialized by a previous
* call to mio_getpos() on the same stream.</para></warning>
*
* Returns: 0 on success, -1 otherwise, in which case errno is set to indicate
* the error.
*/
gint
mio_setpos (MIO *mio,
MIOPos *pos)
{
gint rv = -1;
#ifdef MIO_DEBUG
if (pos->tag != mio) {
g_critical ("mio_setpos((MIO*)%p, (MIOPos*)%p): "
"Given MIOPos was not set by a previous call to mio_getpos() "
"on the same MIO object, which means there is a bug in "
"someone's code.",
(void *)mio, (void *)pos);
errno = EINVAL;
return -1;
}
#endif /* MIO_DEBUG */
rv = mio->v_setpos (mio, pos);
return rv;
}