blob: be45417f52fb648d8b0cc20e4ecb162cbffdbe78 [file]
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _WIN32
// Make `fseeko()` and `ftello()` available.
#if !defined(_XOPEN_SOURCE) || _XOPEN_SOURCE < 500
#undef _XOPEN_SOURCE
#define _XOPEN_SOURCE 500
#endif
// Make `off_t` 64-bit even on 32-bit systems.
#undef _FILE_OFFSET_BITS
#define _FILE_OFFSET_BITS 64
#endif
#include "riegeli/bytes/cfile_reader.h"
#ifdef _WIN32
#include <fcntl.h>
#include <io.h>
#endif
#include <stddef.h>
#include <stdio.h>
#include <cerrno>
#include <limits>
#include <optional>
#include <type_traits>
#include <utility>
#include "absl/base/dynamic_annotations.h"
#include "absl/base/optimization.h"
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "riegeli/base/arithmetic.h"
#include "riegeli/base/assert.h"
#include "riegeli/base/global.h"
#include "riegeli/base/status.h"
#include "riegeli/base/types.h"
#include "riegeli/bytes/buffered_reader.h"
#include "riegeli/bytes/cfile_internal_for_cc.h"
namespace riegeli {
namespace {
// There is no portable way to apply efficient buffering over `FILE` while
// avoiding reading ahead more than eventually needed, because `fread()`
// performs multiple low level reads until the whole requested length is read.
//
// To solve this, `AvailableLength()` tries to determine how much data is
// available in the buffer of the `FILE` in a glibc-specific way, with a
// portable fallback.
template <typename DependentFILE, typename Enable = void>
struct HasAvailableLength : std::false_type {};
template <typename DependentFILE>
struct HasAvailableLength<
DependentFILE,
std::void_t<decltype(std::declval<DependentFILE>()._IO_read_end -
std::declval<DependentFILE>()._IO_read_ptr)>>
: std::true_type {};
template <typename DependentFILE>
inline size_t AvailableLength(DependentFILE* src) {
if constexpr (HasAvailableLength<DependentFILE>::value) {
// Msan does not properly track initialization performed by precompiled
// libraries.
ABSL_ANNOTATE_MEMORY_IS_INITIALIZED(src, sizeof(DependentFILE));
return PtrDistance(src->_IO_read_ptr, src->_IO_read_end);
} else {
return 0;
}
}
} // namespace
const Position CFileReaderBase::kMaxPosition =
Position{std::numeric_limits<cfile_internal::Offset>::max()};
void CFileReaderBase::Initialize(FILE* src, const Options& options) {
RIEGELI_ASSERT_NE(src, nullptr)
<< "Failed precondition of CFileReader: null FILE pointer";
InitializePos(src, options
#ifdef _WIN32
,
/*mode_was_passed_to_fopen=*/false
#endif
);
}
void CFileReaderBase::InitializePos(FILE* src, const Options& options
#ifdef _WIN32
,
bool mode_was_passed_to_fopen
#endif
) {
RIEGELI_ASSERT(!supports_random_access_)
<< "Failed precondition of CFileReaderBase::InitializePos(): "
"supports_random_access_ not reset";
RIEGELI_ASSERT_OK(random_access_status_)
<< "Failed precondition of CFileReaderBase::InitializePos(): "
"random_access_status_ not reset";
#ifdef _WIN32
RIEGELI_ASSERT_EQ(original_mode_, std::nullopt)
<< "Failed precondition of CFileReaderBase::InitializePos(): "
"original_mode_ not reset";
#endif
if (ABSL_PREDICT_FALSE(ferror(src))) {
FailOperation("FILE");
return;
}
#ifdef _WIN32
int text_mode = file_internal::GetTextAsFlags(options.mode());
if (!mode_was_passed_to_fopen && text_mode != 0) {
const int fd = _fileno(src);
if (ABSL_PREDICT_FALSE(fd < 0)) {
FailOperation("_fileno()");
return;
}
const int original_mode = _setmode(fd, text_mode);
if (ABSL_PREDICT_FALSE(original_mode < 0)) {
FailOperation("_setmode()");
return;
}
original_mode_ = original_mode;
}
#endif // _WIN32
std::optional<Position> assumed_pos = options.assumed_pos();
#ifdef _WIN32
if (assumed_pos == std::nullopt) {
if (text_mode == 0) {
const int fd = _fileno(src);
if (ABSL_PREDICT_FALSE(fd < 0)) {
FailOperation("_fileno()");
return;
}
// There is no `_getmode()`, but `_setmode()` returns the previous mode.
text_mode = _setmode(fd, _O_BINARY);
if (ABSL_PREDICT_FALSE(text_mode < 0)) {
FailOperation("_setmode()");
return;
}
if (ABSL_PREDICT_FALSE(_setmode(fd, text_mode) < 0)) {
FailOperation("_setmode()");
return;
}
}
if (text_mode != _O_BINARY) assumed_pos = 0;
}
#endif // _WIN32
if (assumed_pos != std::nullopt) {
if (ABSL_PREDICT_FALSE(*assumed_pos > kMaxPosition)) {
FailOverflow();
return;
}
set_limit_pos(*assumed_pos);
// `supports_random_access_` is left as `false`.
random_access_status_ = Global([] {
return absl::UnimplementedError(
"CFileReaderBase::Options::assumed_pos() excludes random access");
});
} else {
const cfile_internal::Offset file_pos = cfile_internal::FTell(src);
if (file_pos < 0) {
// Random access is not supported. Assume 0 as the initial position.
// `supports_random_access_` is left as `false`.
random_access_status_ =
FailedOperationStatus(cfile_internal::kFTellFunctionName);
clearerr(src);
return;
}
set_limit_pos(IntCast<Position>(file_pos));
// Check the size, and whether random access is supported.
if (cfile_internal::FSeek(src, 0, SEEK_END) != 0) {
// Random access is not supported. `supports_random_access_` is left as
// `false`.
random_access_status_ =
FailedOperationStatus(cfile_internal::kFSeekFunctionName);
clearerr(src);
return;
}
cfile_internal::Offset file_size = cfile_internal::FTell(src);
if (ABSL_PREDICT_FALSE(file_size < 0)) {
FailOperation(cfile_internal::kFTellFunctionName);
return;
}
if (limit_pos() != IntCast<Position>(file_size)) {
if (ABSL_PREDICT_FALSE(cfile_internal::FSeek(
src,
IntCast<cfile_internal::Offset>(limit_pos()),
SEEK_SET) != 0)) {
FailOperation(cfile_internal::kFSeekFunctionName);
return;
}
}
#ifndef _WIN32
if (file_size == 0 && limit_pos() == 0) {
// Some "/proc" and "/sys" files claim to have zero size but have
// non-empty contents when read.
if (BufferedReader::PullSlow(1, 0)) {
if (growing_source_) {
// Check the size again. Maybe the file has grown.
if (ABSL_PREDICT_FALSE(cfile_internal::FSeek(src, 0, SEEK_END) !=
0)) {
FailOperation(cfile_internal::kFSeekFunctionName);
return;
}
file_size = cfile_internal::FTell(src);
if (ABSL_PREDICT_FALSE(file_size < 0)) {
FailOperation(cfile_internal::kFTellFunctionName);
return;
}
if (limit_pos() != IntCast<Position>(file_size)) {
if (ABSL_PREDICT_FALSE(
cfile_internal::FSeek(
src, IntCast<cfile_internal::Offset>(limit_pos()),
SEEK_SET) != 0)) {
FailOperation(cfile_internal::kFSeekFunctionName);
return;
}
}
if (file_size > 0) goto regular;
}
// This is one of "/proc" or "/sys" files which claim to have zero size
// but have non-empty contents when read. Random access is not
// supported. `supports_random_access_` is left as `false`.
random_access_status_ = Global([] {
return absl::UnimplementedError(
"Random access is not supported because "
"the file claims zero size but has non-empty contents when read");
});
return;
}
if (ABSL_PREDICT_FALSE(!ok())) return;
// This is a regular empty file.
}
regular:
#endif
// Random access is supported.
supports_random_access_ = true;
if (!growing_source_) set_exact_size(IntCast<Position>(file_size));
}
BeginRun();
}
void CFileReaderBase::Done() {
BufferedReader::Done();
#ifdef _WIN32
if (original_mode_ != std::nullopt) {
FILE* const src = SrcFile();
const int fd = _fileno(src);
if (ABSL_PREDICT_FALSE(fd < 0)) {
FailOperation("_fileno()");
} else if (ABSL_PREDICT_FALSE(_setmode(fd, *original_mode_) < 0)) {
FailOperation("_setmode()");
}
}
#endif // !_WIN32
random_access_status_ = absl::OkStatus();
}
inline absl::Status CFileReaderBase::FailedOperationStatus(
absl::string_view operation) {
const int error_number = errno;
RIEGELI_ASSERT_NE(error_number, 0)
<< "Failed precondition of CFileReaderBase::FailedOperationStatus(): "
"zero errno";
return absl::ErrnoToStatus(error_number, absl::StrCat(operation, " failed"));
}
bool CFileReaderBase::FailOperation(absl::string_view operation) {
return Fail(FailedOperationStatus(operation));
}
absl::Status CFileReaderBase::AnnotateStatusImpl(absl::Status status) {
return BufferedReader::AnnotateStatusImpl(
Annotate(status, absl::StrCat("reading ", filename())));
}
bool CFileReaderBase::ReadInternal(size_t min_length, size_t max_length,
char* dest) {
RIEGELI_ASSERT_GT(min_length, 0u)
<< "Failed precondition of BufferedReader::ReadInternal(): "
"nothing to read";
RIEGELI_ASSERT_GE(max_length, min_length)
<< "Failed precondition of BufferedReader::ReadInternal(): "
"max_length < min_length";
RIEGELI_ASSERT_OK(*this)
<< "Failed precondition of BufferedReader::ReadInternal()";
FILE* const src = SrcFile();
for (;;) {
if (ABSL_PREDICT_FALSE(limit_pos() >= kMaxPosition)) {
return FailOverflow();
}
const size_t length_to_read =
UnsignedMin(UnsignedClamp(AvailableLength(src), min_length, max_length),
kMaxPosition - limit_pos());
const size_t length_read = fread(dest, 1, length_to_read, src);
RIEGELI_ASSERT_LE(length_read, length_to_read)
<< "fread() read more than requested";
move_limit_pos(length_read);
if (ABSL_PREDICT_FALSE(length_read < length_to_read)) {
RIEGELI_ASSERT_LT(length_read, min_length)
<< "fread() read less than was available";
if (ABSL_PREDICT_FALSE(ferror(src))) return FailOperation("fread()");
RIEGELI_ASSERT(feof(src))
<< "fread() succeeded but read less than requested";
clearerr(src);
if (!growing_source_) set_exact_size(limit_pos());
return false;
}
if (length_read >= min_length) return true;
dest += length_read;
min_length -= length_read;
max_length -= length_read;
}
}
bool CFileReaderBase::SeekBehindBuffer(Position new_pos) {
RIEGELI_ASSERT(new_pos < start_pos() || new_pos > limit_pos())
<< "Failed precondition of BufferedReader::SeekBehindBuffer(): "
"position in the buffer, use Seek() instead";
RIEGELI_ASSERT_EQ(start_to_limit(), 0u)
<< "Failed precondition of BufferedReader::SeekBehindBuffer(): "
"buffer not empty";
if (ABSL_PREDICT_FALSE(!CFileReaderBase::SupportsRandomAccess())) {
if (ABSL_PREDICT_FALSE(new_pos < start_pos())) {
if (ok()) Fail(random_access_status_);
return false;
}
return BufferedReader::SeekBehindBuffer(new_pos);
}
if (ABSL_PREDICT_FALSE(!ok())) return false;
FILE* const src = SrcFile();
if (new_pos > limit_pos()) {
// Seeking forwards.
if (exact_size() != std::nullopt) {
if (ABSL_PREDICT_FALSE(new_pos > *exact_size())) {
// File ends.
if (ABSL_PREDICT_FALSE(cfile_internal::FSeek(
src, IntCast<cfile_internal::Offset>(*exact_size()),
SEEK_SET)) != 0) {
return FailOperation(cfile_internal::kFSeekFunctionName);
}
set_limit_pos(*exact_size());
return false;
}
} else {
if (ABSL_PREDICT_FALSE(cfile_internal::FSeek(src, 0, SEEK_END) != 0)) {
return FailOperation(cfile_internal::kFSeekFunctionName);
}
const cfile_internal::Offset file_size = cfile_internal::FTell(src);
if (ABSL_PREDICT_FALSE(file_size < 0)) {
return FailOperation(cfile_internal::kFTellFunctionName);
}
if (!growing_source_) set_exact_size(IntCast<Position>(file_size));
if (ABSL_PREDICT_FALSE(new_pos > IntCast<Position>(file_size))) {
// File ends.
set_limit_pos(IntCast<Position>(file_size));
return false;
}
}
}
if (ABSL_PREDICT_FALSE(cfile_internal::FSeek(
src, IntCast<cfile_internal::Offset>(new_pos), SEEK_SET)) != 0) {
return FailOperation(cfile_internal::kFSeekFunctionName);
}
set_limit_pos(new_pos);
return true;
}
std::optional<Position> CFileReaderBase::SizeImpl() {
if (ABSL_PREDICT_FALSE(!ok())) return std::nullopt;
if (exact_size() != std::nullopt) return *exact_size();
if (ABSL_PREDICT_FALSE(!CFileReaderBase::SupportsRandomAccess())) {
Fail(random_access_status_);
return std::nullopt;
}
FILE* const src = SrcFile();
if (ABSL_PREDICT_FALSE(cfile_internal::FSeek(src, 0, SEEK_END)) != 0) {
FailOperation(cfile_internal::kFSeekFunctionName);
return std::nullopt;
}
const cfile_internal::Offset file_size = cfile_internal::FTell(src);
if (ABSL_PREDICT_FALSE(file_size < 0)) {
FailOperation(cfile_internal::kFTellFunctionName);
return std::nullopt;
}
if (ABSL_PREDICT_FALSE(cfile_internal::FSeek(
src, IntCast<cfile_internal::Offset>(limit_pos()),
SEEK_SET) != 0)) {
FailOperation(cfile_internal::kFSeekFunctionName);
return std::nullopt;
}
if (!growing_source_) set_exact_size(IntCast<Position>(file_size));
return IntCast<Position>(file_size);
}
} // namespace riegeli