/* lfs_wrap: Wrapper code for reader functions, both internal and external. copyright 2010-2023 by the mpg123 project free software under the terms of the LGPL 2.1 see COPYING and AUTHORS files in distribution or http://mpg123.org initially written by Thomas Orgis, thanks to Guido Draheim for consulting and later manx for stirring it all up again This file used to wrap 32 bit off_t client API to 64 bit off_t API. Now it is mapping any user API with either off_t to portable int64_t API, while also providing the guts of that also via a wrapper handle. This keeps the actual read and seek implementation out of readers.c. See doc/LARGEFILE for the big picture. Note about file descriptors: We just assume that they are generally interchangeable between large and small file code... and that a large file descriptor will trigger errors when accessed with small file code where it may cause trouble (a really large file). */ #include "config.h" // Only activate the explicit largefile stuff here. The rest of the code shall // work with abstract 64 bit offsets, or just plain default off_t (possibly // using _FILE_OFFSET_BYTES magic). // Note that this macro does not influence normal off_t-using code. #ifdef LFS_LARGEFILE_64 #define _LARGEFILE64_SOURCE #endif // For correct MPG123_EXPORT. #include "abi_align.h" // Need the full header with non-portable API, for the bare mpg123_open*() // declarations. But no renaming shenanigans. #define MPG123_NO_LARGENAME #include "mpg123.h" #include "lfs_wrap.h" #include "abi_align.h" #include "compat.h" #include #include #include "debug.h" // We do not want to expose this publicly, but it is cleaner to have it also defined // as portable API to offer the legacy function wrapper over. It's an undocumented // function for int64_t arguments. int attribute_align_arg mpg123_position64( mpg123_handle *fr, int64_t no, int64_t buffsize , int64_t *current_frame, int64_t *frames_left , double *current_seconds, double *seconds_left ); /* I see that I will need custom data storage. Main use is for the replaced I/O later, but the seek table for small file offsets needs extra storage, too. */ // The wrapper handle for descriptor and handle I/O. // It is also used for storing a converted frame index. // Plain portable API I/O should not need it at all. #define IO_HANDLE64 0 /* no wrapping at all: client-provided callbacks */ #define IO_FD 1 /* default off_t callbacks */ #define IO_HANDLE 2 /* Wrapping over custom handle callbacks with off_t. */ #ifdef LFS_LARGEFILE_64 #define IO_FD_64 3 /* off64_t callbacks */ #define IO_HANDLE_64 4 /* ... with off64_t. */ #endif #define IO_INT_FD 5 /* Internal I/O using a file descriptor and wrappers defined here. */ struct wrap_data { /* Storage for small offset index table. */ #if SIZEOF_OFF_T == 4 off_t *indextable; // And ironically, another big offset table for mpg123_set_index_32. // I wand to avoid having to change a line of code in the internals. int64_t *set_indextable; #endif /* I/O handle stuff */ int iotype; // one of the above numbers /* Data for IO_FD variants. */ int fd; int my_fd; /* A descriptor that the wrapper code opened itself. */ #ifdef TIMEOUT_READ time_t timeout_sec; #endif void* handle; // for IO_HANDLE variants /* The actual callbacks from the outside. */ mpg123_ssize_t (*r_read) (int, void *, size_t); off_t (*r_lseek)(int, off_t, int); mpg123_ssize_t (*r_h_read)(void *, void *, size_t); off_t (*r_h_lseek)(void*, off_t, int); #if LFS_LARGEFILE_64 mpg123_ssize_t (*r_read_64) (int, void *, size_t); off64_t (*r_lseek_64)(int, off64_t, int); mpg123_ssize_t (*r_h_read_64)(void *, void *, size_t); off64_t (*r_h_lseek_64)(void*, off64_t, int); #endif void (*h_cleanup)(void*); }; /* Cleanup I/O part of the handle handle... but not deleting the wrapper handle itself. That is stored in the frame and only deleted on mpg123_delete(). */ static void wrap_io_cleanup(void *handle) { struct wrap_data *ioh = handle; debug("wrapper I/O cleanup"); if(ioh->iotype == IO_HANDLE #ifdef LFS_LARGEFILE_64 || ioh->iotype == IO_HANDLE_64 #endif ){ if(ioh->h_cleanup != NULL && ioh->handle != NULL) { mdebug("calling client handle cleanup %p", ioh->handle); ioh->h_cleanup(ioh->handle); } ioh->handle = NULL; } if(ioh->my_fd >= 0) { mdebug("closing my fd %d", ioh->my_fd); close(ioh->my_fd); ioh->my_fd = -1; } } /* Really finish off the handle... freeing all memory. */ void INT123_wrap_destroy(void *handle) { struct wrap_data *wh = handle; if(!wh) return; wrap_io_cleanup(handle); #if SIZEOF_OFF_T == 4 if(wh->indextable != NULL) free(wh->indextable); if(wh->set_indextable != NULL) free(wh->set_indextable); #endif free(wh); } /* More helper code... extract the special wrapper handle, possible allocate and initialize it. */ static struct wrap_data* wrap_get(mpg123_handle *mh, int force_alloc) { struct wrap_data* whd; void ** whd_ = INT123_wrap_handle(mh); if(whd_ == NULL) return NULL; /* Access the private storage inside the mpg123 handle. The real callback functions and handles are stored there. */ if(*whd_ == NULL && force_alloc) { /* Create a new one. */ *whd_ = malloc(sizeof(struct wrap_data)); if(*whd_ == NULL) { INT123_set_err(mh, MPG123_OUT_OF_MEM); return NULL; } whd = *whd_; #if SIZEOF_OFF_T == 4 whd->indextable = NULL; whd->set_indextable = NULL; #endif whd->iotype = IO_HANDLE64; // By default, the I/O path is not affected. whd->fd = -1; whd->my_fd = -1; whd->handle = NULL; whd->r_read = NULL; whd->r_lseek = NULL; whd->r_h_read = NULL; whd->r_h_lseek = NULL; #if LFS_LARGEFILE_64 whd->r_read_64 = NULL; whd->r_lseek_64 = NULL; whd->r_h_read_64 = NULL; whd->r_h_lseek_64 = NULL; #endif whd->h_cleanup = NULL; } else whd = *whd_; return whd; } /* After settling the data... start with some simple wrappers. */ // The fist block of wrappers is always present, using the native off_t width. // A second block mirrors that in case of sizeof(off_t)==4 with _32 suffix. // A third block follows if 64 bit off_t is available with _64 suffix, just aliasing // the int64_t functions. #define OFF_CONV(value, variable, handle) \ if((value) >= OFF_MIN && (value) <= OFF_MAX) \ variable = (off_t)(value); \ else return INT123_set_err(handle, MPG123_LFS_OVERFLOW); #define OFF_CONVP(value, varpointer, handle) \ if(varpointer){ OFF_CONV(value, *(varpointer), handle) } #define OFF_RETURN(value, handle) \ return ((value) >= OFF_MIN && (value) <= OFF_MAX) \ ? (off_t)(value) \ : INT123_set_err(handle, MPG123_LFS_OVERFLOW); int attribute_align_arg mpg123_framebyframe_decode(mpg123_handle *mh, off_t *num, unsigned char **audio, size_t *bytes) { int64_t fnum = 0; int ret = mpg123_framebyframe_decode64(mh, &fnum, audio, bytes); OFF_CONVP(fnum, num, mh) return ret; } int attribute_align_arg mpg123_decode_frame(mpg123_handle *mh, off_t *num, unsigned char **audio, size_t *bytes) { int64_t fnum = 0; int ret = mpg123_decode_frame64(mh, &fnum, audio, bytes); OFF_CONVP(fnum, num, mh) return ret; } off_t attribute_align_arg mpg123_timeframe(mpg123_handle *mh, double seconds) { int64_t b = mpg123_timeframe64(mh, seconds); OFF_RETURN(b, mh) } off_t attribute_align_arg mpg123_tell(mpg123_handle *mh) { int64_t pos = mpg123_tell64(mh); OFF_RETURN(pos, mh) } off_t attribute_align_arg mpg123_tellframe(mpg123_handle *mh) { int64_t frame = mpg123_tellframe64(mh); OFF_RETURN(frame, mh) } off_t attribute_align_arg mpg123_tell_stream(mpg123_handle *mh) { int64_t off = mpg123_tell_stream64(mh); OFF_RETURN(off, mh) } off_t attribute_align_arg mpg123_seek(mpg123_handle *mh, off_t sampleoff, int whence) { int64_t ret = mpg123_seek64(mh, (int64_t)sampleoff, whence); OFF_RETURN(ret, mh) } off_t attribute_align_arg mpg123_feedseek(mpg123_handle *mh, off_t sampleoff, int whence, off_t *input_offset) { int64_t inoff = 0; int64_t ret = mpg123_feedseek64(mh, (int64_t)sampleoff, whence, &inoff); OFF_CONVP(inoff, input_offset, mh) OFF_RETURN(ret, mh) } off_t attribute_align_arg mpg123_seek_frame(mpg123_handle *mh, off_t offset, int whence) { int64_t ret = mpg123_seek_frame64(mh, (int64_t)offset, whence); OFF_RETURN(ret, mh) } int attribute_align_arg mpg123_set_filesize(mpg123_handle *mh, off_t size) { return mpg123_set_filesize64(mh, (int64_t)size); } off_t attribute_align_arg mpg123_framelength(mpg123_handle *mh) { int64_t ret = mpg123_framelength64(mh); OFF_RETURN(ret, mh) } off_t attribute_align_arg mpg123_length(mpg123_handle *mh) { int64_t ret = mpg123_length64(mh); OFF_RETURN(ret, mh) } // Native off_t is either identical to int32_t or int64_t. // If the former, we create a copy of the index table. int attribute_align_arg mpg123_index(mpg123_handle *mh, off_t **offsets, off_t *step, size_t *fill) { #if SIZEOF_OFF_T == 8 return mpg123_index64(mh, (int64_t**)offsets, (int64_t*)step, fill); #else int err; int64_t largestep; int64_t *largeoffsets; struct wrap_data *whd; if(mh == NULL) return MPG123_BAD_HANDLE; if(offsets == NULL || step == NULL || fill == NULL) return INT123_set_err(mh, MPG123_BAD_INDEX_PAR); *fill = 0; // better safe than sorry whd = wrap_get(mh, 1); if(whd == NULL) return MPG123_ERR; err = mpg123_index64(mh, &largeoffsets, &largestep, fill); if(err != MPG123_OK) return err; /* For a _very_ large file, even the step could overflow. */ OFF_CONV(largestep, *step, mh); /* When there are no values stored, there is no table content to take care of. Table pointer does not matter. Mission completed. */ if(*fill == 0) return MPG123_OK; /* Construct a copy of the index to hand over to the small-minded client. */ *offsets = INT123_safe_realloc(whd->indextable, (*fill)*sizeof(int32_t)); if(*offsets == NULL) return INT123_set_err(mh, MPG123_OUT_OF_MEM); whd->indextable = *offsets; /* Elaborate conversion of each index value, with overflow check. */ for(size_t i=0; i<*fill; ++i) OFF_CONV(largeoffsets[i], whd->indextable[i], mh); /* If we came that far... there should be a valid copy of the table now. */ return MPG123_OK; #endif } int attribute_align_arg mpg123_set_index(mpg123_handle *mh, off_t *offsets, off_t step, size_t fill) { #if SIZEOF_OFF_T == 8 return mpg123_set_index64(mh, (int64_t*)offsets, (int64_t)step, fill); #else int err; struct wrap_data *whd; int64_t *indextmp; if(mh == NULL) return MPG123_BAD_HANDLE; whd = wrap_get(mh, 1); if(whd == NULL) return MPG123_ERR; if(fill > 0 && offsets == NULL) return INT123_set_err(mh, MPG123_BAD_INDEX_PAR); else { /* Expensive temporary storage... for staying outside at the API layer. */ indextmp = INT123_safe_realloc(whd->set_indextable, fill*sizeof(int64_t)); if(indextmp == NULL) return INT123_set_err(mh, MPG123_OUT_OF_MEM); whd->set_indextable = indextmp; /* Fill the large-file copy of the provided index, then feed it to mpg123. */ for(size_t i=0; iiotype) { case IO_FD: retgot = ioh->r_read(ioh->fd, buf, count); break; case IO_HANDLE: retgot = ioh->r_h_read(ioh->handle, buf, count); break; #ifdef LFS_LARGEFILE_64 case IO_FD_64: retgot = ioh->r_read_64(ioh->fd, buf, count); break; case IO_HANDLE_64: retgot = ioh->r_h_read_64(ioh->handle, buf, count); break; #endif default: error("Serious breakage - bad IO type in LFS wrapper!"); } if(got) *got = retgot > 0 ? (size_t)retgot : 0; return retgot >= 0 ? 0 : -1; } // One seek callback wrapping over all 4 client callback variants. static int64_t wrap_lseek(void *handle, int64_t offset, int whence) { struct wrap_data *ioh = handle; if( (ioh->iotype == IO_FD || ioh->iotype == IO_HANDLE) && (offset < OFF_MIN || offset > OFF_MAX) ) { errno = EOVERFLOW; return -1; } switch(ioh->iotype) { case IO_FD: return ioh->r_lseek(ioh->fd, (off_t)offset, whence); case IO_HANDLE: return ioh->r_h_lseek(ioh->handle, (off_t)offset, whence); #ifdef LFS_LARGEFILE_64 case IO_FD_64: return ioh->r_lseek_64(ioh->fd, offset, whence); case IO_HANDLE_64: return ioh->r_h_lseek_64(ioh->handle, offset, whence); #endif } error("Serious breakage - bad IO type in LFS wrapper!"); return -1; } // This is assuming an internally opened file, which usually will be // using 64 bit offsets. It keeps reading on on trivial interruptions. // I guess any file descriptor that matches the libc should work fine. static int internal_read64(void *handle, void *buf, size_t bytes, size_t *got_bytes) { int ret = 0; if(!handle || (!buf && bytes)) return -1; struct wrap_data* ioh = handle; int fd = ioh->fd; size_t got = 0; errno = 0; while(bytes) { #ifdef TIMEOUT_READ if(ioh->timeout_sec) { fd_set fds; tv.tv_sec = ioh->timeout_sec; tv.tv_usec = 0; FD_ZERO(&fds); FD_SET(fd, &fds); int sret = select(fd+1, &fds, NULL, NULL, &tv); if(sret < 1) { return -1; // timeout means error // communicate quietness flag? if(NOQUIET) error("stream timed out"); } } #endif errno = 0; ptrdiff_t part = read(fd, (char*)buf+got, bytes); if(part > 0) // == 0 is end of file { SATURATE_SUB(bytes, part, 0) SATURATE_ADD(got, part, SIZE_MAX) } else if(errno != EINTR && errno != EAGAIN #if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN) && errno != EWOULDBLOCK #endif ){ if(part < 0) ret = -1; break; } } if(got_bytes) *got_bytes = got; return ret; } static int64_t internal_lseek64(void *handle, int64_t offset, int whence) { struct wrap_data* ioh = handle; #ifdef LFS_LARGEFILE_64 return lseek64(ioh->fd, offset, whence); #else if(offset < OFF_MIN || offset > OFF_MAX) { errno = EOVERFLOW; return -1; } return lseek(ioh->fd, (off_t)offset, whence); #endif } int INT123_wrap_open(mpg123_handle *mh, void *handle, const char *path, int fd, long timeout, int quiet) { int force_alloc = (path || fd >= 0) ? 1 : 0; struct wrap_data *ioh = wrap_get(mh, force_alloc); if(ioh == NULL && force_alloc) return MPG123_ERR; if(!path && fd < 0) { if(!ioh || ioh->iotype == IO_HANDLE64) { mdebug("user-supplied 64 bit I/O on user-supplied handle %p", handle); return LFS_WRAP_NONE; } if(ioh->iotype == IO_HANDLE) { mdebug("wrapped user handle %p", handle); ioh->handle = handle; if(ioh->r_h_read && ioh->r_h_lseek) return mpg123_reader64(mh, wrap_read, wrap_lseek, wrap_io_cleanup); return INT123_set_err(mh, MPG123_NO_READER); } #ifdef LFS_LARGEFILE_64 if(ioh->iotype == IO_HANDLE_64) { mdebug("wrapped 64 bit user handle %p", handle); ioh->handle = handle; if(ioh->r_h_read_64 && ioh->r_h_lseek_64) return mpg123_reader64(mh, wrap_read, wrap_lseek, wrap_io_cleanup); return INT123_set_err(mh, MPG123_NO_READER); } #endif } if(path) { debug("opening path (providing fd)"); // Open the resource and store the descriptor for closing later. int flags=O_RDONLY; #ifdef O_BINARY flags |= O_BINARY; #endif #ifdef LFS_LARGEFILE_64 flags |= O_LARGEFILE; #endif errno = 0; ioh->my_fd = fd = INT123_compat_open(path, flags); if(fd < 0) { if(!quiet) error2("Cannot open file %s: %s", path, INT123_strerror(errno)); return INT123_set_err(mh, MPG123_BAD_FILE); } } if(fd >= 0) { mdebug("working with given fd %d", fd); ioh->fd = fd; // Prepared I/O using external callbacks. if(ioh->iotype == IO_FD) { debug("native fd callbacks"); if(ioh->r_read && ioh->r_lseek) return mpg123_reader64(mh, wrap_read, wrap_lseek, wrap_io_cleanup); return INT123_set_err(mh, MPG123_NO_READER); } #ifdef LFS_LARGEFILE_64 if(ioh->iotype == IO_FD_64) { debug("64 bit fd callbacks"); if(ioh->r_read_64 && ioh->r_lseek_64) return mpg123_reader64(mh, wrap_read, wrap_lseek, wrap_io_cleanup); return INT123_set_err(mh, MPG123_NO_READER); } debug("internal 64 bit I/O"); #else debug("internal 32-behind-64 bit I/O"); #endif // Doing our own thing using the given/just-opened descriptor. ioh->iotype = IO_INT_FD; #ifdef TIMEOUT_READ ioh->timeout_sec = (time_t)(timeout > 0 ? timeout : 0); if(ioh->timeout_sec > 0) { mdebug("timeout reader with %ld s", timeout); int flags; flags = fcntl(fd, F_GETFL); flags |= O_NONBLOCK; fcntl(fd, F_SETFL, flags); } #endif return mpg123_reader64(mh, internal_read64, internal_lseek64, wrap_io_cleanup); } return MPG123_ERR; } // So, native off_t reader replacement. // Defining a wrapper to the native read to be sure the prototype matches. // There are platforms where it is read(int, void*, unsigned int). // We know that we read small chunks where the difference does not matter. Could // apply specific hackery, use a common compat_read() (INT123_unintr_read()?) with system // specifics. static mpg123_ssize_t fallback_read(int fd, void *buf, size_t count) { return read(fd, buf, count); } /* Reader replacement prepares the hidden handle storage for next mpg123_open_fd() or plain mpg123_open(). */ int attribute_align_arg mpg123_replace_reader(mpg123_handle *mh, mpg123_ssize_t (*r_read) (int, void *, size_t), off_t (*r_lseek)(int, off_t, int) ) { struct wrap_data* ioh; if(mh == NULL) return MPG123_ERR; mpg123_close(mh); ioh = wrap_get(mh, 1); if(ioh == NULL) return MPG123_ERR; /* If both callbacks are NULL, switch totally to internal I/O, else just use fallback for at most half of them. */ if(r_read == NULL && r_lseek == NULL) { ioh->iotype = IO_INT_FD; ioh->fd = -1; ioh->r_read = NULL; ioh->r_lseek = NULL; } else { ioh->iotype = IO_FD; ioh->fd = -1; /* On next mpg123_open_fd(), this gets a value. */ ioh->r_read = r_read != NULL ? r_read : fallback_read; ioh->r_lseek = r_lseek != NULL ? r_lseek : lseek; } /* The real reader replacement will happen while opening. */ return MPG123_OK; } int attribute_align_arg mpg123_replace_reader_handle(mpg123_handle *mh, mpg123_ssize_t (*r_read) (void*, void *, size_t), off_t (*r_lseek)(void*, off_t, int), void (*cleanup)(void*)) { struct wrap_data* ioh; if(mh == NULL) return MPG123_ERR; mpg123_close(mh); ioh = wrap_get(mh, 1); if(ioh == NULL) return MPG123_ERR; ioh->iotype = IO_HANDLE; ioh->handle = NULL; ioh->r_h_read = r_read; ioh->r_h_lseek = r_lseek; ioh->h_cleanup = cleanup; /* The real reader replacement will happen while opening. */ return MPG123_OK; } #if SIZEOF_OFF_T == 4 int attribute_align_arg mpg123_replace_reader_32(mpg123_handle *mh, mpg123_ssize_t (*r_read) (int, void *, size_t), off_t (*r_lseek)(int, off_t, int) ) { return mpg123_replace_reader(mh, r_read, r_lseek); } int attribute_align_arg mpg123_replace_reader_handle_32(mpg123_handle *mh, mpg123_ssize_t (*r_read) (void*, void *, size_t), off_t (*r_lseek)(void*, off_t, int), void (*cleanup)(void*)) { return mpg123_replace_reader_handle(mh, r_read, r_lseek, cleanup); } #endif #ifdef LFS_LARGEFILE_64 int attribute_align_arg mpg123_replace_reader_64(mpg123_handle *mh, mpg123_ssize_t (*r_read) (int, void *, size_t), off64_t (*r_lseek)(int, off64_t, int) ) { struct wrap_data* ioh; if(mh == NULL) return MPG123_ERR; mpg123_close(mh); ioh = wrap_get(mh, 1); if(ioh == NULL) return MPG123_ERR; /* If both callbacks are NULL, switch totally to internal I/O, else just use fallback for at most half of them. */ if(r_read == NULL && r_lseek == NULL) { ioh->iotype = IO_INT_FD; ioh->fd = -1; ioh->r_read_64 = NULL; ioh->r_lseek_64 = NULL; } else { ioh->iotype = IO_FD_64; ioh->fd = -1; /* On next mpg123_open_fd(), this gets a value. */ ioh->r_read_64 = r_read != NULL ? r_read : fallback_read; ioh->r_lseek_64 = r_lseek != NULL ? r_lseek : lseek64; } /* The real reader replacement will happen while opening. */ return MPG123_OK; } int attribute_align_arg mpg123_replace_reader_handle_64(mpg123_handle *mh, mpg123_ssize_t (*r_read) (void*, void *, size_t), off64_t (*r_lseek)(void*, off64_t, int), void (*cleanup)(void*)) { struct wrap_data* ioh; if(mh == NULL) return MPG123_ERR; mpg123_close(mh); ioh = wrap_get(mh, 1); if(ioh == NULL) return MPG123_ERR; ioh->iotype = IO_HANDLE_64; ioh->handle = NULL; ioh->r_h_read_64 = r_read; ioh->r_h_lseek_64 = r_lseek; ioh->h_cleanup = cleanup; /* The real reader replacement will happen while opening. */ return MPG123_OK; } #endif