Commit 24cb5eae authored by Timothy B. Terriberry's avatar Timothy B. Terriberry
Browse files

Fix seekability detection on win32.

The seeking functions on Windows internally dispatch to
 SetFilePointer(), whose behavior is undefined if you do not call
 it on "a file stored on a seeking device".
Check the type of file when it is opened and manually force seeks
 to fail if we do not have such a file.

Thanks to L W Anhonen for the report and Mark Harris for pointing
 out the solution used by opus-tools to avoid this problem.
parent 34f945bb
......@@ -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){
......
......@@ -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;
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment