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