reactos/sdk/lib/ucrt/stdio/fseek.cpp
2025-01-16 14:18:53 +02:00

248 lines
7.4 KiB
C++

//
// fseek.cpp
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// Defines the fseek() family of functions, which repositions a file pointer to
// a new place in a stream.
//
#include <corecrt_internal_stdio.h>
#include <corecrt_internal_ptd_propagation.h>
#define ENABLE_INTSAFE_SIGNED_FUNCTIONS
#include <intsafe.h>
// If a binary mode stream is open for reading and the target of the requested
// seek is within the stream buffer, we can simply adjust the buffer pointer to
// the new location. This allows us to avoid flushing the buffer. If the user
// has set a large buffer on the stream and performs frequent seeks that are
// likely to result in targets within the buffer, this is a huge performance win.
// This function handles the most common cases.
static bool __cdecl common_fseek_binary_mode_read_only_fast_track_nolock(
__crt_stdio_stream const stream,
__int64 offset,
int whence
) throw()
{
// This fast-track path does not handle the seek-from-end case (this is not
// nearly as commonly used as seeking from the beginning or from the current
// position).
if (whence == SEEK_END)
{
return false;
}
// This fast-track path is only useful if the stream is buffered.
if (!stream.has_any_buffer())
{
return false;
}
// This fast-track path requires a stream opened only for reading. It may
// be possible to handle streams opened for writing or update similarly;
// further investigation would be required.
if (stream.has_any_of(_IOWRITE | _IOUPDATE))
{
return false;
}
// The ftell function handles a case where _cnt is negative. It isn't clear
// why _cnt may be negative, so if _cnt is negative, fall back to the
// expensive path. If the _cnt is zero, do not assume that the buffer
// contents contain the immediately preceding file content; data may have
// been read from the file bypassing the buffer. Fall back to the expensive
// path to be sure.
if (stream->_cnt <= 0)
{
return false;
}
// This fast-track path requires a binary mode file handle. When text mode
// or UTF transformations are enabled, the contents of the buffer do not
// exactly match the contents of the underlying file.
int const fh = stream.lowio_handle();
if ((_osfile(fh) & FTEXT) != 0 || _textmode(fh) != __crt_lowio_text_mode::ansi)
{
return false;
}
// Handle the SEEK_SET case by transforming the SEEK_SET offset into a
// SEEK_CUR offset:
if (whence == SEEK_SET)
{
__int64 const lowio_position = _lseeki64_nolock(fh, 0, SEEK_CUR);
if (lowio_position < 0)
{
return false;
}
__int64 const stdio_position = lowio_position - stream->_cnt;
if (FAILED(LongLongSub(offset, stdio_position, &offset)))
{
return false;
}
whence = SEEK_CUR;
}
// Compute the maximum number of bytes that we can seek in each direction
// within the buffer and verify that the requested offset is within that
// range. Note that the minimum reverse seek is a negative value.
__int64 const minimum_reverse_seek = -(stream->_ptr - stream->_base);
__int64 const maximum_forward_seek = stream->_cnt;
bool const seek_is_within_buffer = minimum_reverse_seek <= offset && offset <= maximum_forward_seek;
if (!seek_is_within_buffer)
{
return false;
}
stream->_ptr += offset;
// Note that the cast here is safe: The buffer will never be larger than
// INT_MAX bytes in size. The setvbuf function validates this constraint.
stream->_cnt -= static_cast<int>(offset);
return true;
}
static int __cdecl common_fseek_nolock(
__crt_stdio_stream const stream,
__int64 offset,
int whence,
__crt_cached_ptd_host& ptd
) throw()
{
if (!stream.is_in_use())
{
ptd.get_errno().set(EINVAL);
return -1;
}
stream.unset_flags(_IOEOF);
if (common_fseek_binary_mode_read_only_fast_track_nolock(stream, offset, whence))
{
return 0;
}
// If seeking relative to the current location, then convert to a seek
// relative to the beginning of the file. This accounts for buffering,
// etc., by letting fseek() tell us where we are:
if (whence == SEEK_CUR)
{
offset += _ftelli64_nolock_internal(stream.public_stream(), ptd);
whence = SEEK_SET;
}
__acrt_stdio_flush_nolock(stream.public_stream(), ptd);
// If the stream is opened in update mode and is currently in use for reading,
// the buffer must be abandoned to ensure consistency when transitioning from
// reading to writing.
// __acrt_stdio_flush_nolock will not reset the buffer when _IOWRITE flag
// is not set.
__acrt_stdio_reset_buffer(stream);
// If the file was opened for read/write, clear flags since we don't know
// what the user will do next with the file. If the file was opened for
// read only access, decrease the _bufsiz so that the next call to
// __acrt_stdio_refill_and_read_{narrow,wide}_nolock won't cost quite so
// much:
if (stream.has_all_of(_IOUPDATE))
{
stream.unset_flags(_IOWRITE | _IOREAD);
}
else if (stream.has_all_of(_IOREAD | _IOBUFFER_CRT) && !stream.has_any_of(_IOBUFFER_SETVBUF))
{
stream->_bufsiz = _SMALL_BUFSIZ;
}
if (_lseeki64_nolock_internal(stream.lowio_handle(), offset, whence, ptd) == -1)
{
return -1;
}
return 0;
}
// Repositions the file pointer of a stream to the specified location, relative
// to 'whence', which can be SEEK_SET (the beginning of the file), SEEK_CUR (the
// current pointer position), or SEEK_END (the end of the file). The offset may
// be negative. Returns 0 on success; returns -1 and sets errno on failure.
static int __cdecl common_fseek(
__crt_stdio_stream const stream,
__int64 const offset,
int const whence,
__crt_cached_ptd_host& ptd
) throw()
{
_UCRT_VALIDATE_RETURN(ptd, stream.valid(), EINVAL, -1);
_UCRT_VALIDATE_RETURN(ptd, whence == SEEK_SET || whence == SEEK_CUR || whence == SEEK_END, EINVAL, -1);
int return_value = -1;
_lock_file(stream.public_stream());
__try
{
return_value = common_fseek_nolock(stream, offset, whence, ptd);
}
__finally
{
_unlock_file(stream.public_stream());
}
__endtry
return return_value;
}
extern "C" int __cdecl fseek(
FILE* const public_stream,
long const offset,
int const whence
)
{
__crt_cached_ptd_host ptd;
return common_fseek(__crt_stdio_stream(public_stream), offset, whence, ptd);
}
extern "C" int __cdecl _fseek_nolock(
FILE* const public_stream,
long const offset,
int const whence
)
{
__crt_cached_ptd_host ptd;
return common_fseek_nolock(__crt_stdio_stream(public_stream), offset, whence, ptd);
}
extern "C" int __cdecl _fseeki64(
FILE* const public_stream,
__int64 const offset,
int const whence
)
{
__crt_cached_ptd_host ptd;
return common_fseek(__crt_stdio_stream(public_stream), offset, whence, ptd);
}
extern "C" int __cdecl _fseeki64_nolock(
FILE* const public_stream,
__int64 const offset,
int const whence
)
{
__crt_cached_ptd_host ptd;
return common_fseek_nolock(__crt_stdio_stream(public_stream), offset, whence, ptd);
}