diff --git a/src/opusfile.c b/src/opusfile.c index 8b000a2c58b222363417cee14a0f89ae60c1f7ae..e8eaada57604c671e47b0cf85d332878f33fcb60 100644 --- a/src/opusfile.c +++ b/src/opusfile.c @@ -1531,7 +1531,9 @@ static int op_open1(OggOpusFile *_of, ogg_sync_wrote(&_of->oy,(long)_initial_bytes); } /*Can we seek? - Stevens suggests the seek test is portable.*/ + Stevens suggests the seek test is portable. + It's actually not for files on win32, but we address that by fixing it in + our callback implementation (see stream.c).*/ seekable=_cb->seek!=NULL&&(*_cb->seek)(_stream,0,SEEK_CUR)!=-1; /*If seek is implemented, tell must also be implemented.*/ if(seekable){ diff --git a/src/stream.c b/src/stream.c index 8520ff59cc6597342a563b9b0abbbef49858b7b7..aa9297e8a20c34717fc4428d7229866760157452 100644 --- a/src/stream.c +++ b/src/stream.c @@ -227,6 +227,57 @@ static wchar_t *op_utf8_to_utf16(const char *_src){ return dst; } +/*fsetpos() internally dispatches to the win32 API call SetFilePointer(). + According to SetFilePointer()'s documentation [0], the behavior is + undefined if you do not call it on "a file stored on a seeking device". + However, none of the MSVCRT seeking functions verify what kind of file is + being used before calling it (which I believe is a bug, since they are + supposed to fail and return an error, but it is a bug that has been there + for multiple decades now). + In practice, SetFilePointer() appears to succeed for things like stdin, + even when you are not just piping in a regular file, which prevents the use + of this API to determine whether it is possible to seek in a file at all. + Therefore, we take the approach recommended by the SetFilePointer() + documentation and confirm the type of file using GetFileType() first. + We do this once, when the file is opened, and return the corresponding + callback in order to avoid an extra win32 API call on every seek in the + common case. + Hopefully the return value of GetFileType() cannot actually change for the + lifetime of a file handle. + [0] https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-setfilepointer +*/ +static int op_fseek_fail(void *_stream,opus_int64 _offset,int _whence){ + (void)_stream; + (void)_offset; + (void)_whence; + return -1; +} + +static const OpusFileCallbacks OP_UNSEEKABLE_FILE_CALLBACKS={ + op_fread, + op_fseek_fail, + op_ftell, + (op_close_func)fclose +}; + +# define WIN32_LEAN_AND_MEAN +# define WIN32_EXTRA_LEAN +# include <windows.h> + +static const OpusFileCallbacks *op_get_file_callbacks(FILE *_fp){ + intptr_t h_file; + h_file=_get_osfhandle(_fileno(_fp)); + if(h_file!=-1 + &&(GetFileType((HANDLE)h_file)&~FILE_TYPE_REMOTE)==FILE_TYPE_DISK){ + return &OP_FILE_CALLBACKS; + } + return &OP_UNSEEKABLE_FILE_CALLBACKS; +} +#else +static const OpusFileCallbacks *op_get_file_callbacks(FILE *_fp){ + (void)_fp; + return &OP_FILE_CALLBACKS; +} #endif void *op_fopen(OpusFileCallbacks *_cb,const char *_path,const char *_mode){ @@ -247,14 +298,14 @@ void *op_fopen(OpusFileCallbacks *_cb,const char *_path,const char *_mode){ _ogg_free(wpath); } #endif - if(fp!=NULL)*_cb=*&OP_FILE_CALLBACKS; + if(fp!=NULL)*_cb=*op_get_file_callbacks(fp); return fp; } void *op_fdopen(OpusFileCallbacks *_cb,int _fd,const char *_mode){ FILE *fp; fp=fdopen(_fd,_mode); - if(fp!=NULL)*_cb=*&OP_FILE_CALLBACKS; + if(fp!=NULL)*_cb=*op_get_file_callbacks(fp); return fp; } @@ -277,7 +328,7 @@ void *op_freopen(OpusFileCallbacks *_cb,const char *_path,const char *_mode, _ogg_free(wpath); } #endif - if(fp!=NULL)*_cb=*&OP_FILE_CALLBACKS; + if(fp!=NULL)*_cb=*op_get_file_callbacks(fp); return fp; }