openfoam/src/OpenFOAM/db/IOstreams/Fstreams/fstreamPointers.C
Mark Olesen ee895577ae ENH: improve OFstream append behaviour (#3160)
- previous support for file appending (largely unused) always
  specified opening with the std::ios_base::app flag.

  Now differentiate between append behaviours:

  APPEND_APP
  ~~~~~~~~~~
  Corresponds to std::ios_base::app behaviour:

  - Existing files will be preserved and a seek-to-end is performed at
    every write. With this mode seeks/repositioning within the file
    will effectively be ignored on output.

  APPEND_ATE
  ~~~~~~~~~~
  Largely approximates std::ios_base::ate behaviour:

  - Existing files will be preserved and a seek-to-end is performed
    immediately after opening, but not subsequently. Can use seekp()
    to overwrite parts of a file.
2024-05-29 14:04:52 +00:00

531 lines
13 KiB
C

/*---------------------------------------------------------------------------*\
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration |
\\ / A nd | www.openfoam.com
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2011 OpenFOAM Foundation
Copyright (C) 2018-2024 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
OpenFOAM is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License
along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
\*---------------------------------------------------------------------------*/
#include "fstreamPointer.H"
#include "OCountStream.H"
#include "OSspecific.H"
#include <cstdio>
// HAVE_LIBZ defined externally
// #define HAVE_LIBZ
#ifdef HAVE_LIBZ
#include "gzstream.h"
#endif /* HAVE_LIBZ */
// * * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * //
bool Foam::ifstreamPointer::supports_gz() noexcept
{
#ifdef HAVE_LIBZ
return true;
#else
return false;
#endif
}
bool Foam::ofstreamPointer::supports_gz() noexcept
{
#ifdef HAVE_LIBZ
return true;
#else
return false;
#endif
}
// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
Foam::ifstreamPointer::ifstreamPointer
(
const fileName& pathname,
IOstreamOption streamOpt // Currently unused
)
:
ptr_()
{
open(pathname, streamOpt);
}
Foam::ifstreamPointer::ifstreamPointer
(
const fileName& pathname
)
:
ptr_()
{
open(pathname);
}
Foam::ofstreamPointer::ofstreamPointer() noexcept
:
ptr_(),
mode_(modeType::NONE)
{}
Foam::ofstreamPointer::ofstreamPointer(std::nullptr_t)
:
ptr_(new Foam::ocountstream),
mode_(modeType::NONE)
{}
Foam::ofstreamPointer::ofstreamPointer
(
const fileName& pathname,
IOstreamOption streamOpt,
IOstreamOption::appendType append,
bool atomic
)
:
ptr_(),
mode_(modeType::NONE)
{
// Leave std::ios_base::trunc implicitly handled to make things
// easier for append mode.
std::ios_base::openmode openmode
(
std::ios_base::out | std::ios_base::binary
);
if (append == IOstreamOption::APPEND_APP)
{
openmode |= std::ios_base::app;
// Cannot append to gzstream
streamOpt.compression(IOstreamOption::UNCOMPRESSED);
// Cannot use append + atomic operation, without lots of extra work
atomic = false;
}
else if (append == IOstreamOption::APPEND_ATE)
{
// Handle an "append-like" mode by opening "r+b" and NOT as "ab"
// - file already exists: Sets read position to start
// - file does not exist: Error
openmode |= std::ios_base::in; // [SIC] - use read bit, not append!
// Cannot append to gzstream
streamOpt.compression(IOstreamOption::UNCOMPRESSED);
// Cannot use append + atomic operation, without lots of extra work
atomic = false;
}
// When opening new files, remove file variants out of the way.
// Eg, opening "file1"
// - remove old "file1.gz" (compressed)
// - also remove old "file1" if it is a symlink and we are not appending
//
// Not writing into symlinked files avoids problems with symlinked
// initial fields (eg, 0/U -> ../0.orig/U)
const fileName pathname_gz(pathname + ".gz");
const fileName pathname_tmp(pathname + "~tmp~");
fileName::Type fType = fileName::Type::UNDEFINED;
if (IOstreamOption::COMPRESSED == streamOpt.compression())
{
// Output compression requested.
#ifdef HAVE_LIBZ
// TBD:
// atomic = true; // Always treat COMPRESSED like an atomic
const fileName& target = (atomic ? pathname_tmp : pathname_gz);
// Remove old uncompressed version (if any)
fType = Foam::type(pathname, false);
if (fType == fileName::SYMLINK || fType == fileName::FILE)
{
Foam::rm(pathname);
}
// Avoid writing into symlinked files (non-append mode)
if (atomic || (append == IOstreamOption::NO_APPEND))
{
fType = Foam::type(target, false);
if (fType == fileName::SYMLINK)
{
Foam::rm(target);
}
}
ptr_.reset(new ogzstream(target, openmode));
#else /* HAVE_LIBZ */
streamOpt.compression(IOstreamOption::UNCOMPRESSED);
Warning
<< nl
<< "No write support for gz compressed files (libz)"
<< " : downgraded to UNCOMPRESSED" << nl
<< "file: " << pathname_gz << endl;
#endif /* HAVE_LIBZ */
}
if (IOstreamOption::COMPRESSED != streamOpt.compression())
{
const fileName& target = (atomic ? pathname_tmp : pathname);
// Remove old compressed version (if any)
fType = Foam::type(pathname_gz, false);
if (fType == fileName::SYMLINK || fType == fileName::FILE)
{
Foam::rm(pathname_gz);
}
// Avoid writing into symlinked files (non-append mode)
if (atomic || (append == IOstreamOption::NO_APPEND))
{
fType = Foam::type(target, false);
if (fType == fileName::SYMLINK)
{
Foam::rm(target);
}
}
// File pointer (std::ofstream)
auto filePtr = std::make_unique<std::ofstream>(target, openmode);
if (append == IOstreamOption::APPEND_APP)
{
// Final handling for append 'app' (always non-atomic)
// Set output position to the end (like std::ios_base::ate)
// but only to test if the file had a size.
// No real performance problem since any subsequent write
// will do the same anyhow.
filePtr->seekp(0, std::ios_base::end);
if (filePtr->tellp() <= 0)
{
// Did not open an existing file
append = IOstreamOption::NO_APPEND;
}
}
else if (append == IOstreamOption::APPEND_ATE)
{
// Final handling for append 'ate' (always non-atomic)
if (filePtr->good())
{
// Success if file already exists.
// Set output position to the end - like std::ios_base::ate
filePtr->seekp(0, std::ios_base::end);
if (filePtr->tellp() <= 0)
{
// Did not open an existing file
append = IOstreamOption::NO_APPEND;
}
}
else
{
// Error if file does not already exist.
// Reopen as regular output mode only.
// Did not open an existing file
append = IOstreamOption::NO_APPEND;
if (filePtr->is_open())
{
filePtr->close();
}
filePtr->clear();
filePtr->open
(
target,
(std::ios_base::out | std::ios_base::binary)
);
}
}
ptr_.reset(filePtr.release());
}
// Is appending to an existing file
if (append != IOstreamOption::NO_APPEND)
{
mode_ = modeType::APPENDING;
}
// An atomic output operation (normally not appending!)
if (atomic)
{
mode_ |= modeType::ATOMIC;
}
}
Foam::ofstreamPointer::ofstreamPointer
(
const fileName& pathname,
IOstreamOption::compressionType comp,
IOstreamOption::appendType append,
bool atomic
)
:
ofstreamPointer
(
pathname,
IOstreamOption(IOstreamOption::ASCII, comp),
append,
atomic
)
{}
// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
void Foam::ifstreamPointer::open
(
const fileName& pathname,
IOstreamOption streamOpt // Currently unused
)
{
// Forcibly close old stream (if any)
ptr_.reset(nullptr);
const std::ios_base::openmode openmode
(
std::ios_base::in | std::ios_base::binary
);
ptr_.reset(new std::ifstream(pathname, openmode));
if (!ptr_->good())
{
// Try compressed version instead
const fileName pathname_gz(pathname + ".gz");
if (Foam::isFile(pathname_gz, false))
{
#ifdef HAVE_LIBZ
ptr_.reset(new igzstream(pathname_gz, openmode));
#else /* HAVE_LIBZ */
FatalError
<< "No read support for gz compressed files (libz)"
<< " : could use 'gunzip' from the command-line" << nl
<< "file: " << pathname_gz << endl
<< exit(FatalError);
#endif /* HAVE_LIBZ */
}
else
{
// TBD:
// Can also fallback and open .orig files too
//
// auto* file = dynamic_cast<std::ifstream*>(ptr_.get());
// file->open(pathname + ".orig", mode);
}
}
}
void Foam::ifstreamPointer::reopen_gz(const std::string& pathname)
{
#ifdef HAVE_LIBZ
auto* gz = dynamic_cast<igzstream*>(ptr_.get());
if (gz)
{
// Special treatment for gzstream
gz->close();
gz->clear();
gz->open
(
pathname + ".gz",
(std::ios_base::in | std::ios_base::binary)
);
return;
}
#endif /* HAVE_LIBZ */
}
void Foam::ofstreamPointer::reopen(const std::string& pathname)
{
#ifdef HAVE_LIBZ
auto* gz = dynamic_cast<ogzstream*>(ptr_.get());
if (gz)
{
// Special treatment for gzstream
gz->close();
gz->clear();
if (mode_ & modeType::ATOMIC)
{
gz->open
(
pathname + "~tmp~",
(std::ios_base::out | std::ios_base::binary)
);
}
else
{
gz->open
(
pathname + ".gz",
(std::ios_base::out | std::ios_base::binary)
);
}
return;
}
#endif /* HAVE_LIBZ */
auto* file = dynamic_cast<std::ofstream*>(ptr_.get());
if (file)
{
if (file->is_open())
{
file->close();
}
file->clear();
// Invalidate the appending into existing file information
// since rewind usually means overwrite
mode_ &= ~modeType::APPENDING;
if (mode_ & modeType::ATOMIC)
{
file->open
(
pathname + "~tmp~",
(std::ios_base::out | std::ios_base::binary)
);
}
else
{
file->open
(
pathname,
(std::ios_base::out | std::ios_base::binary)
);
}
return;
}
}
void Foam::ofstreamPointer::close(const std::string& pathname)
{
// Invalidate the appending into existing file information
mode_ &= ~modeType::APPENDING;
if (pathname.empty() || !(mode_ & modeType::ATOMIC))
{
return;
}
#ifdef HAVE_LIBZ
auto* gz = dynamic_cast<ogzstream*>(ptr_.get());
if (gz)
{
// Special treatment for gzstream
gz->close();
gz->clear();
std::rename
(
(pathname + "~tmp~").c_str(),
(pathname + ".gz").c_str()
);
return;
}
#endif /* HAVE_LIBZ */
auto* file = dynamic_cast<std::ofstream*>(ptr_.get());
if (file)
{
if (file->is_open())
{
file->close();
}
file->clear();
std::rename
(
(pathname + "~tmp~").c_str(),
pathname.c_str()
);
return;
}
}
Foam::IOstreamOption::compressionType
Foam::ifstreamPointer::whichCompression() const
{
#ifdef HAVE_LIBZ
if (dynamic_cast<const igzstream*>(ptr_.get()))
{
return IOstreamOption::compressionType::COMPRESSED;
}
#endif /* HAVE_LIBZ */
return IOstreamOption::compressionType::UNCOMPRESSED;
}
Foam::IOstreamOption::compressionType
Foam::ofstreamPointer::whichCompression() const
{
#ifdef HAVE_LIBZ
if (dynamic_cast<const ogzstream*>(ptr_.get()))
{
return IOstreamOption::compressionType::COMPRESSED;
}
#endif /* HAVE_LIBZ */
return IOstreamOption::compressionType::UNCOMPRESSED;
}
// ************************************************************************* //