Coverage Report

Created: 2026-10-28 06:45

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/glib/gio/gconverteroutputstream.c
Line
Count
Source
1
/* GIO - GLib Input, Output and Streaming Library
2
 *
3
 * Copyright (C) 2009 Red Hat, Inc.
4
 *
5
 * SPDX-License-Identifier: LGPL-2.1-or-later
6
 *
7
 * This library is free software; you can redistribute it and/or
8
 * modify it under the terms of the GNU Lesser General Public
9
 * License as published by the Free Software Foundation; either
10
 * version 2.1 of the License, or (at your option) any later version.
11
 *
12
 * This library is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15
 * Lesser General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Lesser General
18
 * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
19
 *
20
 * Author: Alexander Larsson <alexl@redhat.com>
21
 */
22
23
#include "config.h"
24
25
#include <string.h>
26
27
#include "gconverteroutputstream.h"
28
#include "gpollableoutputstream.h"
29
#include "gcancellable.h"
30
#include "gioenumtypes.h"
31
#include "gioerror.h"
32
#include "glibintl.h"
33
34
35
/**
36
 * GConverterOutputStream:
37
 *
38
 * Converter output stream implements [class@Gio.OutputStream] and allows
39
 * conversion of data of various types during reading.
40
 *
41
 * As of GLib 2.34, `GConverterOutputStream` implements
42
 * [iface@Gio.PollableOutputStream].
43
 */
44
45
0
#define INITIAL_BUFFER_SIZE 4096
46
47
typedef struct {
48
  char *data;
49
  gsize start;
50
  gsize end;
51
  gsize size;
52
} Buffer;
53
54
struct _GConverterOutputStreamPrivate {
55
  gboolean at_output_end;
56
  gboolean finished;
57
  GConverter *converter;
58
  Buffer output_buffer; /* To be converted and written */
59
  Buffer converted_buffer; /* Already converted */
60
};
61
62
/* Buffering strategy:
63
 *
64
 * Each time we write we must at least consume some input, or
65
 * return an error. Thus we start with writing all already
66
 * converted data and *then* we start converting (reporting
67
 * an error at any point in this).
68
 *
69
 * Its possible that what the user wrote is not enough data
70
 * for the converter, so we must then buffer it in output_buffer
71
 * and ask for more data, but we want to avoid this as much as
72
 * possible, converting directly from the users buffer.
73
 */
74
75
enum {
76
  PROP_0,
77
  PROP_CONVERTER
78
};
79
80
static void   g_converter_output_stream_set_property (GObject        *object,
81
                  guint           prop_id,
82
                  const GValue   *value,
83
                  GParamSpec     *pspec);
84
static void   g_converter_output_stream_get_property (GObject        *object,
85
                  guint           prop_id,
86
                  GValue         *value,
87
                  GParamSpec     *pspec);
88
static void   g_converter_output_stream_finalize     (GObject        *object);
89
static gssize g_converter_output_stream_write        (GOutputStream  *stream,
90
                  const void     *buffer,
91
                  gsize           count,
92
                  GCancellable   *cancellable,
93
                  GError        **error);
94
static gboolean g_converter_output_stream_flush      (GOutputStream  *stream,
95
                  GCancellable   *cancellable,
96
                  GError        **error);
97
98
static gboolean g_converter_output_stream_can_poll          (GPollableOutputStream *stream);
99
static gboolean g_converter_output_stream_is_writable       (GPollableOutputStream *stream);
100
static gssize   g_converter_output_stream_write_nonblocking (GPollableOutputStream  *stream,
101
                   const void             *buffer,
102
                   gsize                  size,
103
                   GError               **error);
104
105
static GSource *g_converter_output_stream_create_source     (GPollableOutputStream *stream,
106
                   GCancellable          *cancellable);
107
108
static void g_converter_output_stream_pollable_iface_init (GPollableOutputStreamInterface *iface);
109
110
0
G_DEFINE_TYPE_WITH_CODE (GConverterOutputStream,
111
0
       g_converter_output_stream,
112
0
       G_TYPE_FILTER_OUTPUT_STREAM,
113
0
                         G_ADD_PRIVATE (GConverterOutputStream)
114
0
       G_IMPLEMENT_INTERFACE (G_TYPE_POLLABLE_OUTPUT_STREAM,
115
0
            g_converter_output_stream_pollable_iface_init))
116
0
117
0
static void
118
0
g_converter_output_stream_class_init (GConverterOutputStreamClass *klass)
119
0
{
120
0
  GObjectClass *object_class;
121
0
  GOutputStreamClass *istream_class;
122
123
0
  object_class = G_OBJECT_CLASS (klass);
124
0
  object_class->get_property = g_converter_output_stream_get_property;
125
0
  object_class->set_property = g_converter_output_stream_set_property;
126
0
  object_class->finalize     = g_converter_output_stream_finalize;
127
128
0
  istream_class = G_OUTPUT_STREAM_CLASS (klass);
129
0
  istream_class->write_fn = g_converter_output_stream_write;
130
0
  istream_class->flush = g_converter_output_stream_flush;
131
132
  /**
133
   * GConverterOutputStream:converter:
134
   *
135
   * The converter object.
136
   */
137
0
  g_object_class_install_property (object_class,
138
0
           PROP_CONVERTER,
139
0
           g_param_spec_object ("converter", NULL, NULL,
140
0
              G_TYPE_CONVERTER,
141
0
              G_PARAM_READWRITE|
142
0
              G_PARAM_CONSTRUCT_ONLY|
143
0
              G_PARAM_STATIC_STRINGS));
144
145
0
}
146
147
static void
148
g_converter_output_stream_pollable_iface_init (GPollableOutputStreamInterface *iface)
149
0
{
150
0
  iface->can_poll = g_converter_output_stream_can_poll;
151
0
  iface->is_writable = g_converter_output_stream_is_writable;
152
0
  iface->write_nonblocking = g_converter_output_stream_write_nonblocking;
153
0
  iface->create_source = g_converter_output_stream_create_source;
154
0
}
155
156
static void
157
g_converter_output_stream_finalize (GObject *object)
158
0
{
159
0
  GConverterOutputStreamPrivate *priv;
160
0
  GConverterOutputStream        *stream;
161
162
0
  stream = G_CONVERTER_OUTPUT_STREAM (object);
163
0
  priv = stream->priv;
164
165
0
  g_free (priv->output_buffer.data);
166
0
  g_free (priv->converted_buffer.data);
167
0
  if (priv->converter)
168
0
    g_object_unref (priv->converter);
169
170
0
  G_OBJECT_CLASS (g_converter_output_stream_parent_class)->finalize (object);
171
0
}
172
173
static void
174
g_converter_output_stream_set_property (GObject      *object,
175
               guint         prop_id,
176
               const GValue *value,
177
               GParamSpec   *pspec)
178
0
{
179
0
  GConverterOutputStream *cstream;
180
181
0
  cstream = G_CONVERTER_OUTPUT_STREAM (object);
182
183
0
   switch (prop_id)
184
0
    {
185
0
    case PROP_CONVERTER:
186
0
      cstream->priv->converter = g_value_dup_object (value);
187
0
      break;
188
189
0
    default:
190
0
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
191
0
      break;
192
0
    }
193
194
0
}
195
196
static void
197
g_converter_output_stream_get_property (GObject    *object,
198
               guint       prop_id,
199
               GValue     *value,
200
               GParamSpec *pspec)
201
0
{
202
0
  GConverterOutputStreamPrivate *priv;
203
0
  GConverterOutputStream        *cstream;
204
205
0
  cstream = G_CONVERTER_OUTPUT_STREAM (object);
206
0
  priv = cstream->priv;
207
208
0
  switch (prop_id)
209
0
    {
210
0
    case PROP_CONVERTER:
211
0
      g_value_set_object (value, priv->converter);
212
0
      break;
213
214
0
    default:
215
0
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
216
0
      break;
217
0
    }
218
0
}
219
220
static void
221
g_converter_output_stream_init (GConverterOutputStream *stream)
222
0
{
223
0
  stream->priv = g_converter_output_stream_get_instance_private (stream);
224
0
}
225
226
/**
227
 * g_converter_output_stream_new:
228
 * @base_stream: a #GOutputStream
229
 * @converter: a #GConverter
230
 *
231
 * Creates a new converter output stream for the @base_stream.
232
 *
233
 * Returns: a new #GOutputStream.
234
 **/
235
GOutputStream *
236
g_converter_output_stream_new (GOutputStream *base_stream,
237
                               GConverter    *converter)
238
0
{
239
0
  GOutputStream *stream;
240
241
0
  g_return_val_if_fail (G_IS_OUTPUT_STREAM (base_stream), NULL);
242
243
0
  stream = g_object_new (G_TYPE_CONVERTER_OUTPUT_STREAM,
244
0
                         "base-stream", base_stream,
245
0
       "converter", converter,
246
0
       NULL);
247
248
0
  return stream;
249
0
}
250
251
static gsize
252
buffer_data_size (Buffer *buffer)
253
0
{
254
0
  return buffer->end - buffer->start;
255
0
}
256
257
static gsize
258
buffer_tailspace (Buffer *buffer)
259
0
{
260
0
  return buffer->size - buffer->end;
261
0
}
262
263
static char *
264
buffer_data (Buffer *buffer)
265
0
{
266
  /* The buffer is grown on demand, so @data is NULL until something has been
267
   * put in it. Offsetting it would be undefined behavior, so callers must
268
   * check buffer_data_size() first. */
269
0
  g_assert (buffer->data != NULL);
270
271
0
  return buffer->data + buffer->start;
272
0
}
273
274
static void
275
buffer_consumed (Buffer *buffer,
276
     gsize count)
277
0
{
278
0
  buffer->start += count;
279
0
  if (buffer->start == buffer->end)
280
0
    buffer->start = buffer->end = 0;
281
0
}
282
283
static void
284
compact_buffer (Buffer *buffer)
285
0
{
286
0
  gsize in_buffer;
287
288
0
  in_buffer = buffer_data_size (buffer);
289
0
  memmove (buffer->data,
290
0
     buffer->data + buffer->start,
291
0
     in_buffer);
292
0
  buffer->end -= buffer->start;
293
0
  buffer->start = 0;
294
0
}
295
296
static void
297
grow_buffer (Buffer *buffer)
298
0
{
299
0
  char *data;
300
0
  gsize size, in_buffer;
301
302
0
  if (buffer->size == 0)
303
0
    size = INITIAL_BUFFER_SIZE;
304
0
  else
305
0
    size = buffer->size * 2;
306
307
0
  data = g_malloc (size);
308
0
  in_buffer = buffer_data_size (buffer);
309
310
0
  if (in_buffer != 0)
311
0
    memcpy (data,
312
0
            buffer->data + buffer->start,
313
0
            in_buffer);
314
315
0
  g_free (buffer->data);
316
0
  buffer->data = data;
317
0
  buffer->end -= buffer->start;
318
0
  buffer->start = 0;
319
0
  buffer->size = size;
320
0
}
321
322
/* Ensures that the buffer can fit at_least_size bytes,
323
 * *including* the current in-buffer data */
324
static void
325
buffer_ensure_space (Buffer *buffer,
326
         gsize at_least_size)
327
0
{
328
0
  gsize in_buffer, left_to_fill;
329
330
0
  in_buffer = buffer_data_size (buffer);
331
332
0
  if (in_buffer >= at_least_size)
333
0
    return;
334
335
0
  left_to_fill = buffer_tailspace (buffer);
336
337
0
  if (in_buffer + left_to_fill >= at_least_size)
338
0
    {
339
      /* We fit in remaining space at end */
340
      /* If the copy is small, compact now anyway so we can fill more */
341
0
      if (in_buffer < 256)
342
0
  compact_buffer (buffer);
343
0
    }
344
0
  else if (buffer->size >= at_least_size)
345
0
    {
346
      /* We fit, but only if we compact */
347
0
      compact_buffer (buffer);
348
0
    }
349
0
  else
350
0
    {
351
      /* Need to grow buffer */
352
0
      while (buffer->size < at_least_size)
353
0
  grow_buffer (buffer);
354
0
    }
355
0
}
356
357
static void
358
buffer_append (Buffer *buffer,
359
         const char *data,
360
         gsize data_size)
361
0
{
362
0
  buffer_ensure_space (buffer,
363
0
           buffer_data_size (buffer) + data_size);
364
0
  memcpy (buffer->data + buffer->end, data, data_size);
365
0
  buffer->end += data_size;
366
0
}
367
368
369
static gboolean
370
flush_buffer (GConverterOutputStream *stream,
371
        gboolean                blocking,
372
        GCancellable           *cancellable,
373
        GError                **error)
374
0
{
375
0
  GConverterOutputStreamPrivate *priv;
376
0
  GOutputStream *base_stream;
377
0
  gsize nwritten;
378
0
  gsize available;
379
0
  gboolean res;
380
381
0
  priv = stream->priv;
382
383
0
  base_stream = G_FILTER_OUTPUT_STREAM (stream)->base_stream;
384
385
0
  available = buffer_data_size (&priv->converted_buffer);
386
0
  if (available > 0)
387
0
    {
388
0
      res = g_pollable_stream_write_all (base_stream,
389
0
           buffer_data (&priv->converted_buffer),
390
0
           available,
391
0
           blocking,
392
0
           &nwritten,
393
0
           cancellable,
394
0
           error);
395
0
      buffer_consumed (&priv->converted_buffer, nwritten);
396
0
      return res;
397
0
    }
398
0
  return TRUE;
399
0
}
400
401
402
static gssize
403
write_internal (GOutputStream  *stream,
404
    const void     *buffer,
405
    gsize           count,
406
    gboolean        blocking,
407
    GCancellable   *cancellable,
408
    GError        **error)
409
0
{
410
0
  GConverterOutputStream *cstream;
411
0
  GConverterOutputStreamPrivate *priv;
412
0
  gssize retval;
413
0
  GConverterResult res;
414
0
  gsize bytes_read;
415
0
  gsize bytes_written;
416
0
  GError *my_error;
417
0
  const char *to_convert;
418
0
  gsize to_convert_size, converted_bytes;
419
0
  gboolean converting_from_buffer;
420
421
0
  cstream = G_CONVERTER_OUTPUT_STREAM (stream);
422
0
  priv = cstream->priv;
423
424
  /* Write out all available pre-converted data and fail if
425
     not possible */
426
0
  if (!flush_buffer (cstream, blocking, cancellable, error))
427
0
    return -1;
428
429
0
  if (priv->finished)
430
0
    {
431
0
      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_MESSAGE_TOO_LARGE,
432
0
                           _("Unexpected data after end of conversion"));
433
0
      return -1;
434
0
    }
435
436
  /* Convert as much as possible */
437
0
  if (buffer_data_size (&priv->output_buffer) > 0)
438
0
    {
439
0
      converting_from_buffer = TRUE;
440
0
      buffer_append (&priv->output_buffer, buffer, count);
441
0
      to_convert = buffer_data (&priv->output_buffer);
442
0
      to_convert_size = buffer_data_size (&priv->output_buffer);
443
0
    }
444
0
  else
445
0
    {
446
0
      converting_from_buffer = FALSE;
447
0
      to_convert = buffer;
448
0
      to_convert_size = count;
449
0
    }
450
451
  /* Ensure we have *some* initial target space */
452
0
  buffer_ensure_space (&priv->converted_buffer, to_convert_size);
453
454
0
  converted_bytes = 0;
455
0
  while (!priv->finished && converted_bytes < to_convert_size)
456
0
    {
457
      /* Ensure we have *some* target space */
458
0
      if (buffer_tailspace (&priv->converted_buffer) == 0)
459
0
  grow_buffer (&priv->converted_buffer);
460
461
      /* Try to convert to our buffer */
462
0
      my_error = NULL;
463
0
      res = g_converter_convert (priv->converter,
464
0
         to_convert + converted_bytes,
465
0
         to_convert_size - converted_bytes,
466
0
         buffer_data (&priv->converted_buffer) + buffer_data_size (&priv->converted_buffer),
467
0
         buffer_tailspace (&priv->converted_buffer),
468
0
         0,
469
0
         &bytes_read,
470
0
         &bytes_written,
471
0
         &my_error);
472
473
0
      if (res != G_CONVERTER_ERROR)
474
0
  {
475
0
    priv->converted_buffer.end += bytes_written;
476
0
    converted_bytes += bytes_read;
477
478
0
    if (res == G_CONVERTER_FINISHED)
479
0
      priv->finished = TRUE;
480
0
  }
481
0
      else
482
0
  {
483
    /* No-space errors can be handled locally: */
484
0
    if (g_error_matches (my_error,
485
0
             G_IO_ERROR,
486
0
             G_IO_ERROR_NO_SPACE))
487
0
      {
488
        /* Need more destination space, grow it
489
         * Note: if we actually grow the buffer (as opposed to compacting it),
490
         * this will double the size, not just add one byte. */
491
0
        buffer_ensure_space (&priv->converted_buffer,
492
0
           priv->converted_buffer.size + 1);
493
0
        g_error_free (my_error);
494
0
        continue;
495
0
      }
496
497
0
    if (converted_bytes > 0)
498
0
      {
499
        /* We got a conversion error, but we did convert some bytes before
500
     that, so handle those before reporting the error */
501
0
        g_error_free (my_error);
502
0
        break;
503
0
      }
504
505
0
    if (g_error_matches (my_error,
506
0
             G_IO_ERROR,
507
0
             G_IO_ERROR_PARTIAL_INPUT))
508
0
      {
509
        /* Consume everything to buffer that we append to next time
510
     we write */
511
0
        if (!converting_from_buffer)
512
0
    buffer_append (&priv->output_buffer, buffer, count);
513
        /* in the converting_from_buffer case we already appended this */
514
515
0
              g_error_free (my_error);
516
0
        return count; /* consume everything */
517
0
      }
518
519
    /* Converted no data and got a normal error, return it */
520
0
    g_propagate_error (error, my_error);
521
0
    return -1;
522
0
  }
523
0
    }
524
525
0
  if (converting_from_buffer)
526
0
    {
527
0
      buffer_consumed (&priv->output_buffer, converted_bytes);
528
0
      retval = count;
529
0
    }
530
0
  else
531
0
    retval = converted_bytes;
532
533
  /* We now successfully consumed retval bytes, so we can't return an error,
534
     even if writing this to the base stream fails. If it does we'll just
535
     stop early and report this error when we try again on the next
536
     write call. */
537
0
  flush_buffer (cstream, blocking, cancellable, NULL);
538
539
0
  return retval;
540
0
}
541
542
static gssize
543
g_converter_output_stream_write (GOutputStream  *stream,
544
         const void     *buffer,
545
         gsize           count,
546
         GCancellable   *cancellable,
547
         GError        **error)
548
0
{
549
0
  return write_internal (stream, buffer, count, TRUE, cancellable, error);
550
0
}
551
552
static gboolean
553
g_converter_output_stream_flush (GOutputStream  *stream,
554
         GCancellable   *cancellable,
555
         GError        **error)
556
0
{
557
0
  GConverterOutputStream *cstream;
558
0
  GConverterOutputStreamPrivate *priv;
559
0
  GConverterResult res;
560
0
  GError *my_error;
561
0
  gboolean is_closing;
562
0
  gboolean flushed;
563
0
  gsize bytes_read;
564
0
  gsize bytes_written;
565
0
  gsize to_convert_size;
566
567
0
  cstream = G_CONVERTER_OUTPUT_STREAM (stream);
568
0
  priv = cstream->priv;
569
570
0
  is_closing = g_output_stream_is_closing (stream);
571
572
  /* Write out all available pre-converted data and fail if
573
     not possible */
574
0
  if (!flush_buffer (cstream, TRUE, cancellable, error))
575
0
    return FALSE;
576
577
  /* Ensure we have *some* initial target space */
578
0
  buffer_ensure_space (&priv->converted_buffer, 1);
579
580
  /* Convert whole buffer */
581
0
  flushed = FALSE;
582
0
  while (!priv->finished && !flushed)
583
0
    {
584
      /* Ensure we have *some* target space */
585
0
      if (buffer_tailspace (&priv->converted_buffer) == 0)
586
0
  grow_buffer (&priv->converted_buffer);
587
588
      /* Try to convert to our buffer */
589
0
      to_convert_size = buffer_data_size (&priv->output_buffer);
590
0
      my_error = NULL;
591
0
      res = g_converter_convert (priv->converter,
592
0
         to_convert_size > 0 ? buffer_data (&priv->output_buffer) : NULL,
593
0
         to_convert_size,
594
0
         buffer_data (&priv->converted_buffer) + buffer_data_size (&priv->converted_buffer),
595
0
         buffer_tailspace (&priv->converted_buffer),
596
0
         is_closing ? G_CONVERTER_INPUT_AT_END : G_CONVERTER_FLUSH,
597
0
         &bytes_read,
598
0
         &bytes_written,
599
0
         &my_error);
600
601
0
      if (res != G_CONVERTER_ERROR)
602
0
  {
603
0
    priv->converted_buffer.end += bytes_written;
604
0
    buffer_consumed (&priv->output_buffer, bytes_read);
605
606
0
    if (res == G_CONVERTER_FINISHED)
607
0
      priv->finished = TRUE;
608
0
    if (!is_closing &&
609
0
        res == G_CONVERTER_FLUSHED)
610
0
      {
611
        /* Should not have returned FLUSHED with input left */
612
0
        g_assert (buffer_data_size (&priv->output_buffer) == 0);
613
0
        flushed = TRUE;
614
0
      }
615
0
  }
616
0
      else
617
0
  {
618
    /* No-space errors can be handled locally: */
619
0
    if (g_error_matches (my_error,
620
0
             G_IO_ERROR,
621
0
             G_IO_ERROR_NO_SPACE))
622
0
      {
623
        /* Need more destination space, grow it
624
         * Note: if we actually grow the buffer (as opposed to compacting it),
625
         * this will double the size, not just add one byte. */
626
0
        buffer_ensure_space (&priv->converted_buffer,
627
0
           priv->converted_buffer.size + 1);
628
0
        g_error_free (my_error);
629
0
        continue;
630
0
      }
631
632
    /* Any other error, including PARTIAL_INPUT can't be fixed by now
633
       and is an error */
634
0
    g_propagate_error (error, my_error);
635
0
    return FALSE;
636
0
  }
637
0
    }
638
639
  /* Now write all converted data to base stream */
640
0
  if (!flush_buffer (cstream, TRUE, cancellable, error))
641
0
    return FALSE;
642
643
0
  return TRUE;
644
0
}
645
646
static gboolean
647
g_converter_output_stream_can_poll (GPollableOutputStream *stream)
648
0
{
649
0
  GOutputStream *base_stream = G_FILTER_OUTPUT_STREAM (stream)->base_stream;
650
651
0
  return (G_IS_POLLABLE_OUTPUT_STREAM (base_stream) &&
652
0
    g_pollable_output_stream_can_poll (G_POLLABLE_OUTPUT_STREAM (base_stream)));
653
0
}
654
655
static gboolean
656
g_converter_output_stream_is_writable (GPollableOutputStream *stream)
657
0
{
658
0
  GOutputStream *base_stream = G_FILTER_OUTPUT_STREAM (stream)->base_stream;
659
660
0
  return g_pollable_output_stream_is_writable (G_POLLABLE_OUTPUT_STREAM (base_stream));
661
0
}
662
663
static gssize
664
g_converter_output_stream_write_nonblocking (GPollableOutputStream  *stream,
665
               const void             *buffer,
666
               gsize                   count,
667
               GError                **error)
668
0
{
669
0
  return write_internal (G_OUTPUT_STREAM (stream), buffer, count, FALSE,
670
0
       NULL, error);
671
0
}
672
673
static GSource *
674
g_converter_output_stream_create_source (GPollableOutputStream *stream,
675
           GCancellable          *cancellable)
676
0
{
677
0
  GOutputStream *base_stream = G_FILTER_OUTPUT_STREAM (stream)->base_stream;
678
0
  GSource *base_source, *pollable_source;
679
680
0
  base_source = g_pollable_output_stream_create_source (G_POLLABLE_OUTPUT_STREAM (base_stream), NULL);
681
0
  pollable_source = g_pollable_source_new_full (stream, base_source,
682
0
            cancellable);
683
0
  g_source_unref (base_source);
684
685
0
  return pollable_source;
686
0
}
687
688
/**
689
 * g_converter_output_stream_get_converter:
690
 * @converter_stream: a #GConverterOutputStream
691
 *
692
 * Gets the #GConverter that is used by @converter_stream.
693
 *
694
 * Returns: (transfer none): the converter of the converter output stream
695
 *
696
 * Since: 2.24
697
 */
698
GConverter *
699
g_converter_output_stream_get_converter (GConverterOutputStream *converter_stream)
700
0
{
701
0
  return converter_stream->priv->converter;
702
0
}