/* GStreamer * Copyright (C) 2014 Wim Taymans * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include "gstsparsefile.h" #ifdef G_OS_WIN32 #include /* lseek, open, close, read */ #undef fseek #define fseek _fseeki64 #undef off_t #define off_t guint64 #else #include #endif #ifdef HAVE_FSEEKO #define FSEEK_FILE(file,offset) (fseeko (file, (off_t) offset, SEEK_SET) != 0) #elif defined (G_OS_UNIX) #define FSEEK_FILE(file,offset) (lseek (fileno (file), (off_t) offset, SEEK_SET) == (off_t) -1) #else #define FSEEK_FILE(file,offset) (fseek (file, offset, SEEK_SET) != 0) #endif #define GST_SPARSE_FILE_IO_ERROR \ g_quark_from_static_string("gst-sparse-file-io-error-quark") static GstSparseFileIOErrorEnum gst_sparse_file_io_error_from_errno (gint err_no); typedef struct _GstSparseRange GstSparseRange; struct _GstSparseRange { GstSparseRange *next; gsize start; gsize stop; }; #define RANGE_CONTAINS(r,o) ((r)->start <= (o) && (r)->stop > (o)) struct _GstSparseFile { gint fd; FILE *file; gsize current_pos; gboolean was_writing; GstSparseRange *ranges; guint n_ranges; GstSparseRange *write_range; GstSparseRange *read_range; }; static GstSparseRange * get_write_range (GstSparseFile * file, gsize offset) { GstSparseRange *next, *prev, *result = NULL; if (file->write_range && file->write_range->stop == offset) return file->write_range; prev = NULL; next = file->ranges; while (next) { if (next->start > offset) break; if (next->stop >= offset) { result = next; break; } prev = next; next = next->next; } if (result == NULL) { result = g_slice_new0 (GstSparseRange); result->start = offset; result->stop = offset; result->next = next; if (prev) prev->next = result; else file->ranges = result; file->write_range = result; file->read_range = NULL; file->n_ranges++; } return result; } static GstSparseRange * get_read_range (GstSparseFile * file, gsize offset, gsize count) { GstSparseRange *walk, *result = NULL; if (file->read_range && RANGE_CONTAINS (file->read_range, offset)) return file->read_range; for (walk = file->ranges; walk; walk = walk->next) { if (walk->start > offset) break; if (walk->stop >= offset + count) { result = walk; break; } } return result; } /** * gst_sparse_file_new: * * Make a new #GstSparseFile * * Returns: a new #GstSparseFile, gst_sparse_file_free() after usage. * * Since: 1.4 */ GstSparseFile * gst_sparse_file_new (void) { GstSparseFile *result; result = g_slice_new0 (GstSparseFile); result->current_pos = 0; result->ranges = NULL; result->n_ranges = 0; return result; } /** * gst_sparse_file_set_fd: * @file: a #GstSparseFile * @fd: a file descriptor * * Store the data for @file in the file represented with @fd. * * Returns: %TRUE when @fd could be set * * Since: 1.4 */ gboolean gst_sparse_file_set_fd (GstSparseFile * file, gint fd) { g_return_val_if_fail (file != NULL, FALSE); g_return_val_if_fail (fd != 0, FALSE); file->file = fdopen (fd, "wb+"); file->fd = fd; return file->file != NULL; } /** * gst_sparse_file_clear: * @file: a #GstSparseFile * * Clear all the ranges in @file. */ void gst_sparse_file_clear (GstSparseFile * file) { g_return_if_fail (file != NULL); g_slice_free_chain (GstSparseRange, file->ranges, next); file->current_pos = 0; file->ranges = NULL; file->n_ranges = 0; file->was_writing = FALSE; } /** * gst_sparse_file_free: * @file: a #GstSparseFile * * Free the memory used by @file. * * Since: 1.4 */ void gst_sparse_file_free (GstSparseFile * file) { g_return_if_fail (file != NULL); if (file->file) { fflush (file->file); fclose (file->file); } g_slice_free_chain (GstSparseRange, file->ranges, next); g_slice_free (GstSparseFile, file); } /** * gst_sparse_file_write: * @file: a #GstSparseFile * @offset: the offset * @data: the data * @count: amount of bytes * @available: amount of bytes already available * @error: a #GError * * Write @count bytes from @data to @file at @offset. * * If @available is not %NULL, it will be updated with the amount of * data already available after the last written byte. * * Returns: The number of bytes written or 0 on error. * * Since: 1.4 */ gsize gst_sparse_file_write (GstSparseFile * file, gsize offset, gconstpointer data, gsize count, gsize * available, GError ** error) { GstSparseRange *range, *next; gsize stop; g_return_val_if_fail (file != NULL, 0); g_return_val_if_fail (count != 0, 0); if (file->file) { if (file->current_pos != offset) { GST_DEBUG ("seeking to %" G_GSIZE_FORMAT, offset); if (FSEEK_FILE (file->file, offset)) goto error; } else if (!file->was_writing) { fflush (file->file); } file->was_writing = TRUE; if (fwrite (data, count, 1, file->file) != 1) goto error; } file->current_pos = offset + count; /* update the new stop position in the range */ range = get_write_range (file, offset); stop = offset + count; range->stop = MAX (range->stop, stop); /* see if we can merge with next region */ while ((next = range->next)) { if (next->start > range->stop) break; GST_DEBUG ("merging range %" G_GSIZE_FORMAT "-%" G_GSIZE_FORMAT ", next %" G_GSIZE_FORMAT "-%" G_GSIZE_FORMAT, range->start, range->stop, next->start, next->stop); range->stop = MAX (next->stop, range->stop); range->next = next->next; if (file->write_range == next) file->write_range = NULL; if (file->read_range == next) file->read_range = NULL; g_slice_free (GstSparseRange, next); file->n_ranges--; } if (available) *available = range->stop - stop; return count; /* ERRORS */ error: { g_set_error (error, GST_SPARSE_FILE_IO_ERROR, gst_sparse_file_io_error_from_errno (errno), "Error writing file: %s", g_strerror (errno)); return 0; } } /** * gst_sparse_file_read: * @file: a #GstSparseFile * @offset: the offset * @data: the data * @count: amount of bytes * @remaining: amount of bytes remaining * @error: a #GError * * Read @count bytes from @file at @offset into @data. * * On error, @error will be set. If there are no @count bytes available * at @offset, %GST_SPARSE_FILE_IO_ERROR_WOULD_BLOCK is returned. * * @remaining will be set to the amount of bytes remaining in the read * range. * * Returns: The number of bytes read of 0 on error. * * Since: 1.4 */ gsize gst_sparse_file_read (GstSparseFile * file, gsize offset, gpointer data, gsize count, gsize * remaining, GError ** error) { GstSparseRange *range; gsize res = 0; g_return_val_if_fail (file != NULL, 0); g_return_val_if_fail (count != 0, 0); if ((range = get_read_range (file, offset, count)) == NULL) goto no_range; if (file->file) { if (file->current_pos != offset) { GST_DEBUG ("seeking from %" G_GSIZE_FORMAT " to %" G_GSIZE_FORMAT, file->current_pos, offset); if (FSEEK_FILE (file->file, offset)) goto error; } else if (file->was_writing) { fflush (file->file); } file->was_writing = FALSE; res = fread (data, 1, count, file->file); if (G_UNLIKELY (res < count)) goto error; } file->current_pos = offset + res; if (remaining) *remaining = range->stop - file->current_pos; return count; /* ERRORS */ no_range: { g_set_error_literal (error, GST_SPARSE_FILE_IO_ERROR, GST_SPARSE_FILE_IO_ERROR_WOULD_BLOCK, "Offset not written to file yet"); return 0; } error: { if (ferror (file->file)) { g_set_error (error, GST_SPARSE_FILE_IO_ERROR, gst_sparse_file_io_error_from_errno (errno), "Error reading file: %s", g_strerror (errno)); } else if (feof (file->file)) { return res; } return 0; } } /** * gst_sparse_file_n_ranges: * @file: a #GstSparseFile * * Get the number of ranges that are written in @file. * * Returns: the number of written ranges. * * Since: 1.4 */ guint gst_sparse_file_n_ranges (GstSparseFile * file) { g_return_val_if_fail (file != NULL, 0); return file->n_ranges; } /** * gst_sparse_file_get_range_before: * @file: a #GstSparseFile * @offset: the range offset * @start: result start * @stop: result stop * * Get the start and stop offset of the range containing data before or * including @offset. * * Returns: %TRUE if the range with data before @offset exists. * * Since: 1.4 */ gboolean gst_sparse_file_get_range_before (GstSparseFile * file, gsize offset, gsize * start, gsize * stop) { GstSparseRange *walk, *result = NULL; g_return_val_if_fail (file != NULL, FALSE); for (walk = file->ranges; walk; walk = walk->next) { GST_DEBUG ("start %" G_GSIZE_FORMAT " > %" G_GSIZE_FORMAT, walk->stop, offset); if (walk->start > offset) break; if (walk->start <= offset) result = walk; } if (result) { if (start) *start = result->start; if (stop) *stop = result->stop; } return result != NULL; } /** * gst_sparse_file_get_range_after: * @file: a #GstSparseFile * @offset: the range offset * @start: result start * @stop: result stop * * Get the start and stop offset of the range containing data after or * including @offset. * * Returns: %TRUE if the range with data after @offset exists. * * Since: 1.4 */ gboolean gst_sparse_file_get_range_after (GstSparseFile * file, gsize offset, gsize * start, gsize * stop) { GstSparseRange *walk, *result = NULL; g_return_val_if_fail (file != NULL, FALSE); for (walk = file->ranges; walk; walk = walk->next) { GST_DEBUG ("stop %" G_GSIZE_FORMAT " > %" G_GSIZE_FORMAT, walk->stop, offset); if (walk->stop > offset) { result = walk; break; } } if (result) { if (start) *start = result->start; if (stop) *stop = result->stop; } return result != NULL; } /* we don't want to rely on libgio just for g_io_error_from_errno() */ static GstSparseFileIOErrorEnum gst_sparse_file_io_error_from_errno (gint err_no) { switch (err_no) { #ifdef EEXIST case EEXIST: return GST_SPARSE_FILE_IO_ERROR_EXISTS; break; #endif #ifdef EISDIR case EISDIR: return GST_SPARSE_FILE_IO_ERROR_IS_DIRECTORY; break; #endif #ifdef EACCES case EACCES: return GST_SPARSE_FILE_IO_ERROR_PERMISSION_DENIED; break; #endif #ifdef ENAMETOOLONG case ENAMETOOLONG: return GST_SPARSE_FILE_IO_ERROR_FILENAME_TOO_LONG; break; #endif #ifdef ENOENT case ENOENT: return GST_SPARSE_FILE_IO_ERROR_NOT_FOUND; break; #endif #ifdef ENOTDIR case ENOTDIR: return GST_SPARSE_FILE_IO_ERROR_NOT_DIRECTORY; break; #endif #ifdef EROFS case EROFS: return GST_SPARSE_FILE_IO_ERROR_READ_ONLY; break; #endif #ifdef ELOOP case ELOOP: return GST_SPARSE_FILE_IO_ERROR_TOO_MANY_LINKS; break; #endif #ifdef ENOSPC case ENOSPC: return GST_SPARSE_FILE_IO_ERROR_NO_SPACE; break; #endif #ifdef ENOMEM case ENOMEM: return GST_SPARSE_FILE_IO_ERROR_NO_SPACE; break; #endif #ifdef EINVAL case EINVAL: return GST_SPARSE_FILE_IO_ERROR_INVALID_ARGUMENT; break; #endif #ifdef EPERM case EPERM: return GST_SPARSE_FILE_IO_ERROR_PERMISSION_DENIED; break; #endif #ifdef ECANCELED case ECANCELED: return GST_SPARSE_FILE_IO_ERROR_CANCELLED; break; #endif /* ENOTEMPTY == EEXIST on AIX for backward compatibility reasons */ #if defined (ENOTEMPTY) && (!defined (EEXIST) || (ENOTEMPTY != EEXIST)) case ENOTEMPTY: return GST_SPARSE_FILE_IO_ERROR_NOT_EMPTY; break; #endif #ifdef ENOTSUP case ENOTSUP: return GST_SPARSE_FILE_IO_ERROR_NOT_SUPPORTED; break; #endif /* EOPNOTSUPP == ENOTSUP on Linux, but POSIX considers them distinct */ #if defined (EOPNOTSUPP) && (!defined (ENOTSUP) || (EOPNOTSUPP != ENOTSUP)) case EOPNOTSUPP: return GST_SPARSE_FILE_IO_ERROR_NOT_SUPPORTED; break; #endif #ifdef EPROTONOSUPPORT case EPROTONOSUPPORT: return GST_SPARSE_FILE_IO_ERROR_NOT_SUPPORTED; break; #endif #ifdef ESOCKTNOSUPPORT case ESOCKTNOSUPPORT: return GST_SPARSE_FILE_IO_ERROR_NOT_SUPPORTED; break; #endif #ifdef EPFNOSUPPORT case EPFNOSUPPORT: return GST_SPARSE_FILE_IO_ERROR_NOT_SUPPORTED; break; #endif #ifdef EAFNOSUPPORT case EAFNOSUPPORT: return GST_SPARSE_FILE_IO_ERROR_NOT_SUPPORTED; break; #endif #ifdef ETIMEDOUT case ETIMEDOUT: return GST_SPARSE_FILE_IO_ERROR_TIMED_OUT; break; #endif #ifdef EBUSY case EBUSY: return GST_SPARSE_FILE_IO_ERROR_BUSY; break; #endif #ifdef EWOULDBLOCK case EWOULDBLOCK: return GST_SPARSE_FILE_IO_ERROR_WOULD_BLOCK; break; #endif /* EWOULDBLOCK == EAGAIN on most systems, but POSIX considers them distinct */ #if defined (EAGAIN) && (!defined (EWOULDBLOCK) || (EWOULDBLOCK != EAGAIN)) case EAGAIN: return GST_SPARSE_FILE_IO_ERROR_WOULD_BLOCK; break; #endif #ifdef EMFILE case EMFILE: return GST_SPARSE_FILE_IO_ERROR_TOO_MANY_OPEN_FILES; break; #endif #ifdef EADDRINUSE case EADDRINUSE: return GST_SPARSE_FILE_IO_ERROR_ADDRESS_IN_USE; break; #endif #ifdef EHOSTUNREACH case EHOSTUNREACH: return GST_SPARSE_FILE_IO_ERROR_HOST_UNREACHABLE; break; #endif #ifdef ENETUNREACH case ENETUNREACH: return GST_SPARSE_FILE_IO_ERROR_NETWORK_UNREACHABLE; break; #endif #ifdef ECONNREFUSED case ECONNREFUSED: return GST_SPARSE_FILE_IO_ERROR_CONNECTION_REFUSED; break; #endif #ifdef EPIPE case EPIPE: return GST_SPARSE_FILE_IO_ERROR_BROKEN_PIPE; break; #endif default: return GST_SPARSE_FILE_IO_ERROR_FAILED; break; } }