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