cebf81f2eae53b18b7e33c457d3afc4885ddd6f4
[platform/upstream/gstreamer.git] / plugins / elements / gstfilesrc.c
1 /* GStreamer
2  * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
3  *               2000,2005 Wim Taymans <wim@fluendo.com>
4  *
5  * gstfilesrc.c:
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 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  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20  * Boston, MA 02111-1307, USA.
21  */
22 /**
23  * SECTION:element-filesrc
24  * @short_description: read from arbitrary point in a file
25  * @see_also: #GstFileSrc
26  *
27  * Read data from a file in the local file system. The implementation is using
28  * mmap(2) to read chunks from the file in an efficient way.
29  */
30
31 #ifdef HAVE_CONFIG_H
32 #  include "config.h"
33 #endif
34
35 #include <gst/gst.h>
36 #include "gstfilesrc.h"
37
38 #include <stdio.h>
39 #include <sys/types.h>
40 #include <sys/stat.h>
41 #include <fcntl.h>
42
43 #ifdef HAVE_UNISTD_H
44 #  include <unistd.h>
45 #endif
46
47 #ifdef HAVE_MMAP
48 # include <sys/mman.h>
49 #endif
50
51 #ifdef HAVE_WIN32
52 #  include <io.h>               /* lseek, open, close, read */
53 #endif
54
55 #include <errno.h>
56 #include <string.h>
57
58 #include "../../gst/gst-i18n-lib.h"
59
60 static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
61     GST_PAD_SRC,
62     GST_PAD_ALWAYS,
63     GST_STATIC_CAPS_ANY);
64
65 /* FIXME we should be using glib for this */
66 #ifndef S_ISREG
67 #define S_ISREG(mode) ((mode)&_S_IFREG)
68 #endif
69 #ifndef S_ISDIR
70 #define S_ISDIR(mode) ((mode)&_S_IFDIR)
71 #endif
72 #ifndef S_ISSOCK
73 #define S_ISSOCK(x) (0)
74 #endif
75 #ifndef O_BINARY
76 #define O_BINARY (0)
77 #endif
78
79
80 /**********************************************************************
81  * GStreamer Default File Source
82  * Theory of Operation
83  *
84  * This source uses mmap(2) to efficiently load data from a file.
85  * To do this without seriously polluting the applications' memory
86  * space, it must do so in smaller chunks, say 1-4MB at a time.
87  * Buffers are then subdivided from these mmap'd chunks, to directly
88  * make use of the mmap.
89  *
90  * To handle refcounting so that the mmap can be freed at the appropriate
91  * time, a buffer will be created for each mmap'd region, and all new
92  * buffers will be sub-buffers of this top-level buffer.  As they are
93  * freed, the refcount goes down on the mmap'd buffer and its free()
94  * function is called, which will call munmap(2) on itself.
95  *
96  * If a buffer happens to cross the boundaries of an mmap'd region, we
97  * have to decide whether it's more efficient to copy the data into a
98  * new buffer, or mmap() just that buffer.  There will have to be a
99  * breakpoint size to determine which will be done.  The mmap() size
100  * has a lot to do with this as well, because you end up in double-
101  * jeopardy: the larger the outgoing buffer, the more data to copy when
102  * it overlaps, *and* the more frequently you'll have buffers that *do*
103  * overlap.
104  *
105  * Seeking is another tricky aspect to do efficiently.  The initial
106  * implementation of this source won't make use of these features, however.
107  * The issue is that if an application seeks backwards in a file, *and*
108  * that region of the file is covered by an mmap that hasn't been fully
109  * deallocated, we really should re-use it.  But keeping track of these
110  * regions is tricky because we have to lock the structure that holds
111  * them.  We need to settle on a locking primitive (GMutex seems to be
112  * a really good option...), then we can do that.
113  */
114
115
116 GST_DEBUG_CATEGORY_STATIC (gst_file_src_debug);
117 #define GST_CAT_DEFAULT gst_file_src_debug
118
119 static GstElementDetails gst_file_src_details =
120 GST_ELEMENT_DETAILS ("File Source",
121     "Source/File",
122     "Read from arbitrary point in a file",
123     "Erik Walthinsen <omega@cse.ogi.edu>");
124
125 /* FileSrc signals and args */
126 enum
127 {
128   /* FILL ME */
129   LAST_SIGNAL
130 };
131
132 #define DEFAULT_BLOCKSIZE       4*1024
133 #define DEFAULT_MMAPSIZE        4*1024*1024
134 #define DEFAULT_TOUCH           FALSE
135
136 enum
137 {
138   ARG_0,
139   ARG_LOCATION,
140   ARG_FD,
141   ARG_MMAPSIZE,
142   ARG_TOUCH
143 };
144
145 static void gst_file_src_finalize (GObject * object);
146
147 static void gst_file_src_set_property (GObject * object, guint prop_id,
148     const GValue * value, GParamSpec * pspec);
149 static void gst_file_src_get_property (GObject * object, guint prop_id,
150     GValue * value, GParamSpec * pspec);
151
152 static gboolean gst_file_src_start (GstBaseSrc * basesrc);
153 static gboolean gst_file_src_stop (GstBaseSrc * basesrc);
154
155 static gboolean gst_file_src_is_seekable (GstBaseSrc * src);
156 static gboolean gst_file_src_get_size (GstBaseSrc * src, guint64 * size);
157 static GstFlowReturn gst_file_src_create (GstBaseSrc * src, guint64 offset,
158     guint length, GstBuffer ** buffer);
159
160 static void gst_file_src_uri_handler_init (gpointer g_iface,
161     gpointer iface_data);
162
163 static void
164 _do_init (GType filesrc_type)
165 {
166   static const GInterfaceInfo urihandler_info = {
167     gst_file_src_uri_handler_init,
168     NULL,
169     NULL
170   };
171
172   g_type_add_interface_static (filesrc_type, GST_TYPE_URI_HANDLER,
173       &urihandler_info);
174   GST_DEBUG_CATEGORY_INIT (gst_file_src_debug, "filesrc", 0, "filesrc element");
175 }
176
177 GST_BOILERPLATE_FULL (GstFileSrc, gst_file_src, GstBaseSrc, GST_TYPE_BASE_SRC,
178     _do_init);
179
180 static void
181 gst_file_src_base_init (gpointer g_class)
182 {
183   GstElementClass *gstelement_class = GST_ELEMENT_CLASS (g_class);
184
185   gst_element_class_add_pad_template (gstelement_class,
186       gst_static_pad_template_get (&srctemplate));
187
188   gst_element_class_set_details (gstelement_class, &gst_file_src_details);
189 }
190
191 static void
192 gst_file_src_class_init (GstFileSrcClass * klass)
193 {
194   GObjectClass *gobject_class;
195   GstElementClass *gstelement_class;
196   GstBaseSrcClass *gstbasesrc_class;
197
198   gobject_class = (GObjectClass *) klass;
199   gstelement_class = (GstElementClass *) klass;
200   gstbasesrc_class = (GstBaseSrcClass *) klass;
201
202   gobject_class->set_property = gst_file_src_set_property;
203   gobject_class->get_property = gst_file_src_get_property;
204
205   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_FD,
206       g_param_spec_int ("fd", "File-descriptor",
207           "File-descriptor for the file being mmap()d", 0, G_MAXINT, 0,
208           G_PARAM_READABLE));
209   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_LOCATION,
210       g_param_spec_string ("location", "File Location",
211           "Location of the file to read", NULL, G_PARAM_READWRITE));
212   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_MMAPSIZE,
213       g_param_spec_ulong ("mmapsize", "mmap() Block Size",
214           "Size in bytes of mmap()d regions", 0, G_MAXULONG, DEFAULT_MMAPSIZE,
215           G_PARAM_READWRITE));
216   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_TOUCH,
217       g_param_spec_boolean ("touch", "Touch read data",
218           "Touch data to force disk read", DEFAULT_TOUCH, G_PARAM_READWRITE));
219
220   gobject_class->finalize = gst_file_src_finalize;
221
222   gstbasesrc_class->start = GST_DEBUG_FUNCPTR (gst_file_src_start);
223   gstbasesrc_class->stop = GST_DEBUG_FUNCPTR (gst_file_src_stop);
224   gstbasesrc_class->is_seekable = GST_DEBUG_FUNCPTR (gst_file_src_is_seekable);
225   gstbasesrc_class->get_size = GST_DEBUG_FUNCPTR (gst_file_src_get_size);
226   gstbasesrc_class->create = GST_DEBUG_FUNCPTR (gst_file_src_create);
227
228   if (sizeof (off_t) < 8) {
229     GST_LOG ("No large file support, sizeof (off_t) = %u!", sizeof (off_t));
230   }
231 }
232
233 static void
234 gst_file_src_init (GstFileSrc * src, GstFileSrcClass * g_class)
235 {
236 #ifdef HAVE_MMAP
237   src->pagesize = getpagesize ();
238 #endif
239
240   src->filename = NULL;
241   src->fd = 0;
242   src->uri = NULL;
243
244   src->touch = DEFAULT_TOUCH;
245
246   src->mapbuf = NULL;
247   src->mapsize = DEFAULT_MMAPSIZE;      /* default is 4MB */
248
249   src->is_regular = FALSE;
250 }
251
252 static void
253 gst_file_src_finalize (GObject * object)
254 {
255   GstFileSrc *src;
256
257   src = GST_FILE_SRC (object);
258
259   g_free (src->filename);
260   g_free (src->uri);
261
262   G_OBJECT_CLASS (parent_class)->finalize (object);
263 }
264
265 static gboolean
266 gst_file_src_set_location (GstFileSrc * src, const gchar * location)
267 {
268   /* the element must be stopped in order to do this */
269   GST_STATE_LOCK (src);
270   {
271     GstState state;
272
273     state = GST_STATE (src);
274     if (state != GST_STATE_READY && state != GST_STATE_NULL)
275       goto wrong_state;
276   }
277   GST_STATE_UNLOCK (src);
278
279   g_free (src->filename);
280   g_free (src->uri);
281
282   /* clear the filename if we get a NULL (is that possible?) */
283   if (location == NULL) {
284     src->filename = NULL;
285     src->uri = NULL;
286   } else {
287     src->filename = g_strdup (location);
288     src->uri = gst_uri_construct ("file", src->filename);
289   }
290   g_object_notify (G_OBJECT (src), "location");
291   gst_uri_handler_new_uri (GST_URI_HANDLER (src), src->uri);
292
293   return TRUE;
294
295   /* ERROR */
296 wrong_state:
297   {
298     GST_DEBUG_OBJECT (src, "setting location in wrong state");
299     GST_STATE_UNLOCK (src);
300     return FALSE;
301   }
302 }
303
304 static void
305 gst_file_src_set_property (GObject * object, guint prop_id,
306     const GValue * value, GParamSpec * pspec)
307 {
308   GstFileSrc *src;
309
310   g_return_if_fail (GST_IS_FILE_SRC (object));
311
312   src = GST_FILE_SRC (object);
313
314   switch (prop_id) {
315     case ARG_LOCATION:
316       gst_file_src_set_location (src, g_value_get_string (value));
317       break;
318     case ARG_MMAPSIZE:
319       if ((src->mapsize % src->pagesize) == 0) {
320         src->mapsize = g_value_get_ulong (value);
321         g_object_notify (G_OBJECT (src), "mmapsize");
322       } else {
323         GST_INFO_OBJECT (src,
324             "invalid mapsize, must be a multiple of pagesize, which is %d",
325             src->pagesize);
326       }
327       break;
328     case ARG_TOUCH:
329       src->touch = g_value_get_boolean (value);
330       g_object_notify (G_OBJECT (src), "touch");
331       break;
332     default:
333       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
334       break;
335   }
336 }
337
338 static void
339 gst_file_src_get_property (GObject * object, guint prop_id, GValue * value,
340     GParamSpec * pspec)
341 {
342   GstFileSrc *src;
343
344   g_return_if_fail (GST_IS_FILE_SRC (object));
345
346   src = GST_FILE_SRC (object);
347
348   switch (prop_id) {
349     case ARG_LOCATION:
350       g_value_set_string (value, src->filename);
351       break;
352     case ARG_FD:
353       g_value_set_int (value, src->fd);
354       break;
355     case ARG_MMAPSIZE:
356       g_value_set_ulong (value, src->mapsize);
357       break;
358     case ARG_TOUCH:
359       g_value_set_boolean (value, src->touch);
360       break;
361     default:
362       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
363       break;
364   }
365 }
366
367 /***
368  * mmap code below
369  */
370
371 #ifdef HAVE_MMAP
372
373 /* GstMmapBuffer */
374
375 typedef struct _GstMmapBuffer GstMmapBuffer;
376 typedef struct _GstMmapBufferClass GstMmapBufferClass;
377
378 #define GST_TYPE_MMAP_BUFFER                         (gst_mmap_buffer_get_type())
379
380 #define GST_IS_MMAP_BUFFER(obj)  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_MMAP_BUFFER))
381 #define GST_IS_MMAP_BUFFER_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_MMAP_BUFFER))
382 #define GST_MMAP_BUFFER_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_MMAP_BUFFER, GstMmapBufferClass))
383 #define GST_MMAP_BUFFER(obj)     (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_MMAP_BUFFER, GstMmapBuffer))
384 #define GST_MMAP_BUFFER_CLASS(klass)  (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_MMAP_BUFFER, GstMmapBufferClass))
385
386
387
388 struct _GstMmapBuffer
389 {
390   GstBuffer buffer;
391
392   GstFileSrc *filesrc;
393 };
394
395 struct _GstMmapBufferClass
396 {
397   GstBufferClass buffer_class;
398 };
399
400 static void gst_mmap_buffer_init (GTypeInstance * instance, gpointer g_class);
401 static void gst_mmap_buffer_class_init (gpointer g_class, gpointer class_data);
402 static void gst_mmap_buffer_finalize (GstMmapBuffer * mmap_buffer);
403
404 static GType
405 gst_mmap_buffer_get_type (void)
406 {
407   static GType _gst_mmap_buffer_type;
408
409   if (G_UNLIKELY (_gst_mmap_buffer_type == 0)) {
410     static const GTypeInfo mmap_buffer_info = {
411       sizeof (GstMmapBufferClass),
412       NULL,
413       NULL,
414       gst_mmap_buffer_class_init,
415       NULL,
416       NULL,
417       sizeof (GstMmapBuffer),
418       0,
419       gst_mmap_buffer_init,
420       NULL
421     };
422
423     _gst_mmap_buffer_type = g_type_register_static (GST_TYPE_BUFFER,
424         "GstMmapBuffer", &mmap_buffer_info, 0);
425   }
426   return _gst_mmap_buffer_type;
427 }
428
429 static void
430 gst_mmap_buffer_class_init (gpointer g_class, gpointer class_data)
431 {
432   GstMiniObjectClass *mini_object_class = GST_MINI_OBJECT_CLASS (g_class);
433
434   mini_object_class->finalize =
435       (GstMiniObjectFinalizeFunction) gst_mmap_buffer_finalize;
436 }
437
438 static void
439 gst_mmap_buffer_init (GTypeInstance * instance, gpointer g_class)
440 {
441   GstBuffer *buf = (GstBuffer *) instance;
442
443   GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_READONLY);
444   /* before we re-enable this flag, we probably need to fix _copy()
445    * _make_writable(), etc. in GstMiniObject/GstBuffer as well */
446   /* GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_ORIGINAL); */
447 }
448
449 static void
450 gst_mmap_buffer_finalize (GstMmapBuffer * mmap_buffer)
451 {
452   guint size;
453   gpointer data;
454   guint64 offset;
455   GstFileSrc *src;
456   GstBuffer *buffer = GST_BUFFER (mmap_buffer);
457
458   /* get info */
459   size = GST_BUFFER_SIZE (buffer);
460   offset = GST_BUFFER_OFFSET (buffer);
461   data = GST_BUFFER_DATA (buffer);
462   src = mmap_buffer->filesrc;
463
464   GST_LOG ("freeing mmap()d buffer at %" G_GUINT64_FORMAT "+%u", offset, size);
465
466 #ifdef MADV_DONTNEED
467   /* madvise to tell the kernel what to do with it */
468   if (madvise (data, size, MADV_DONTNEED) < 0) {
469     GST_WARNING_OBJECT (src, "warning: madvise failed: %s", strerror (errno));
470   }
471 #endif
472
473   /* now unmap the memory */
474   if (munmap (data, size) < 0) {
475     GST_WARNING_OBJECT (src, "warning: munmap failed: %s", strerror (errno));
476   }
477
478   /* cast to unsigned long, since there's no gportable way to print
479    * guint64 as hex */
480   GST_LOG ("unmapped region %08lx+%08lx at %p",
481       (gulong) offset, (gulong) size, data);
482 }
483
484 static GstBuffer *
485 gst_file_src_map_region (GstFileSrc * src, off_t offset, size_t size,
486     gboolean testonly)
487 {
488   GstBuffer *buf;
489   void *mmapregion;
490
491   g_return_val_if_fail (offset >= 0, NULL);
492
493   GST_LOG_OBJECT (src, "mapping region %08llx+%08lx from file into memory",
494       offset, (gulong) size);
495
496   mmapregion = mmap (NULL, size, PROT_READ, MAP_SHARED, src->fd, offset);
497
498   if (mmapregion == NULL || mmapregion == MAP_FAILED)
499     goto mmap_failed;
500
501   GST_LOG_OBJECT (src, "mapped region %08lx+%08lx from file into memory at %p",
502       (gulong) offset, (gulong) size, mmapregion);
503
504   /* time to allocate a new mapbuf */
505   buf = (GstBuffer *) gst_mini_object_new (GST_TYPE_MMAP_BUFFER);
506   /* mmap() the data into this new buffer */
507   GST_BUFFER_DATA (buf) = mmapregion;
508   GST_MMAP_BUFFER (buf)->filesrc = src;
509
510 #ifdef MADV_SEQUENTIAL
511   /* madvise to tell the kernel what to do with it */
512   if (madvise (mmapregion, size, MADV_SEQUENTIAL) < 0) {
513     GST_WARNING_OBJECT (src, "warning: madvise failed: %s", strerror (errno));
514   }
515 #endif
516
517   /* fill in the rest of the fields */
518   GST_BUFFER_SIZE (buf) = size;
519   GST_BUFFER_OFFSET (buf) = offset;
520   GST_BUFFER_OFFSET_END (buf) = offset + size;
521   GST_BUFFER_TIMESTAMP (buf) = GST_CLOCK_TIME_NONE;
522
523   return buf;
524
525   /* ERROR */
526 mmap_failed:
527   {
528     if (!testonly) {
529       GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, (NULL),
530           ("mmap (0x%08lx, %d, 0x%llx) failed: %s",
531               (gulong) size, src->fd, offset, strerror (errno)));
532     }
533     return NULL;
534   }
535 }
536
537 static GstBuffer *
538 gst_file_src_map_small_region (GstFileSrc * src, off_t offset, size_t size)
539 {
540   GstBuffer *ret;
541   off_t mod;
542   guint pagesize;
543
544   GST_LOG_OBJECT (src,
545       "attempting to map a small buffer at %llu+%d",
546       (unsigned long long) offset, (gint) size);
547
548   pagesize = src->pagesize;
549
550   mod = offset % pagesize;
551
552   /* if the offset starts at a non-page boundary, we have to special case */
553   if (mod != 0) {
554     size_t mapsize;
555     off_t mapbase;
556     GstBuffer *map;
557
558     mapbase = offset - mod;
559     mapsize = ((size + mod + pagesize - 1) / pagesize) * pagesize;
560
561     GST_LOG_OBJECT (src,
562         "not on page boundaries, resizing to map to %llu+%d",
563         (unsigned long long) mapbase, (gint) mapsize);
564
565     map = gst_file_src_map_region (src, mapbase, mapsize, FALSE);
566     if (map == NULL)
567       return NULL;
568
569     ret = gst_buffer_create_sub (map, offset - mapbase, size);
570     GST_BUFFER_OFFSET (ret) = GST_BUFFER_OFFSET (map) + offset - mapbase;
571
572     gst_buffer_unref (map);
573   } else {
574     ret = gst_file_src_map_region (src, offset, size, FALSE);
575   }
576
577   return ret;
578 }
579
580 static GstFlowReturn
581 gst_file_src_create_mmap (GstFileSrc * src, guint64 offset, guint length,
582     GstBuffer ** buffer)
583 {
584   GstBuffer *buf = NULL;
585   size_t readsize, mapsize;
586   off_t readend, mapstart, mapend;
587   int i;
588
589   /* calculate end pointers so we don't have to do so repeatedly later */
590   readsize = length;
591   readend = offset + readsize;  /* note this is the byte *after* the read */
592
593   mapstart = GST_BUFFER_OFFSET (src->mapbuf);
594   mapsize = GST_BUFFER_SIZE (src->mapbuf);
595   mapend = mapstart + mapsize;  /* note this is the byte *after* the map */
596
597   GST_LOG ("attempting to read %08lx, %08lx, %08lx, %08lx",
598       (unsigned long) readsize, (unsigned long) readend,
599       (unsigned long) mapstart, (unsigned long) mapend);
600
601   /* if the start is past the mapstart */
602   if (offset >= mapstart) {
603     /* if the end is before the mapend, the buffer is in current mmap region... */
604     /* ('cause by definition if readend is in the buffer, so's readstart) */
605     if (readend <= mapend) {
606       GST_LOG_OBJECT (src,
607           "read buf %llu+%d lives in current mapbuf %lld+%d, creating subbuffer of mapbuf",
608           offset, (int) readsize, mapstart, mapsize);
609       buf = gst_buffer_create_sub (src->mapbuf, offset - mapstart, readsize);
610       GST_BUFFER_OFFSET (buf) = offset;
611
612       /* if the start actually is within the current mmap region, map an overlap buffer */
613     } else if (offset < mapend) {
614       GST_LOG_OBJECT (src,
615           "read buf %llu+%d starts in mapbuf %d+%d but ends outside, creating new mmap",
616           (unsigned long long) offset, (gint) readsize, (gint) mapstart,
617           (gint) mapsize);
618       buf = gst_file_src_map_small_region (src, offset, readsize);
619       if (buf == NULL)
620         goto could_not_mmap;
621     }
622
623     /* the only other option is that buffer is totally outside, which means we search for it */
624
625     /* now we can assume that the start is *before* the current mmap region */
626     /* if the readend is past mapstart, we have two options */
627   } else if (readend >= mapstart) {
628     /* either the read buffer overlaps the start of the mmap region */
629     /* or the read buffer fully contains the current mmap region    */
630     /* either way, it's really not relevant, we just create a new region anyway */
631     GST_LOG_OBJECT (src,
632         "read buf %llu+%d starts before mapbuf %d+%d, but overlaps it",
633         (unsigned long long) offset, (gint) readsize, (gint) mapstart,
634         (gint) mapsize);
635     buf = gst_file_src_map_small_region (src, offset, readsize);
636     if (buf == NULL)
637       goto could_not_mmap;
638   }
639
640   /* then deal with the case where the read buffer is totally outside */
641   if (buf == NULL) {
642     /* first check to see if there's a map that covers the right region already */
643     GST_LOG_OBJECT (src, "searching for mapbuf to cover %llu+%d",
644         offset, (int) readsize);
645
646     /* if the read buffer crosses a mmap region boundary, create a one-off region */
647     if ((offset / src->mapsize) != (readend / src->mapsize)) {
648       GST_LOG_OBJECT (src,
649           "read buf %llu+%d crosses a %d-byte boundary, creating a one-off",
650           offset, (int) readsize, (int) src->mapsize);
651       buf = gst_file_src_map_small_region (src, offset, readsize);
652       if (buf == NULL)
653         goto could_not_mmap;
654
655       /* otherwise we will create a new mmap region and set it to the default */
656     } else {
657       size_t mapsize;
658
659       off_t nextmap = offset - (offset % src->mapsize);
660
661       GST_LOG_OBJECT (src,
662           "read buf %llu+%d in new mapbuf at %llu+%d, mapping and subbuffering",
663           offset, readsize, nextmap, src->mapsize);
664       /* first, we're done with the old mapbuf */
665       gst_buffer_unref (src->mapbuf);
666       mapsize = src->mapsize;
667
668       /* double the mapsize as long as the readsize is smaller */
669       while (readsize + offset > nextmap + mapsize) {
670         GST_LOG_OBJECT (src, "readsize smaller then mapsize %08x %d",
671             readsize, (int) mapsize);
672         mapsize <<= 1;
673       }
674       /* create a new one */
675       src->mapbuf = gst_file_src_map_region (src, nextmap, mapsize, FALSE);
676       if (src->mapbuf == NULL)
677         goto could_not_mmap;
678
679       /* subbuffer it */
680       buf = gst_buffer_create_sub (src->mapbuf, offset - nextmap, readsize);
681       GST_BUFFER_OFFSET (buf) =
682           GST_BUFFER_OFFSET (src->mapbuf) + offset - nextmap;
683     }
684   }
685
686   /* if we need to touch the buffer (to bring it into memory), do so */
687   if (src->touch) {
688     volatile guchar *p = GST_BUFFER_DATA (buf), c;
689
690     /* read first byte of each page */
691     for (i = 0; i < GST_BUFFER_SIZE (buf); i += src->pagesize)
692       c = p[i];
693   }
694
695   /* we're done, return the buffer */
696   *buffer = buf;
697
698   return GST_FLOW_OK;
699
700   /* ERROR */
701 could_not_mmap:
702   {
703     return GST_FLOW_ERROR;
704   }
705 }
706 #endif
707
708 /***
709  * read code below
710  * that is to say, you shouldn't read the code below, but the code that reads
711  * stuff is below.  Well, you shouldn't not read the code below, feel free
712  * to read it of course.  It's just that "read code below" is a pretty crappy
713  * documentation string because it sounds like we're expecting you to read
714  * the code to understand what it does, which, while true, is really not
715  * the sort of attitude we want to be advertising.  No sir.
716  *
717  */
718
719 static GstFlowReturn
720 gst_file_src_create_read (GstFileSrc * src, guint64 offset, guint length,
721     GstBuffer ** buffer)
722 {
723   int ret;
724   GstBuffer *buf;
725
726   if (src->read_position != offset) {
727     off_t res;
728
729     res = lseek (src->fd, offset, SEEK_SET);
730     if (res < 0 || res != offset)
731       goto seek_failed;
732   }
733
734   buf = gst_buffer_new_and_alloc (length);
735
736   GST_LOG_OBJECT (src, "Reading %d bytes", length);
737   ret = read (src->fd, GST_BUFFER_DATA (buf), length);
738   if (ret < 0)
739     goto could_not_read;
740
741   /* regular files should have given us what we expected */
742   if ((guint) ret < length && src->is_regular)
743     goto unexpected_eos;
744
745   /* other files should eos if they read 0 */
746   if (ret == 0) {
747     GST_DEBUG ("non-regular file hits EOS");
748     gst_buffer_unref (buf);
749     return GST_FLOW_UNEXPECTED;
750   }
751   length = ret;
752
753   GST_BUFFER_SIZE (buf) = length;
754   GST_BUFFER_OFFSET (buf) = offset;
755   GST_BUFFER_OFFSET_END (buf) = offset + length;
756
757   *buffer = buf;
758
759   src->read_position += length;
760
761   return GST_FLOW_OK;
762
763   /* ERROR */
764 seek_failed:
765   {
766     GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL), GST_ERROR_SYSTEM);
767     return GST_FLOW_ERROR;
768   }
769 could_not_read:
770   {
771     GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL), GST_ERROR_SYSTEM);
772     gst_buffer_unref (buf);
773     return GST_FLOW_ERROR;
774   }
775 unexpected_eos:
776   {
777     GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL),
778         ("unexpected end of file."));
779     gst_buffer_unref (buf);
780     return GST_FLOW_ERROR;
781   }
782 }
783
784 static GstFlowReturn
785 gst_file_src_create (GstBaseSrc * basesrc, guint64 offset, guint length,
786     GstBuffer ** buffer)
787 {
788   GstFileSrc *src;
789   GstFlowReturn ret;
790
791   src = GST_FILE_SRC (basesrc);
792
793 #ifdef HAVE_MMAP
794   if (src->using_mmap) {
795     ret = gst_file_src_create_mmap (src, offset, length, buffer);
796   } else {
797     ret = gst_file_src_create_read (src, offset, length, buffer);
798   }
799 #else
800   ret = gst_file_src_create_read (src, offset, length, buffer);
801 #endif
802
803   return ret;
804 }
805
806 static gboolean
807 gst_file_src_is_seekable (GstBaseSrc * basesrc)
808 {
809   GstFileSrc *src = GST_FILE_SRC (basesrc);
810
811   return src->seekable;
812 }
813
814 static gboolean
815 gst_file_src_get_size (GstBaseSrc * basesrc, guint64 * size)
816 {
817   struct stat stat_results;
818   GstFileSrc *src;
819
820   src = GST_FILE_SRC (basesrc);
821
822   if (!src->seekable) {
823     /* If it isn't seekable, we won't know the length (but fstat will still
824      * succeed, and wrongly say our length is zero. */
825     return FALSE;
826   }
827
828   if (fstat (src->fd, &stat_results) < 0)
829     goto could_not_stat;
830
831   *size = stat_results.st_size;
832
833   return TRUE;
834
835   /* ERROR */
836 could_not_stat:
837   {
838     return FALSE;
839   }
840 }
841
842 /* open the file and mmap it, necessary to go to READY state */
843 static gboolean
844 gst_file_src_start (GstBaseSrc * basesrc)
845 {
846   GstFileSrc *src = GST_FILE_SRC (basesrc);
847   struct stat stat_results;
848
849   if (src->filename == NULL || src->filename[0] == '\0')
850     goto no_filename;
851
852   GST_INFO_OBJECT (src, "opening file %s", src->filename);
853
854   /* open the file */
855   src->fd = open (src->filename, O_RDONLY | O_BINARY);
856   if (src->fd < 0)
857     goto open_failed;
858
859   /* check if it is a regular file, otherwise bail out */
860   if (fstat (src->fd, &stat_results) < 0)
861     goto no_stat;
862
863   if (S_ISDIR (stat_results.st_mode))
864     goto was_directory;
865
866   if (S_ISSOCK (stat_results.st_mode))
867     goto was_socket;
868
869   src->using_mmap = FALSE;
870   src->read_position = 0;
871
872   /* record if it's a regular (hence seekable and lengthable) file */
873   if (S_ISREG (stat_results.st_mode))
874     src->is_regular = TRUE;
875
876 #ifdef HAVE_MMAP
877   /* FIXME: maybe we should only try to mmap if it's a regular file */
878   /* allocate the first mmap'd region if it's a regular file ? */
879   src->mapbuf = gst_file_src_map_region (src, 0, src->mapsize, TRUE);
880   if (src->mapbuf != NULL) {
881     GST_DEBUG_OBJECT (src, "using mmap for file");
882     src->using_mmap = TRUE;
883     src->seekable = TRUE;
884   } else
885 #endif
886   {
887     /* If not in mmap mode, we need to check if the underlying file is
888      * seekable. */
889     off_t res = lseek (src->fd, 0, SEEK_CUR);
890
891     if (res < 0) {
892       GST_LOG_OBJECT (src, "disabling seeking, not in mmap mode and lseek "
893           "failed: %s", strerror (errno));
894       src->seekable = FALSE;
895     } else {
896       src->seekable = TRUE;
897     }
898   }
899
900   /* We can only really do seeking on regular files - for other file types, we
901    * don't know their length, so seeking isn't useful/meaningful */
902   src->seekable = src->seekable && src->is_regular;
903
904   return TRUE;
905
906   /* ERROR */
907 no_filename:
908   {
909     GST_ELEMENT_ERROR (src, RESOURCE, NOT_FOUND,
910         (_("No file name specified for reading.")), (NULL));
911     return FALSE;
912   }
913 open_failed:
914   {
915     switch (errno) {
916       case ENOENT:
917         GST_ELEMENT_ERROR (src, RESOURCE, NOT_FOUND, (NULL),
918             ("No such file \"%s\"", src->filename));
919         break;
920       default:
921         GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ,
922             (_("Could not open file \"%s\" for reading: %s."), src->filename,
923                 strerror (errno)), GST_ERROR_SYSTEM);
924         break;
925     }
926     return FALSE;
927   }
928 no_stat:
929   {
930     GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ,
931         (_("could not get info on \"%s\"."), src->filename), (NULL));
932     close (src->fd);
933     return FALSE;
934   }
935 was_directory:
936   {
937     GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ,
938         (_("\"%s\" is a directory."), src->filename), (NULL));
939     close (src->fd);
940     return FALSE;
941   }
942 was_socket:
943   {
944     GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ,
945         (_("File \"%s\" is a socket."), src->filename), (NULL));
946     close (src->fd);
947     return FALSE;
948   }
949 }
950
951 /* unmap and close the file */
952 static gboolean
953 gst_file_src_stop (GstBaseSrc * basesrc)
954 {
955   GstFileSrc *src = GST_FILE_SRC (basesrc);
956
957   /* close the file */
958   close (src->fd);
959
960   /* zero out a lot of our state */
961   src->fd = 0;
962   src->is_regular = FALSE;
963
964   if (src->mapbuf) {
965     gst_buffer_unref (src->mapbuf);
966     src->mapbuf = NULL;
967   }
968
969   return TRUE;
970 }
971
972 /*** GSTURIHANDLER INTERFACE *************************************************/
973
974 static guint
975 gst_file_src_uri_get_type (void)
976 {
977   return GST_URI_SRC;
978 }
979 static gchar **
980 gst_file_src_uri_get_protocols (void)
981 {
982   static gchar *protocols[] = { "file", NULL };
983
984   return protocols;
985 }
986 static const gchar *
987 gst_file_src_uri_get_uri (GstURIHandler * handler)
988 {
989   GstFileSrc *src = GST_FILE_SRC (handler);
990
991   return src->uri;
992 }
993
994 static gboolean
995 gst_file_src_uri_set_uri (GstURIHandler * handler, const gchar * uri)
996 {
997   gchar *protocol, *location;
998   gboolean ret;
999   GstFileSrc *src = GST_FILE_SRC (handler);
1000
1001   protocol = gst_uri_get_protocol (uri);
1002   if (strcmp (protocol, "file") != 0) {
1003     g_free (protocol);
1004     return FALSE;
1005   }
1006   g_free (protocol);
1007   location = gst_uri_get_location (uri);
1008   ret = gst_file_src_set_location (src, location);
1009   g_free (location);
1010
1011   return ret;
1012 }
1013
1014 static void
1015 gst_file_src_uri_handler_init (gpointer g_iface, gpointer iface_data)
1016 {
1017   GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;
1018
1019   iface->get_type = gst_file_src_uri_get_type;
1020   iface->get_protocols = gst_file_src_uri_get_protocols;
1021   iface->get_uri = gst_file_src_uri_get_uri;
1022   iface->set_uri = gst_file_src_uri_set_uri;
1023 }