/* GIO - GLib Input, Output and Streaming Library * * Copyright (C) 2006-2007 Red Hat, Inc. * * SPDX-License-Identifier: LGPL-2.1-or-later * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, see . * * Authors: * Christian Kellner * Krzysztof KosiƄski */ #include "config.h" #include "gmemoryoutputstream.h" #include "goutputstream.h" #include "gpollableoutputstream.h" #include "gseekable.h" #include "gtask.h" #include "gioerror.h" #include "string.h" #include "glibintl.h" #include "gutilsprivate.h" /** * GMemoryOutputStream: * * `GMemoryOutputStream` is a class for using arbitrary * memory chunks as output for GIO streaming output operations. * * As of GLib 2.34, `GMemoryOutputStream` trivially implements * [iface@Gio.PollableOutputStream]: it always polls as ready. */ #define MIN_ARRAY_SIZE 16 enum { PROP_0, PROP_DATA, PROP_SIZE, PROP_DATA_SIZE, PROP_REALLOC_FUNCTION, PROP_DESTROY_FUNCTION }; struct _GMemoryOutputStreamPrivate { gpointer data; /* Write buffer */ gsize len; /* Current length of the data buffer. Can change with resizing. */ gsize valid_len; /* The part of data that has been written to */ gsize pos; /* Current position in the stream. Distinct from valid_len, because the stream is seekable. */ GReallocFunc realloc_fn; GDestroyNotify destroy; }; static void g_memory_output_stream_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void g_memory_output_stream_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void g_memory_output_stream_finalize (GObject *object); static gssize g_memory_output_stream_write (GOutputStream *stream, const void *buffer, gsize count, GCancellable *cancellable, GError **error); static gboolean g_memory_output_stream_close (GOutputStream *stream, GCancellable *cancellable, GError **error); static void g_memory_output_stream_close_async (GOutputStream *stream, int io_priority, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data); static gboolean g_memory_output_stream_close_finish (GOutputStream *stream, GAsyncResult *result, GError **error); static void g_memory_output_stream_seekable_iface_init (GSeekableIface *iface); static goffset g_memory_output_stream_tell (GSeekable *seekable); static gboolean g_memory_output_stream_can_seek (GSeekable *seekable); static gboolean g_memory_output_stream_seek (GSeekable *seekable, goffset offset, GSeekType type, GCancellable *cancellable, GError **error); static gboolean g_memory_output_stream_can_truncate (GSeekable *seekable); static gboolean g_memory_output_stream_truncate (GSeekable *seekable, goffset offset, GCancellable *cancellable, GError **error); static gboolean g_memory_output_stream_is_writable (GPollableOutputStream *stream); static GSource *g_memory_output_stream_create_source (GPollableOutputStream *stream, GCancellable *cancellable); static void g_memory_output_stream_pollable_iface_init (GPollableOutputStreamInterface *iface); G_DEFINE_TYPE_WITH_CODE (GMemoryOutputStream, g_memory_output_stream, G_TYPE_OUTPUT_STREAM, G_ADD_PRIVATE (GMemoryOutputStream) G_IMPLEMENT_INTERFACE (G_TYPE_SEEKABLE, g_memory_output_stream_seekable_iface_init); G_IMPLEMENT_INTERFACE (G_TYPE_POLLABLE_OUTPUT_STREAM, g_memory_output_stream_pollable_iface_init)) static void g_memory_output_stream_class_init (GMemoryOutputStreamClass *klass) { GOutputStreamClass *ostream_class; GObjectClass *gobject_class; gobject_class = G_OBJECT_CLASS (klass); gobject_class->set_property = g_memory_output_stream_set_property; gobject_class->get_property = g_memory_output_stream_get_property; gobject_class->finalize = g_memory_output_stream_finalize; ostream_class = G_OUTPUT_STREAM_CLASS (klass); ostream_class->write_fn = g_memory_output_stream_write; ostream_class->close_fn = g_memory_output_stream_close; ostream_class->close_async = g_memory_output_stream_close_async; ostream_class->close_finish = g_memory_output_stream_close_finish; /** * GMemoryOutputStream:data: * * Pointer to buffer where data will be written. * * Since: 2.24 **/ g_object_class_install_property (gobject_class, PROP_DATA, g_param_spec_pointer ("data", NULL, NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); /** * GMemoryOutputStream:size: * * Current size of the data buffer. * * Since: 2.24 **/ g_object_class_install_property (gobject_class, PROP_SIZE, g_param_spec_ulong ("size", NULL, NULL, 0, G_MAXULONG, 0, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); /** * GMemoryOutputStream:data-size: * * Size of data written to the buffer. * * Since: 2.24 **/ g_object_class_install_property (gobject_class, PROP_DATA_SIZE, g_param_spec_ulong ("data-size", NULL, NULL, 0, G_MAXULONG, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); /** * GMemoryOutputStream:realloc-function: (skip) * * Function with realloc semantics called to enlarge the buffer. * * Since: 2.24 **/ g_object_class_install_property (gobject_class, PROP_REALLOC_FUNCTION, g_param_spec_pointer ("realloc-function", NULL, NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); /** * GMemoryOutputStream:destroy-function: (skip) * * Function called with the buffer as argument when the stream is destroyed. * * Since: 2.24 **/ g_object_class_install_property (gobject_class, PROP_DESTROY_FUNCTION, g_param_spec_pointer ("destroy-function", NULL, NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); } static void g_memory_output_stream_pollable_iface_init (GPollableOutputStreamInterface *iface) { iface->is_writable = g_memory_output_stream_is_writable; iface->create_source = g_memory_output_stream_create_source; } static void g_memory_output_stream_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GMemoryOutputStream *stream; GMemoryOutputStreamPrivate *priv; stream = G_MEMORY_OUTPUT_STREAM (object); priv = stream->priv; switch (prop_id) { case PROP_DATA: priv->data = g_value_get_pointer (value); break; case PROP_SIZE: priv->len = g_value_get_ulong (value); break; case PROP_REALLOC_FUNCTION: priv->realloc_fn = g_value_get_pointer (value); break; case PROP_DESTROY_FUNCTION: priv->destroy = g_value_get_pointer (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void g_memory_output_stream_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GMemoryOutputStream *stream; GMemoryOutputStreamPrivate *priv; stream = G_MEMORY_OUTPUT_STREAM (object); priv = stream->priv; switch (prop_id) { case PROP_DATA: g_value_set_pointer (value, priv->data); break; case PROP_SIZE: g_value_set_ulong (value, priv->len); break; case PROP_DATA_SIZE: g_value_set_ulong (value, priv->valid_len); break; case PROP_REALLOC_FUNCTION: g_value_set_pointer (value, priv->realloc_fn); break; case PROP_DESTROY_FUNCTION: g_value_set_pointer (value, priv->destroy); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void g_memory_output_stream_finalize (GObject *object) { GMemoryOutputStream *stream; GMemoryOutputStreamPrivate *priv; stream = G_MEMORY_OUTPUT_STREAM (object); priv = stream->priv; if (priv->destroy) priv->destroy (priv->data); G_OBJECT_CLASS (g_memory_output_stream_parent_class)->finalize (object); } static void g_memory_output_stream_seekable_iface_init (GSeekableIface *iface) { iface->tell = g_memory_output_stream_tell; iface->can_seek = g_memory_output_stream_can_seek; iface->seek = g_memory_output_stream_seek; iface->can_truncate = g_memory_output_stream_can_truncate; iface->truncate_fn = g_memory_output_stream_truncate; } static void g_memory_output_stream_init (GMemoryOutputStream *stream) { stream->priv = g_memory_output_stream_get_instance_private (stream); stream->priv->pos = 0; stream->priv->valid_len = 0; } /** * g_memory_output_stream_new: (skip) * @data: (nullable): pointer to a chunk of memory to use, or %NULL * @size: the size of @data * @realloc_function: (nullable): a function with realloc() semantics (like g_realloc()) * to be called when @data needs to be grown, or %NULL * @destroy_function: (nullable): a function to be called on @data when the stream is * finalized, or %NULL * * Creates a new #GMemoryOutputStream. * * In most cases this is not the function you want. See * g_memory_output_stream_new_resizable() instead. * * If @data is non-%NULL, the stream will use that for its internal storage. * * If @realloc_fn is non-%NULL, it will be used for resizing the internal * storage when necessary and the stream will be considered resizable. * In that case, the stream will start out being (conceptually) empty. * @size is used only as a hint for how big @data is. Specifically, * seeking to the end of a newly-created stream will seek to zero, not * @size. Seeking past the end of the stream and then writing will * introduce a zero-filled gap. * * If @realloc_fn is %NULL then the stream is fixed-sized. Seeking to * the end will seek to @size exactly. Writing past the end will give * an 'out of space' error. Attempting to seek past the end will fail. * Unlike the resizable case, seeking to an offset within the stream and * writing will preserve the bytes passed in as @data before that point * and will return them as part of g_memory_output_stream_steal_data(). * If you intend to seek you should probably therefore ensure that @data * is properly initialised. * * It is probably only meaningful to provide @data and @size in the case * that you want a fixed-sized stream. Put another way: if @realloc_fn * is non-%NULL then it makes most sense to give @data as %NULL and * @size as 0 (allowing #GMemoryOutputStream to do the initial * allocation for itself). * * |[ * // a stream that can grow * stream = g_memory_output_stream_new (NULL, 0, realloc, free); * * // another stream that can grow * stream2 = g_memory_output_stream_new (NULL, 0, g_realloc, g_free); * * // a fixed-size stream * data = malloc (200); * stream3 = g_memory_output_stream_new (data, 200, NULL, free); * ]| * * Returns: A newly created #GMemoryOutputStream object. **/ GOutputStream * g_memory_output_stream_new (gpointer data, gsize size, GReallocFunc realloc_function, GDestroyNotify destroy_function) { GOutputStream *stream; stream = g_object_new (G_TYPE_MEMORY_OUTPUT_STREAM, "data", data, "size", size, "realloc-function", realloc_function, "destroy-function", destroy_function, NULL); return stream; } /** * g_memory_output_stream_new_resizable: * * Creates a new #GMemoryOutputStream, using g_realloc() and g_free() * for memory allocation. * * Since: 2.36 */ GOutputStream * g_memory_output_stream_new_resizable (void) { return g_memory_output_stream_new (NULL, 0, g_realloc, g_free); } /** * g_memory_output_stream_get_data: * @ostream: a #GMemoryOutputStream * * Gets any loaded data from the @ostream. * * Note that the returned pointer may become invalid on the next * write or truncate operation on the stream. * * Returns: (transfer none): pointer to the stream's data, or %NULL if the data * has been stolen **/ gpointer g_memory_output_stream_get_data (GMemoryOutputStream *ostream) { g_return_val_if_fail (G_IS_MEMORY_OUTPUT_STREAM (ostream), NULL); return ostream->priv->data; } /** * g_memory_output_stream_get_size: * @ostream: a #GMemoryOutputStream * * Gets the size of the currently allocated data area (available from * g_memory_output_stream_get_data()). * * You probably don't want to use this function on resizable streams. * See g_memory_output_stream_get_data_size() instead. For resizable * streams the size returned by this function is an implementation * detail and may be change at any time in response to operations on the * stream. * * If the stream is fixed-sized (ie: no realloc was passed to * g_memory_output_stream_new()) then this is the maximum size of the * stream and further writes will return %G_IO_ERROR_NO_SPACE. * * In any case, if you want the number of bytes currently written to the * stream, use g_memory_output_stream_get_data_size(). * * Returns: the number of bytes allocated for the data buffer */ gsize g_memory_output_stream_get_size (GMemoryOutputStream *ostream) { g_return_val_if_fail (G_IS_MEMORY_OUTPUT_STREAM (ostream), 0); return ostream->priv->len; } /** * g_memory_output_stream_get_data_size: * @ostream: a #GMemoryOutputStream * * Returns the number of bytes from the start up to including the last * byte written in the stream that has not been truncated away. * * Returns: the number of bytes written to the stream * * Since: 2.18 */ gsize g_memory_output_stream_get_data_size (GMemoryOutputStream *ostream) { g_return_val_if_fail (G_IS_MEMORY_OUTPUT_STREAM (ostream), 0); return ostream->priv->valid_len; } /** * g_memory_output_stream_steal_data: * @ostream: a #GMemoryOutputStream * * Gets any loaded data from the @ostream. Ownership of the data * is transferred to the caller; when no longer needed it must be * freed using the free function set in @ostream's * #GMemoryOutputStream:destroy-function property. * * @ostream must be closed before calling this function. * * Returns: (transfer full): the stream's data, or %NULL if it has previously * been stolen * * Since: 2.26 **/ gpointer g_memory_output_stream_steal_data (GMemoryOutputStream *ostream) { gpointer data; g_return_val_if_fail (G_IS_MEMORY_OUTPUT_STREAM (ostream), NULL); g_return_val_if_fail (g_output_stream_is_closed (G_OUTPUT_STREAM (ostream)), NULL); data = ostream->priv->data; ostream->priv->data = NULL; return data; } /** * g_memory_output_stream_steal_as_bytes: * @ostream: a #GMemoryOutputStream * * Returns data from the @ostream as a #GBytes. @ostream must be * closed before calling this function. * * Returns: (transfer full): the stream's data * * Since: 2.34 **/ GBytes * g_memory_output_stream_steal_as_bytes (GMemoryOutputStream *ostream) { GBytes *result; g_return_val_if_fail (G_IS_MEMORY_OUTPUT_STREAM (ostream), NULL); g_return_val_if_fail (g_output_stream_is_closed (G_OUTPUT_STREAM (ostream)), NULL); result = g_bytes_new_with_free_func (ostream->priv->data, ostream->priv->valid_len, ostream->priv->destroy, ostream->priv->data); ostream->priv->data = NULL; return result; } static gboolean array_resize (GMemoryOutputStream *ostream, gsize size, gboolean allow_partial, GError **error) { GMemoryOutputStreamPrivate *priv; gpointer data; gsize len; priv = ostream->priv; if (priv->len == size) return TRUE; if (!priv->realloc_fn) { if (allow_partial && priv->pos < priv->len) return TRUE; /* Short write */ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NO_SPACE, _("Memory output stream not resizable")); return FALSE; } len = priv->len; data = priv->realloc_fn (priv->data, size); if (size > 0 && !data) { if (allow_partial && priv->pos < priv->len) return TRUE; /* Short write */ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NO_SPACE, _("Failed to resize memory output stream")); return FALSE; } if (size > len) memset ((guint8 *)data + len, 0, size - len); priv->data = data; priv->len = size; if (priv->len < priv->valid_len) priv->valid_len = priv->len; return TRUE; } static gssize g_memory_output_stream_write (GOutputStream *stream, const void *buffer, gsize count, GCancellable *cancellable, GError **error) { GMemoryOutputStream *ostream; GMemoryOutputStreamPrivate *priv; guint8 *dest; gsize new_size; ostream = G_MEMORY_OUTPUT_STREAM (stream); priv = ostream->priv; if (count == 0) return 0; /* Check for address space overflow, but only if the buffer is resizable. Otherwise we just do a short write and don't worry. */ if (priv->realloc_fn && priv->pos + count < priv->pos) goto overflow; if (priv->pos + count > priv->len) { /* At least enough to fit the write, rounded up for greater than * linear growth. * * Assuming that we're using something like realloc(), the kernel * will overcommit memory to us, so doubling the size each time * will keep the number of realloc calls low without wasting too * much memory. */ new_size = g_nearest_pow (priv->pos + count); /* Check for overflow again. We have checked if pos + count > G_MAXSIZE, but now check if g_nearest_pow () has overflowed */ if (new_size == 0) goto overflow; new_size = MAX (new_size, MIN_ARRAY_SIZE); if (!array_resize (ostream, new_size, TRUE, error)) return -1; } /* Make sure we handle short writes if the array_resize only added part of the required memory */ count = MIN (count, priv->len - priv->pos); dest = (guint8 *)priv->data + priv->pos; memcpy (dest, buffer, count); priv->pos += count; if (priv->pos > priv->valid_len) priv->valid_len = priv->pos; return count; overflow: /* Overflow: buffer size would need to be bigger than G_MAXSIZE. */ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NO_SPACE, _("Amount of memory required to process the write is " "larger than available address space")); return -1; } static gboolean g_memory_output_stream_close (GOutputStream *stream, GCancellable *cancellable, GError **error) { return TRUE; } static void g_memory_output_stream_close_async (GOutputStream *stream, int io_priority, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data) { GTask *task; task = g_task_new (stream, cancellable, callback, data); g_task_set_source_tag (task, g_memory_output_stream_close_async); /* will always return TRUE */ g_memory_output_stream_close (stream, cancellable, NULL); g_task_return_boolean (task, TRUE); g_object_unref (task); } static gboolean g_memory_output_stream_close_finish (GOutputStream *stream, GAsyncResult *result, GError **error) { g_return_val_if_fail (g_task_is_valid (result, stream), FALSE); return g_task_propagate_boolean (G_TASK (result), error); } static goffset g_memory_output_stream_tell (GSeekable *seekable) { GMemoryOutputStream *stream; GMemoryOutputStreamPrivate *priv; stream = G_MEMORY_OUTPUT_STREAM (seekable); priv = stream->priv; return priv->pos; } static gboolean g_memory_output_stream_can_seek (GSeekable *seekable) { return TRUE; } static gboolean g_memory_output_stream_seek (GSeekable *seekable, goffset offset, GSeekType type, GCancellable *cancellable, GError **error) { GMemoryOutputStream *stream; GMemoryOutputStreamPrivate *priv; goffset absolute; stream = G_MEMORY_OUTPUT_STREAM (seekable); priv = stream->priv; switch (type) { case G_SEEK_CUR: absolute = priv->pos + offset; break; case G_SEEK_SET: absolute = offset; break; case G_SEEK_END: /* For resizable streams, we consider the end to be the data * length. For fixed-sized streams, we consider the end to be the * size of the buffer. */ if (priv->realloc_fn) absolute = priv->valid_len + offset; else absolute = priv->len + offset; break; default: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Invalid GSeekType supplied")); return FALSE; } if (absolute < 0) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Requested seek before the beginning of the stream")); return FALSE; } /* Can't seek past the end of a fixed-size stream. * * Note: seeking to the non-existent byte at the end of a fixed-sized * stream is valid (eg: a 1-byte fixed sized stream can have position * 0 or 1). Therefore '>' is what we want. * */ if (priv->realloc_fn == NULL && (gsize) absolute > priv->len) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Requested seek beyond the end of the stream")); return FALSE; } priv->pos = absolute; return TRUE; } static gboolean g_memory_output_stream_can_truncate (GSeekable *seekable) { GMemoryOutputStream *ostream; GMemoryOutputStreamPrivate *priv; ostream = G_MEMORY_OUTPUT_STREAM (seekable); priv = ostream->priv; /* We do not allow truncation of fixed-sized streams */ return priv->realloc_fn != NULL; } static gboolean g_memory_output_stream_truncate (GSeekable *seekable, goffset offset, GCancellable *cancellable, GError **error) { GMemoryOutputStream *ostream = G_MEMORY_OUTPUT_STREAM (seekable); if (!array_resize (ostream, offset, FALSE, error)) return FALSE; ostream->priv->valid_len = offset; return TRUE; } static gboolean g_memory_output_stream_is_writable (GPollableOutputStream *stream) { return TRUE; } static GSource * g_memory_output_stream_create_source (GPollableOutputStream *stream, GCancellable *cancellable) { GSource *base_source, *pollable_source; base_source = g_timeout_source_new (0); pollable_source = g_pollable_source_new_full (stream, base_source, cancellable); g_source_unref (base_source); return pollable_source; }