1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * gsf-input.c: interface for used by the ole layer to read raw data
4 *
5 * Copyright (C) 2002-2006 Jody Goldberg (jody@gnome.org)
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of version 2.1 of the GNU Lesser General Public
9 * License as published by the Free Software Foundation.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
19 * USA
20 */
21
22 #include <gsf-config.h>
23 #include <gsf/gsf-input.h>
24 #include <gsf/gsf.h>
25 #include <glib/gi18n-lib.h>
26
27 #include <string.h>
28
29
30 /*
31 * FIXME!
32 *
33 * We cannot extend GsfInput, so for now we hang this on the object as
34 * an attribute.
35 */
36 #define MODTIME_ATTR "GsfInput::modtime"
37
38
39
40 #define GET_CLASS(instance) G_TYPE_INSTANCE_GET_CLASS (instance, GSF_INPUT_TYPE, GsfInputClass)
41
42 static GObjectClass *parent_class;
43
44 enum {
45 PROP_0,
46 PROP_NAME,
47 PROP_SIZE,
48 PROP_EOF,
49 PROP_REMAINING,
50 PROP_POS,
51 PROP_MODTIME,
52 PROP_CONTAINER
53 };
54
55 static void
gsf_input_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)56 gsf_input_get_property (GObject *object,
57 guint property_id,
58 GValue *value,
59 GParamSpec *pspec)
60 {
61 GsfInput *input = GSF_INPUT (object);
62
63 /* gsf_off_t is typedef'd to gint64 */
64
65 switch (property_id) {
66 case PROP_NAME:
67 g_value_set_string (value, gsf_input_name (input));
68 break;
69 case PROP_SIZE:
70 g_value_set_int64 (value, gsf_input_size (input));
71 break;
72 case PROP_EOF:
73 g_value_set_boolean (value, gsf_input_eof (input));
74 break;
75 case PROP_REMAINING:
76 g_value_set_int64 (value, gsf_input_remaining (input));
77 break;
78 case PROP_POS:
79 g_value_set_int64 (value, gsf_input_tell (input));
80 break;
81 case PROP_MODTIME:
82 g_value_set_boxed (value, gsf_input_get_modtime (input));
83 break;
84 case PROP_CONTAINER:
85 g_value_set_object (value, input->container);
86 break;
87 default:
88 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
89 break;
90 }
91 }
92
93 static void
gsf_input_dispose(GObject * obj)94 gsf_input_dispose (GObject *obj)
95 {
96 GsfInput *input = GSF_INPUT (obj);
97
98 gsf_input_set_container (input, NULL);
99 gsf_input_set_name (input, NULL);
100 gsf_input_set_modtime (input, NULL);
101
102 parent_class->dispose (obj);
103 }
104
105 static void
gsf_input_init(GObject * obj)106 gsf_input_init (GObject *obj)
107 {
108 GsfInput *input = GSF_INPUT (obj);
109
110 input->size = 0;
111 input->cur_offset = 0;
112 input->name = NULL;
113 input->container = NULL;
114 }
115
116 static void
gsf_input_class_init(GObjectClass * gobject_class)117 gsf_input_class_init (GObjectClass *gobject_class)
118 {
119 parent_class = g_type_class_peek_parent (gobject_class);
120
121 gobject_class->dispose = gsf_input_dispose;
122 gobject_class->get_property = gsf_input_get_property;
123
124 g_object_class_install_property
125 (gobject_class,
126 PROP_NAME,
127 g_param_spec_string ("name",
128 _("Name"),
129 _("The input's name"),
130 NULL,
131 G_PARAM_STATIC_STRINGS |
132 G_PARAM_READABLE));
133
134 /**
135 * GsfInput:size:
136 *
137 * The total number of bytes in the file.
138 */
139 g_object_class_install_property
140 (gobject_class,
141 PROP_SIZE,
142 g_param_spec_int64 ("size",
143 _("Size"),
144 _("The input's size"),
145 0, G_MAXINT64, 0,
146 G_PARAM_STATIC_STRINGS |
147 G_PARAM_READABLE));
148
149 /**
150 * GsfInput:eof:
151 *
152 * %TRUE if the end of the file has been reached.
153 */
154 g_object_class_install_property
155 (gobject_class,
156 PROP_EOF,
157 g_param_spec_boolean ("eof",
158 _("EOF"),
159 _("End of file"),
160 FALSE,
161 G_PARAM_STATIC_STRINGS |
162 G_PARAM_READABLE));
163
164 /**
165 * GsfInput:remaining:
166 *
167 * The number of bytes remaining in the file.
168 */
169 g_object_class_install_property
170 (gobject_class,
171 PROP_REMAINING,
172 g_param_spec_int64 ("remaining",
173 _("Remaining"),
174 _("Amount of data remaining"),
175 0, G_MAXINT64, 0,
176 G_PARAM_STATIC_STRINGS |
177 G_PARAM_READABLE));
178
179 /**
180 * GsfInput:position:
181 *
182 * The current position in the input.
183 */
184 g_object_class_install_property
185 (gobject_class,
186 PROP_POS,
187 g_param_spec_int64 ("position",
188 _("Position"),
189 _("The input's current position"),
190 0, G_MAXINT64, 0,
191 G_PARAM_STATIC_STRINGS |
192 G_PARAM_READABLE));
193
194 /**
195 * GsfInput:modtime:
196 *
197 * The time the input was last updated. This represents the
198 * timestamp from the originating file or @GsfInfile member.
199 * It is not supported by all derived classes.
200 */
201 g_object_class_install_property
202 (gobject_class,
203 PROP_MODTIME,
204 g_param_spec_boxed
205 ("modtime",
206 _("Modification time"),
207 _("An optional GDateTime representing the time the input was last changed"),
208 G_TYPE_DATE_TIME,
209 G_PARAM_STATIC_STRINGS |
210 G_PARAM_READABLE));
211
212 /**
213 * GsfInput:container:
214 *
215 * The container, optionally %NULL, in which this input lives.
216 */
217 g_object_class_install_property
218 (gobject_class, PROP_CONTAINER,
219 g_param_spec_object ("container",
220 _("Container"),
221 _("The parent GsfInfile"),
222 GSF_INFILE_TYPE,
223 G_PARAM_STATIC_STRINGS |
224 G_PARAM_READABLE));
225 }
226
GSF_CLASS_ABSTRACT(GsfInput,gsf_input,gsf_input_class_init,gsf_input_init,G_TYPE_OBJECT)227 GSF_CLASS_ABSTRACT (GsfInput, gsf_input,
228 gsf_input_class_init, gsf_input_init,
229 G_TYPE_OBJECT)
230
231 /**
232 * gsf_input_name:
233 * @input: the input stream
234 *
235 * The name of the input stream.
236 *
237 * Returns: (transfer none): @input's name in utf8 form, or %NULL if it
238 * has no name.
239 **/
240 char const *
241 gsf_input_name (GsfInput *input)
242 {
243 g_return_val_if_fail (GSF_IS_INPUT (input), NULL);
244 return input->name;
245 }
246
247 /**
248 * gsf_input_container:
249 * @input: the input stream
250 *
251 * Returns: (transfer none) (nullable): @input's container
252 **/
253 GsfInfile *
gsf_input_container(GsfInput * input)254 gsf_input_container (GsfInput *input)
255 {
256 g_return_val_if_fail (GSF_IS_INPUT (input), NULL);
257 return input->container;
258 }
259
260 /**
261 * gsf_input_dup: (virtual Dup)
262 * @input: The input to duplicate
263 * @err: (allow-none): place to store a #GError if anything goes wrong
264 *
265 * Duplicates @input leaving the new one at the same offset.
266 *
267 * Returns: (transfer full) (nullable): the duplicate
268 **/
269 GsfInput *
gsf_input_dup(GsfInput * input,GError ** err)270 gsf_input_dup (GsfInput *input, GError **err)
271 {
272 GsfInput *dst;
273
274 g_return_val_if_fail (input != NULL, NULL);
275
276 dst = GET_CLASS (input)->Dup (input, err);
277 if (dst != NULL) {
278 if (dst->size != input->size) {
279 if (err != NULL)
280 *err = g_error_new (gsf_input_error_id (), 0,
281 _("Duplicate size mismatch"));
282 g_object_unref (dst);
283 return NULL;
284 }
285 if (gsf_input_seek (dst, input->cur_offset, G_SEEK_SET)) {
286 if (err != NULL)
287 *err = g_error_new (gsf_input_error_id (), 0,
288 _("Seek failed"));
289 g_object_unref (dst);
290 return NULL;
291 }
292
293 gsf_input_set_name (dst, input->name);
294 gsf_input_set_container (dst, input->container);
295 }
296 return dst;
297 }
298
299 /**
300 * gsf_input_sibling: (virtual OpenSibling)
301 * @input: The input
302 * @name: name.
303 * @err: (allow-none): place to store a #GError if anything goes wrong
304 *
305 * UNIMPLEMENTED BY ANY BACKEND
306 * and it is probably unnecessary. gsf_input_get_container provides
307 * enough power to do what is necessary.
308 *
309 * Attempts to open a 'sibling' of @input. The caller is responsible for
310 * managing the resulting object.
311 *
312 * Returns: (transfer full): A related #GsfInput
313 **/
314 GsfInput *
gsf_input_sibling(GsfInput const * input,char const * name,GError ** err)315 gsf_input_sibling (GsfInput const *input, char const *name, GError **err)
316 {
317 g_return_val_if_fail (GET_CLASS (input)->OpenSibling, NULL);
318
319 return GET_CLASS (input)->OpenSibling (input, name, err);
320 }
321
322 /**
323 * gsf_input_size:
324 * @input: The input
325 *
326 * Returns: the total number of bytes in the input or -1 on error
327 **/
328 gsf_off_t
gsf_input_size(GsfInput * input)329 gsf_input_size (GsfInput *input)
330 {
331 g_return_val_if_fail (input != NULL, -1);
332 return input->size;
333 }
334
335 /**
336 * gsf_input_eof:
337 * @input: the input
338 *
339 * Are we at the end of the file?
340 *
341 * Returns: %TRUE if the input is at the eof.
342 **/
343 gboolean
gsf_input_eof(GsfInput * input)344 gsf_input_eof (GsfInput *input)
345 {
346 g_return_val_if_fail (input != NULL, FALSE);
347
348 return input->cur_offset >= input->size;
349 }
350
351 /**
352 * gsf_input_read: (virtual Read) (skip)
353 * @input: the input stream
354 * @num_bytes: number of bytes to read
355 * @optional_buffer: (array) (allow-none): Pointer to destination memory area
356 *
357 * Read at least @num_bytes. Does not change the current position if there
358 * is an error. Will only read if the entire amount can be read. Invalidates
359 * the buffer associated with previous calls to gsf_input_read.
360 *
361 * Returns: (array) (nullable): pointer to the buffer or %NULL if there is
362 * an error or 0 bytes are requested.
363 **/
364
365 guint8 const *
gsf_input_read(GsfInput * input,size_t num_bytes,guint8 * optional_buffer)366 gsf_input_read (GsfInput *input, size_t num_bytes, guint8 *optional_buffer)
367 {
368 guint8 const *res;
369 gsf_off_t newpos = input->cur_offset + num_bytes;
370
371 g_return_val_if_fail (input != NULL, NULL);
372
373 if (newpos <= input->cur_offset || newpos > input->size)
374 return NULL;
375 res = GET_CLASS (input)->Read (input, num_bytes, optional_buffer);
376 if (res == NULL)
377 return NULL;
378
379 input->cur_offset = newpos;
380 return res;
381 }
382
383 /**
384 * gsf_input_read0: (rename-to gsf_input_read)
385 * @input: the input stream
386 * @num_bytes: (in): number of bytes to read
387 * @bytes_read: (out): copy of @num_bytes
388 *
389 * Read @num_bytes. Does not change the current position if there
390 * is an error. Will only read if the entire amount can be read.
391 *
392 * Returns: (array length=bytes_read) (element-type guint8) (transfer full):
393 * the data read.
394 **/
395
396 guint8 *
gsf_input_read0(GsfInput * input,size_t num_bytes,size_t * bytes_read)397 gsf_input_read0 (GsfInput *input, size_t num_bytes, size_t *bytes_read)
398 {
399 guint8 *res;
400
401 g_return_val_if_fail (input != NULL, NULL);
402 g_return_val_if_fail (bytes_read != NULL, NULL);
403
404 *bytes_read = num_bytes;
405
406 if (num_bytes < 0 || (gsf_off_t)num_bytes > gsf_input_remaining (input))
407 return NULL;
408
409 res = g_new (guint8, num_bytes);
410 if (gsf_input_read (input, num_bytes, res))
411 return res;
412
413 g_free (res);
414 return NULL;
415 }
416
417 /**
418 * gsf_input_remaining:
419 * @input: the input stream
420 *
421 * Returns: the number of bytes left in the file.
422 **/
423 gsf_off_t
gsf_input_remaining(GsfInput * input)424 gsf_input_remaining (GsfInput *input)
425 {
426 g_return_val_if_fail (input != NULL, 0);
427
428 return input->size - input->cur_offset;
429 }
430
431 /**
432 * gsf_input_tell:
433 * @input: the input stream
434 *
435 * Returns: the current offset in the file.
436 **/
437 gsf_off_t
gsf_input_tell(GsfInput * input)438 gsf_input_tell (GsfInput *input)
439 {
440 g_return_val_if_fail (input != NULL, 0);
441
442 return input->cur_offset;
443 }
444
445 /**
446 * gsf_input_seek: (virtual Seek)
447 * @input: the input stream
448 * @offset: target offset
449 * @whence: determines whether the offset is relative to the beginning or
450 * the end of the stream, or to the current location.
451 *
452 * Move the current location in the input stream.
453 *
454 * Returns: %TRUE on error.
455 **/
456 gboolean
gsf_input_seek(GsfInput * input,gsf_off_t offset,GSeekType whence)457 gsf_input_seek (GsfInput *input, gsf_off_t offset, GSeekType whence)
458 {
459 gsf_off_t pos = offset;
460
461 g_return_val_if_fail (input != NULL, TRUE);
462
463 switch (whence) {
464 case G_SEEK_SET : break;
465 case G_SEEK_CUR : pos += input->cur_offset; break;
466 case G_SEEK_END : pos += input->size; break;
467 default : return TRUE;
468 }
469
470 if (pos < 0 || pos > input->size)
471 return TRUE;
472
473 /*
474 * If we go nowhere, just return. This in particular handles null
475 * seeks for streams with no seek method.
476 */
477 if (pos == input->cur_offset)
478 return FALSE;
479
480 if (GET_CLASS (input)->Seek (input, offset, whence))
481 return TRUE;
482
483 input->cur_offset = pos;
484 return FALSE;
485 }
486
487 /**
488 * gsf_input_set_name:
489 * @input: the input stream
490 * @name: (allow-none): the new name of the stream
491 *
492 * protected.
493 *
494 * Returns: %TRUE if the assignment was ok.
495 **/
496 gboolean
gsf_input_set_name(GsfInput * input,char const * name)497 gsf_input_set_name (GsfInput *input, char const *name)
498 {
499 g_return_val_if_fail (input != NULL, FALSE);
500
501 if (g_strcmp0 (name, input->name)) {
502 g_free (input->name);
503 input->name = g_strdup (name);
504 g_object_notify (G_OBJECT (input), "name");
505 }
506
507 return TRUE;
508 }
509
510 /**
511 * gsf_input_set_name_from_filename:
512 * @input: the input stream
513 * @filename: the (fs-sys encoded) filename
514 *
515 * protected.
516 *
517 * Returns: %TRUE if the assignment was ok.
518 **/
519 gboolean
gsf_input_set_name_from_filename(GsfInput * input,char const * filename)520 gsf_input_set_name_from_filename (GsfInput *input, char const *filename)
521 {
522 g_return_val_if_fail (input != NULL, FALSE);
523
524 g_free (input->name);
525 input->name = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL);
526 return TRUE;
527 }
528
529
530 /**
531 * gsf_input_set_container:
532 * @input: the input stream
533 * @container: (allow-none)
534 *
535 * Returns: %TRUE if the assignment was ok.
536 */
537 gboolean
gsf_input_set_container(GsfInput * input,GsfInfile * container)538 gsf_input_set_container (GsfInput *input, GsfInfile *container)
539 {
540 g_return_val_if_fail (input != NULL, FALSE);
541
542 if (container != NULL)
543 g_object_ref (container);
544 if (input->container != NULL)
545 g_object_unref (input->container);
546 input->container = container;
547 return TRUE;
548 }
549
550 /**
551 * gsf_input_set_size:
552 * @input: the input stream
553 * @size: the size of the stream
554 *
555 * Returns: %TRUE if the assignment was ok.
556 */
557 gboolean
gsf_input_set_size(GsfInput * input,gsf_off_t size)558 gsf_input_set_size (GsfInput *input, gsf_off_t size)
559 {
560 g_return_val_if_fail (input != NULL, FALSE);
561 g_return_val_if_fail (size >= 0, FALSE);
562
563 input->size = size;
564 return TRUE;
565 }
566
567 /**
568 * gsf_input_seek_emulate:
569 * @input: stream to emulate seek for
570 * @pos: absolute position to seek to
571 *
572 * Emulate forward seeks by reading.
573 *
574 * Returns: %TRUE if the emulation failed.
575 */
576 gboolean
gsf_input_seek_emulate(GsfInput * input,gsf_off_t pos)577 gsf_input_seek_emulate (GsfInput *input, gsf_off_t pos)
578 {
579 if (pos < input->cur_offset)
580 return TRUE;
581
582 while (pos > input->cur_offset) {
583 gsf_off_t readcount = MIN (pos - input->cur_offset, 8192);
584 if (!gsf_input_read (input, readcount, NULL))
585 return TRUE;
586 }
587 return FALSE;
588 }
589
590 /**
591 * gsf_input_get_modtime:
592 * @input: the input stream
593 *
594 * Returns: (transfer none): A #GDateTime representing when the input
595 * was last modified, or %NULL if not known.
596 */
597 GDateTime *
gsf_input_get_modtime(GsfInput * input)598 gsf_input_get_modtime (GsfInput *input)
599 {
600 g_return_val_if_fail (GSF_IS_INPUT (input), NULL);
601
602 return g_object_get_data (G_OBJECT (input), MODTIME_ATTR);
603 }
604
605 /**
606 * gsf_input_set_modtime:
607 * @input: the input stream
608 * @modtime: (transfer none) (allow-none): the new modification time.
609 *
610 * protected.
611 *
612 * Returns: %TRUE if the assignment was ok.
613 */
614 gboolean
gsf_input_set_modtime(GsfInput * input,GDateTime * modtime)615 gsf_input_set_modtime (GsfInput *input, GDateTime *modtime)
616 {
617 g_return_val_if_fail (GSF_IS_INPUT (input), FALSE);
618
619 if (modtime)
620 modtime = g_date_time_add (modtime, 0); /* Copy */
621
622 /* This actually also works for null modtime. */
623 g_object_set_data_full (G_OBJECT (input),
624 MODTIME_ATTR, modtime,
625 (GDestroyNotify)g_date_time_unref);
626
627 return TRUE;
628 }
629
630 gboolean
gsf_input_set_modtime_from_stat(GsfInput * input,const struct stat * st)631 gsf_input_set_modtime_from_stat (GsfInput *input,
632 const struct stat *st)
633 {
634 GDateTime *modtime = NULL, *ut = NULL;
635 gboolean res;
636 gint64 sec, usec;
637
638 if (st->st_mtime == (time_t)-1)
639 return FALSE;
640
641 sec = st->st_mtime;
642 #if defined (HAVE_STRUCT_STAT_ST_MTIMENSEC)
643 usec = st->st_mtimensec / 1000;
644 #elif defined (HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC)
645 usec = st->st_mtim.tv_nsec / 1000;
646 #else
647 usec = 0;
648 #endif
649
650 ut = g_date_time_new_from_unix_utc (sec);
651 modtime = g_date_time_add (ut, usec);
652 res = gsf_input_set_modtime (GSF_INPUT (input), modtime);
653 g_date_time_unref (ut);
654 g_date_time_unref (modtime);
655
656 return res;
657 }
658
659
660 /****************************************************************************/
661
662 /**
663 * gsf_input_error_id:
664 *
665 * Returns: A utility quark to flag a #GError as being an input problem.
666 */
667 GQuark
gsf_input_error_id(void)668 gsf_input_error_id (void)
669 {
670 static GQuark quark;
671 if (!quark)
672 quark = g_quark_from_static_string ("gsf_input_error_id");
673 return quark;
674 }
675
676 /**
677 * gsf_input_error:
678 *
679 * Deprecated as of GSF 1.12.0; use gsf_input_error_id() instead.
680 *
681 * Returns: A utility quark to flag a #GError as being an input problem.
682 */
683 GQuark
gsf_input_error(void)684 gsf_input_error (void)
685 {
686 return gsf_input_error_id ();
687 }
688
689 /****************************************************************************/
690
691 #define GSF_READ_BUFSIZE (1024 * 4)
692
693 /**
694 * gsf_input_copy:
695 * @input: a non-null #GsfInput
696 * @output: a non-null #GsfOutput
697 *
698 * Copy the contents from @input to @output from their respective
699 * current positions. So if you want to be sure to copy *everything*,
700 * make sure to call gsf_input_seek (input, 0, G_SEEK_SET) and
701 * gsf_output_seek (output, 0, G_SEEK_SET) first, if applicable.
702 *
703 * Returns: %TRUE on success
704 **/
705 gboolean
gsf_input_copy(GsfInput * input,GsfOutput * output)706 gsf_input_copy (GsfInput *input, GsfOutput *output)
707 {
708 gboolean success = TRUE;
709 gsf_off_t remaining;
710
711 g_return_val_if_fail (input != NULL, FALSE);
712 g_return_val_if_fail (output != NULL, FALSE);
713
714 while (success && (remaining = gsf_input_remaining (input)) > 0) {
715 gsf_off_t toread = MIN (remaining, GSF_READ_BUFSIZE);
716 const guint8 * buffer = gsf_input_read (input, toread, NULL);
717 if (buffer)
718 success = gsf_output_write (output, toread, buffer);
719 else
720 success = FALSE;
721 }
722
723 return success;
724 }
725
726 /****************************************************************************/
727
728 /**
729 * gsf_input_uncompress:
730 * @src: (transfer full): stream to be uncompressed.
731 *
732 * This functions takes ownership of the incoming reference and yields a
733 * new one as its output.
734 *
735 * Returns: (transfer full): A stream equivalent to the source stream,
736 * but uncompressed if the source was compressed.
737 **/
738 GsfInput *
gsf_input_uncompress(GsfInput * src)739 gsf_input_uncompress (GsfInput *src)
740 {
741 gsf_off_t cur_offset = src->cur_offset;
742 guint8 header[4];
743
744 if (gsf_input_seek (src, 0, G_SEEK_SET))
745 goto error;
746
747 /* Read header up front, so we avoid extra seeks in tests. */
748 if (!gsf_input_read (src, 4, header))
749 goto error;
750
751 /* Let's try gzip. */
752 {
753 const unsigned char gzip_sig[2] = { 0x1f, 0x8b };
754
755 if (memcmp (gzip_sig, header, sizeof (gzip_sig)) == 0) {
756 GsfInput *res = gsf_input_gzip_new (src, NULL);
757 if (res) {
758 g_object_unref (src);
759 return gsf_input_uncompress (res);
760 }
761 }
762 }
763
764 /* Let's try bzip. */
765 {
766 guint8 const *bzip_sig = "BZh";
767
768 if (memcmp (bzip_sig, header, strlen (bzip_sig)) == 0) {
769 GsfInput *res = gsf_input_memory_new_from_bzip (src, NULL);
770 if (res) {
771 g_object_unref (src);
772 return gsf_input_uncompress (res);
773 }
774 }
775 }
776
777 /* Other methods go here. */
778
779 error:
780 (void)gsf_input_seek (src, cur_offset, G_SEEK_SET);
781 return src;
782 }
783