• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GStreamer
2  * Copyright (C) 2014 Wim Taymans <wtaymans@redhat.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19 
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23 
24 #include <gst/gst.h>
25 #include <glib/gstdio.h>
26 
27 #include "gstsparsefile.h"
28 
29 #ifdef G_OS_WIN32
30 #include <io.h>                 /* lseek, open, close, read */
31 #undef fseek
32 #define fseek _fseeki64
33 #undef off_t
34 #define off_t guint64
35 #else
36 #include <unistd.h>
37 #endif
38 
39 #ifdef HAVE_FSEEKO
40 #define FSEEK_FILE(file,offset)  (fseeko (file, (off_t) offset, SEEK_SET) != 0)
41 #elif defined (G_OS_UNIX)
42 #define FSEEK_FILE(file,offset)  (lseek (fileno (file), (off_t) offset, SEEK_SET) == (off_t) -1)
43 #else
44 #define FSEEK_FILE(file,offset)  (fseek (file, offset, SEEK_SET) != 0)
45 #endif
46 
47 #define GST_SPARSE_FILE_IO_ERROR \
48     g_quark_from_static_string("gst-sparse-file-io-error-quark")
49 
50 static GstSparseFileIOErrorEnum
51 gst_sparse_file_io_error_from_errno (gint err_no);
52 
53 typedef struct _GstSparseRange GstSparseRange;
54 
55 struct _GstSparseRange
56 {
57   GstSparseRange *next;
58 
59   gsize start;
60   gsize stop;
61 };
62 
63 #define RANGE_CONTAINS(r,o) ((r)->start <= (o) && (r)->stop > (o))
64 
65 struct _GstSparseFile
66 {
67   gint fd;
68   FILE *file;
69   gsize current_pos;
70   gboolean was_writing;
71 
72   GstSparseRange *ranges;
73   guint n_ranges;
74 
75   GstSparseRange *write_range;
76   GstSparseRange *read_range;
77 };
78 
79 static GstSparseRange *
get_write_range(GstSparseFile * file,gsize offset)80 get_write_range (GstSparseFile * file, gsize offset)
81 {
82   GstSparseRange *next, *prev, *result = NULL;
83 
84   if (file->write_range && file->write_range->stop == offset)
85     return file->write_range;
86 
87   prev = NULL;
88   next = file->ranges;
89   while (next) {
90     if (next->start > offset)
91       break;
92 
93     if (next->stop >= offset) {
94       result = next;
95       break;
96     }
97     prev = next;
98     next = next->next;
99   }
100   if (result == NULL) {
101     result = g_slice_new0 (GstSparseRange);
102     result->start = offset;
103     result->stop = offset;
104 
105     result->next = next;
106     if (prev)
107       prev->next = result;
108     else
109       file->ranges = result;
110 
111     file->write_range = result;
112     file->read_range = NULL;
113 
114     file->n_ranges++;
115   }
116   return result;
117 }
118 
119 static GstSparseRange *
get_read_range(GstSparseFile * file,gsize offset,gsize count)120 get_read_range (GstSparseFile * file, gsize offset, gsize count)
121 {
122   GstSparseRange *walk, *result = NULL;
123 
124   if (file->read_range && RANGE_CONTAINS (file->read_range, offset))
125     return file->read_range;
126 
127   for (walk = file->ranges; walk; walk = walk->next) {
128     if (walk->start > offset)
129       break;
130 
131     if (walk->stop >= offset + count) {
132       result = walk;
133       break;
134     }
135   }
136   return result;
137 }
138 
139 /**
140  * gst_sparse_file_new:
141  *
142  * Make a new #GstSparseFile
143  *
144  * Returns: a new #GstSparseFile, gst_sparse_file_free() after usage.
145  *
146  * Since: 1.4
147  */
148 GstSparseFile *
gst_sparse_file_new(void)149 gst_sparse_file_new (void)
150 {
151   GstSparseFile *result;
152 
153   result = g_slice_new0 (GstSparseFile);
154   result->current_pos = 0;
155   result->ranges = NULL;
156   result->n_ranges = 0;
157 
158   return result;
159 }
160 
161 /**
162  * gst_sparse_file_set_fd:
163  * @file: a #GstSparseFile
164  * @fd: a file descriptor
165  *
166  * Store the data for @file in the file represented with @fd.
167  *
168  * Returns: %TRUE when @fd could be set
169  *
170  * Since: 1.4
171  */
172 gboolean
gst_sparse_file_set_fd(GstSparseFile * file,gint fd)173 gst_sparse_file_set_fd (GstSparseFile * file, gint fd)
174 {
175   g_return_val_if_fail (file != NULL, FALSE);
176   g_return_val_if_fail (fd != 0, FALSE);
177 
178   file->file = fdopen (fd, "wb+");
179   file->fd = fd;
180 
181   return file->file != NULL;
182 }
183 
184 /**
185  * gst_sparse_file_clear:
186  * @file: a #GstSparseFile
187  *
188  * Clear all the ranges in @file.
189  */
190 void
gst_sparse_file_clear(GstSparseFile * file)191 gst_sparse_file_clear (GstSparseFile * file)
192 {
193   g_return_if_fail (file != NULL);
194 
195   g_slice_free_chain (GstSparseRange, file->ranges, next);
196   file->current_pos = 0;
197   file->ranges = NULL;
198   file->n_ranges = 0;
199   file->was_writing = FALSE;
200 }
201 
202 /**
203  * gst_sparse_file_free:
204  * @file: a #GstSparseFile
205  *
206  * Free the memory used by @file.
207  *
208  * Since: 1.4
209  */
210 void
gst_sparse_file_free(GstSparseFile * file)211 gst_sparse_file_free (GstSparseFile * file)
212 {
213   g_return_if_fail (file != NULL);
214 
215   if (file->file) {
216     fflush (file->file);
217     fclose (file->file);
218   }
219   g_slice_free_chain (GstSparseRange, file->ranges, next);
220   g_slice_free (GstSparseFile, file);
221 }
222 
223 /**
224  * gst_sparse_file_write:
225  * @file: a #GstSparseFile
226  * @offset: the offset
227  * @data: the data
228  * @count: amount of bytes
229  * @available: amount of bytes already available
230  * @error: a #GError
231  *
232  * Write @count bytes from @data to @file at @offset.
233  *
234  * If @available is not %NULL, it will be updated with the amount of
235  * data already available after the last written byte.
236  *
237  * Returns: The number of bytes written or 0 on error.
238  *
239  * Since: 1.4
240  */
241 gsize
gst_sparse_file_write(GstSparseFile * file,gsize offset,gconstpointer data,gsize count,gsize * available,GError ** error)242 gst_sparse_file_write (GstSparseFile * file, gsize offset, gconstpointer data,
243     gsize count, gsize * available, GError ** error)
244 {
245   GstSparseRange *range, *next;
246   gsize stop;
247 
248   g_return_val_if_fail (file != NULL, 0);
249   g_return_val_if_fail (count != 0, 0);
250 
251   if (file->file) {
252     if (file->current_pos != offset) {
253       GST_DEBUG ("seeking to %" G_GSIZE_FORMAT, offset);
254       if (FSEEK_FILE (file->file, offset))
255         goto error;
256     } else if (!file->was_writing) {
257       fflush (file->file);
258     }
259     file->was_writing = TRUE;
260     if (fwrite (data, count, 1, file->file) != 1)
261       goto error;
262   }
263 
264   file->current_pos = offset + count;
265 
266   /* update the new stop position in the range */
267   range = get_write_range (file, offset);
268   stop = offset + count;
269   range->stop = MAX (range->stop, stop);
270 
271   /* see if we can merge with next region */
272   while ((next = range->next)) {
273     if (next->start > range->stop)
274       break;
275 
276     GST_DEBUG ("merging range %" G_GSIZE_FORMAT "-%" G_GSIZE_FORMAT ", next %"
277         G_GSIZE_FORMAT "-%" G_GSIZE_FORMAT, range->start, range->stop,
278         next->start, next->stop);
279 
280     range->stop = MAX (next->stop, range->stop);
281     range->next = next->next;
282 
283     if (file->write_range == next)
284       file->write_range = NULL;
285     if (file->read_range == next)
286       file->read_range = NULL;
287     g_slice_free (GstSparseRange, next);
288     file->n_ranges--;
289   }
290   if (available)
291     *available = range->stop - stop;
292 
293   return count;
294 
295   /* ERRORS */
296 error:
297   {
298     g_set_error (error, GST_SPARSE_FILE_IO_ERROR,
299         gst_sparse_file_io_error_from_errno (errno), "Error writing file: %s",
300         g_strerror (errno));
301     return 0;
302   }
303 }
304 
305 /**
306  * gst_sparse_file_read:
307  * @file: a #GstSparseFile
308  * @offset: the offset
309  * @data: the data
310  * @count: amount of bytes
311  * @remaining: amount of bytes remaining
312  * @error: a #GError
313  *
314  * Read @count bytes from @file at @offset into @data.
315  *
316  * On error, @error will be set. If there are no @count bytes available
317  * at @offset, %GST_SPARSE_FILE_IO_ERROR_WOULD_BLOCK is returned.
318  *
319  * @remaining will be set to the amount of bytes remaining in the read
320  * range.
321  *
322  * Returns: The number of bytes read of 0 on error.
323  *
324  * Since: 1.4
325  */
326 gsize
gst_sparse_file_read(GstSparseFile * file,gsize offset,gpointer data,gsize count,gsize * remaining,GError ** error)327 gst_sparse_file_read (GstSparseFile * file, gsize offset, gpointer data,
328     gsize count, gsize * remaining, GError ** error)
329 {
330   GstSparseRange *range;
331   gsize res = 0;
332 
333   g_return_val_if_fail (file != NULL, 0);
334   g_return_val_if_fail (count != 0, 0);
335 
336   if ((range = get_read_range (file, offset, count)) == NULL)
337     goto no_range;
338 
339   if (file->file) {
340     if (file->current_pos != offset) {
341       GST_DEBUG ("seeking from %" G_GSIZE_FORMAT " to %" G_GSIZE_FORMAT,
342           file->current_pos, offset);
343       if (FSEEK_FILE (file->file, offset))
344         goto error;
345     } else if (file->was_writing) {
346       fflush (file->file);
347     }
348     file->was_writing = FALSE;
349     res = fread (data, 1, count, file->file);
350     if (G_UNLIKELY (res < count))
351       goto error;
352   }
353 
354   file->current_pos = offset + res;
355 
356   if (remaining)
357     *remaining = range->stop - file->current_pos;
358 
359   return count;
360 
361   /* ERRORS */
362 no_range:
363   {
364     g_set_error_literal (error, GST_SPARSE_FILE_IO_ERROR,
365         GST_SPARSE_FILE_IO_ERROR_WOULD_BLOCK, "Offset not written to file yet");
366     return 0;
367   }
368 error:
369   {
370     if (ferror (file->file)) {
371       g_set_error (error, GST_SPARSE_FILE_IO_ERROR,
372           gst_sparse_file_io_error_from_errno (errno), "Error reading file: %s",
373           g_strerror (errno));
374     } else if (feof (file->file)) {
375       return res;
376     }
377     return 0;
378   }
379 }
380 
381 /**
382  * gst_sparse_file_n_ranges:
383  * @file: a #GstSparseFile
384  *
385  * Get the number of ranges that are written in @file.
386  *
387  * Returns: the number of written ranges.
388  *
389  * Since: 1.4
390  */
391 guint
gst_sparse_file_n_ranges(GstSparseFile * file)392 gst_sparse_file_n_ranges (GstSparseFile * file)
393 {
394   g_return_val_if_fail (file != NULL, 0);
395 
396   return file->n_ranges;
397 }
398 
399 /**
400  * gst_sparse_file_get_range_before:
401  * @file: a #GstSparseFile
402  * @offset: the range offset
403  * @start: result start
404  * @stop: result stop
405  *
406  * Get the start and stop offset of the range containing data before or
407  * including @offset.
408  *
409  * Returns: %TRUE if the range with data before @offset exists.
410  *
411  * Since: 1.4
412  */
413 gboolean
gst_sparse_file_get_range_before(GstSparseFile * file,gsize offset,gsize * start,gsize * stop)414 gst_sparse_file_get_range_before (GstSparseFile * file, gsize offset,
415     gsize * start, gsize * stop)
416 {
417   GstSparseRange *walk, *result = NULL;
418 
419   g_return_val_if_fail (file != NULL, FALSE);
420 
421   for (walk = file->ranges; walk; walk = walk->next) {
422     GST_DEBUG ("start %" G_GSIZE_FORMAT " > %" G_GSIZE_FORMAT,
423         walk->stop, offset);
424     if (walk->start > offset)
425       break;
426 
427     if (walk->start <= offset)
428       result = walk;
429   }
430 
431   if (result) {
432     if (start)
433       *start = result->start;
434     if (stop)
435       *stop = result->stop;
436   }
437   return result != NULL;
438 }
439 
440 /**
441  * gst_sparse_file_get_range_after:
442  * @file: a #GstSparseFile
443  * @offset: the range offset
444  * @start: result start
445  * @stop: result stop
446  *
447  * Get the start and stop offset of the range containing data after or
448  * including @offset.
449  *
450  * Returns: %TRUE if the range with data after @offset exists.
451  *
452  * Since: 1.4
453  */
454 gboolean
gst_sparse_file_get_range_after(GstSparseFile * file,gsize offset,gsize * start,gsize * stop)455 gst_sparse_file_get_range_after (GstSparseFile * file, gsize offset,
456     gsize * start, gsize * stop)
457 {
458   GstSparseRange *walk, *result = NULL;
459 
460   g_return_val_if_fail (file != NULL, FALSE);
461 
462   for (walk = file->ranges; walk; walk = walk->next) {
463     GST_DEBUG ("stop %" G_GSIZE_FORMAT " > %" G_GSIZE_FORMAT,
464         walk->stop, offset);
465     if (walk->stop > offset) {
466       result = walk;
467       break;
468     }
469   }
470   if (result) {
471     if (start)
472       *start = result->start;
473     if (stop)
474       *stop = result->stop;
475   }
476   return result != NULL;
477 }
478 
479 /* we don't want to rely on libgio just for g_io_error_from_errno() */
480 static GstSparseFileIOErrorEnum
gst_sparse_file_io_error_from_errno(gint err_no)481 gst_sparse_file_io_error_from_errno (gint err_no)
482 {
483   switch (err_no) {
484 #ifdef EEXIST
485     case EEXIST:
486       return GST_SPARSE_FILE_IO_ERROR_EXISTS;
487       break;
488 #endif
489 
490 #ifdef EISDIR
491     case EISDIR:
492       return GST_SPARSE_FILE_IO_ERROR_IS_DIRECTORY;
493       break;
494 #endif
495 
496 #ifdef EACCES
497     case EACCES:
498       return GST_SPARSE_FILE_IO_ERROR_PERMISSION_DENIED;
499       break;
500 #endif
501 
502 #ifdef ENAMETOOLONG
503     case ENAMETOOLONG:
504       return GST_SPARSE_FILE_IO_ERROR_FILENAME_TOO_LONG;
505       break;
506 #endif
507 
508 #ifdef ENOENT
509     case ENOENT:
510       return GST_SPARSE_FILE_IO_ERROR_NOT_FOUND;
511       break;
512 #endif
513 
514 #ifdef ENOTDIR
515     case ENOTDIR:
516       return GST_SPARSE_FILE_IO_ERROR_NOT_DIRECTORY;
517       break;
518 #endif
519 
520 #ifdef EROFS
521     case EROFS:
522       return GST_SPARSE_FILE_IO_ERROR_READ_ONLY;
523       break;
524 #endif
525 
526 #ifdef ELOOP
527     case ELOOP:
528       return GST_SPARSE_FILE_IO_ERROR_TOO_MANY_LINKS;
529       break;
530 #endif
531 
532 #ifdef ENOSPC
533     case ENOSPC:
534       return GST_SPARSE_FILE_IO_ERROR_NO_SPACE;
535       break;
536 #endif
537 
538 #ifdef ENOMEM
539     case ENOMEM:
540       return GST_SPARSE_FILE_IO_ERROR_NO_SPACE;
541       break;
542 #endif
543 
544 #ifdef EINVAL
545     case EINVAL:
546       return GST_SPARSE_FILE_IO_ERROR_INVALID_ARGUMENT;
547       break;
548 #endif
549 
550 #ifdef EPERM
551     case EPERM:
552       return GST_SPARSE_FILE_IO_ERROR_PERMISSION_DENIED;
553       break;
554 #endif
555 
556 #ifdef ECANCELED
557     case ECANCELED:
558       return GST_SPARSE_FILE_IO_ERROR_CANCELLED;
559       break;
560 #endif
561 
562       /* ENOTEMPTY == EEXIST on AIX for backward compatibility reasons */
563 #if defined (ENOTEMPTY) && (!defined (EEXIST) || (ENOTEMPTY != EEXIST))
564     case ENOTEMPTY:
565       return GST_SPARSE_FILE_IO_ERROR_NOT_EMPTY;
566       break;
567 #endif
568 
569 #ifdef ENOTSUP
570     case ENOTSUP:
571       return GST_SPARSE_FILE_IO_ERROR_NOT_SUPPORTED;
572       break;
573 #endif
574 
575       /* EOPNOTSUPP == ENOTSUP on Linux, but POSIX considers them distinct */
576 #if defined (EOPNOTSUPP) && (!defined (ENOTSUP) || (EOPNOTSUPP != ENOTSUP))
577     case EOPNOTSUPP:
578       return GST_SPARSE_FILE_IO_ERROR_NOT_SUPPORTED;
579       break;
580 #endif
581 
582 #ifdef EPROTONOSUPPORT
583     case EPROTONOSUPPORT:
584       return GST_SPARSE_FILE_IO_ERROR_NOT_SUPPORTED;
585       break;
586 #endif
587 
588 #ifdef ESOCKTNOSUPPORT
589     case ESOCKTNOSUPPORT:
590       return GST_SPARSE_FILE_IO_ERROR_NOT_SUPPORTED;
591       break;
592 #endif
593 
594 #ifdef EPFNOSUPPORT
595     case EPFNOSUPPORT:
596       return GST_SPARSE_FILE_IO_ERROR_NOT_SUPPORTED;
597       break;
598 #endif
599 
600 #ifdef EAFNOSUPPORT
601     case EAFNOSUPPORT:
602       return GST_SPARSE_FILE_IO_ERROR_NOT_SUPPORTED;
603       break;
604 #endif
605 
606 #ifdef ETIMEDOUT
607     case ETIMEDOUT:
608       return GST_SPARSE_FILE_IO_ERROR_TIMED_OUT;
609       break;
610 #endif
611 
612 #ifdef EBUSY
613     case EBUSY:
614       return GST_SPARSE_FILE_IO_ERROR_BUSY;
615       break;
616 #endif
617 
618 #ifdef EWOULDBLOCK
619     case EWOULDBLOCK:
620       return GST_SPARSE_FILE_IO_ERROR_WOULD_BLOCK;
621       break;
622 #endif
623 
624       /* EWOULDBLOCK == EAGAIN on most systems, but POSIX considers them distinct */
625 #if defined (EAGAIN) && (!defined (EWOULDBLOCK) || (EWOULDBLOCK != EAGAIN))
626     case EAGAIN:
627       return GST_SPARSE_FILE_IO_ERROR_WOULD_BLOCK;
628       break;
629 #endif
630 
631 #ifdef EMFILE
632     case EMFILE:
633       return GST_SPARSE_FILE_IO_ERROR_TOO_MANY_OPEN_FILES;
634       break;
635 #endif
636 
637 #ifdef EADDRINUSE
638     case EADDRINUSE:
639       return GST_SPARSE_FILE_IO_ERROR_ADDRESS_IN_USE;
640       break;
641 #endif
642 
643 #ifdef EHOSTUNREACH
644     case EHOSTUNREACH:
645       return GST_SPARSE_FILE_IO_ERROR_HOST_UNREACHABLE;
646       break;
647 #endif
648 
649 #ifdef ENETUNREACH
650     case ENETUNREACH:
651       return GST_SPARSE_FILE_IO_ERROR_NETWORK_UNREACHABLE;
652       break;
653 #endif
654 
655 #ifdef ECONNREFUSED
656     case ECONNREFUSED:
657       return GST_SPARSE_FILE_IO_ERROR_CONNECTION_REFUSED;
658       break;
659 #endif
660 
661 #ifdef EPIPE
662     case EPIPE:
663       return GST_SPARSE_FILE_IO_ERROR_BROKEN_PIPE;
664       break;
665 #endif
666 
667     default:
668       return GST_SPARSE_FILE_IO_ERROR_FAILED;
669       break;
670   }
671 }
672