Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .github/workflows/coverity_scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,46 @@ jobs:
sudo apt-get update
sudo apt-get install -y curl build-essential cmake pkg-config libsystemd-dev
- name: Check Coverity Token
id: check-token
env:
COVERITY_TOKEN: ${{ secrets.COVERITY_TOKEN }}
run: |
if [ -z "${COVERITY_TOKEN}" ]; then
echo "COVERITY_TOKEN secret is not set. Skipping Coverity scan."
echo "skip=true" >> $GITHUB_OUTPUT
else
echo "skip=false" >> $GITHUB_OUTPUT
fi
- name: Download Coverity Tool
if: steps.check-token.outputs.skip == 'false'
run: |
echo "Downloading Coverity tool..."
curl -s -L --output coverity_tool.tgz "https://scan.coverity.com/download/linux64?token=${{ secrets.COVERITY_TOKEN }}&project=gabime%2Fspdlog"
# Verify the downloaded file is a valid tar archive
if ! file coverity_tool.tgz | grep -q "gzip compressed"; then
echo "Error: Downloaded file is not a valid gzip archive"
echo "File content (first 200 bytes):"
head -c 200 coverity_tool.tgz
exit 1
fi
mkdir coverity_tool
tar -C coverity_tool --strip-components=1 -xf coverity_tool.tgz
echo "$PWD/coverity_tool/bin" >> $GITHUB_PATH
- name: Build with Coverity
if: steps.check-token.outputs.skip == 'false'
run: |
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_STANDARD=17
cd ..
cov-build --dir cov-int make -C build -j4
- name: Submit results to Coverity
if: steps.check-token.outputs.skip == 'false'
run: |
tar czf cov-int.tgz cov-int
response=$(curl --silent --show-error --fail \
Expand Down
260 changes: 258 additions & 2 deletions include/spdlog/details/file_helper-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,27 @@

#include <cerrno>
#include <cstdio>
#include <cstring>
#include <string>
#include <tuple>
#include <algorithm>

// Platform-specific includes for mmap support
#ifdef _WIN32
#include <spdlog/details/windows_include.h>
#include <io.h>
#include <fcntl.h>
#include <sys/stat.h>
#else
#include <sys/mman.h>
#include <unistd.h>
#endif

namespace spdlog {
namespace details {

SPDLOG_INLINE file_helper::file_helper(const file_event_handlers &event_handlers)
: event_handlers_(event_handlers) {}
SPDLOG_INLINE file_helper::file_helper(const file_event_handlers &event_handlers, bool mmap_enabled)
: event_handlers_(event_handlers), mmap_enabled_(mmap_enabled) {}

SPDLOG_INLINE file_helper::~file_helper() { close(); }

Expand Down Expand Up @@ -51,6 +64,13 @@ SPDLOG_INLINE void file_helper::open(const filename_t &fname, bool truncate) {
if (event_handlers_.after_open) {
event_handlers_.after_open(filename_, fd_);
}

// Try to initialize mmap if enabled
if (mmap_enabled_ && !try_init_mmap()) {
// mmap initialization failed, continue with regular file I/O
// No need to throw exception, just log a debug message if needed
}

return;
}

Expand All @@ -69,12 +89,40 @@ SPDLOG_INLINE void file_helper::reopen(bool truncate) {
}

SPDLOG_INLINE void file_helper::flush() {
if (mmap_active_) {
// For mmap, flush means sync the memory mapping
#ifdef _WIN32
if (!FlushViewOfFile(mmap_ptr_, mmap_offset_)) {
// If mmap flush fails, fallback to stdio
fallback_to_stdio();
}
#else
if (msync(mmap_ptr_, mmap_offset_, MS_ASYNC) != 0) {
// If mmap flush fails, fallback to stdio
fallback_to_stdio();
}
#endif
}

if (std::fflush(fd_) != 0) {
throw_spdlog_ex("Failed flush to file " + os::filename_to_str(filename_), errno);
}
}

SPDLOG_INLINE void file_helper::sync() {
if (mmap_active_) {
// For mmap, sync means synchronous sync of the memory mapping
#ifdef _WIN32
if (!FlushViewOfFile(mmap_ptr_, mmap_offset_)) {
fallback_to_stdio();
}
#else
if (msync(mmap_ptr_, mmap_offset_, MS_SYNC) != 0) {
fallback_to_stdio();
}
#endif
}

if (!os::fsync(fd_)) {
throw_spdlog_ex("Failed to fsync file " + os::filename_to_str(filename_), errno);
}
Expand All @@ -86,6 +134,9 @@ SPDLOG_INLINE void file_helper::close() {
event_handlers_.before_close(filename_, fd_);
}

// Clean up mmap resources before closing file
cleanup_mmap();

std::fclose(fd_);
fd_ = nullptr;

Expand All @@ -100,6 +151,31 @@ SPDLOG_INLINE void file_helper::write(const memory_buf_t &buf) {
size_t msg_size = buf.size();
auto data = buf.data();

// Try mmap write first if active
if (mmap_active_) {
size_t required_size = mmap_offset_ + msg_size;

// Check if we need to expand the mapping
if (required_size > mmap_size_) {
if (!expand_mmap(required_size)) {
// Expansion failed, fallback to stdio
goto stdio_write;
}
}

// Write to mmap
try {
std::memcpy(static_cast<char*>(mmap_ptr_) + mmap_offset_, data, msg_size);
mmap_offset_ += msg_size;
return;
} catch (...) {
// mmap write failed, fallback to stdio
fallback_to_stdio();
}
}

stdio_write:
// Standard file I/O fallback
if (!details::os::fwrite_bytes(data, msg_size, fd_)) {
throw_spdlog_ex("Failed writing to file " + os::filename_to_str(filename_), errno);
}
Expand All @@ -109,6 +185,13 @@ SPDLOG_INLINE size_t file_helper::size() const {
if (fd_ == nullptr) {
throw_spdlog_ex("Cannot use size() on closed file " + os::filename_to_str(filename_));
}

// If mmap is active, return the current offset (actual written size)
if (mmap_active_) {
// DEBUG: Print mmap_offset_ value
return mmap_offset_;
}

return os::filesize(fd_);
}

Expand Down Expand Up @@ -147,5 +230,178 @@ SPDLOG_INLINE std::tuple<filename_t, filename_t> file_helper::split_by_extension
return std::make_tuple(fname.substr(0, ext_index), fname.substr(ext_index));
}

// mmap helper methods implementation
SPDLOG_INLINE void file_helper::set_mmap_enabled(bool enabled) {
mmap_enabled_ = enabled;
if (!enabled && mmap_active_) {
fallback_to_stdio();
}
}

SPDLOG_INLINE bool file_helper::is_mmap_enabled() const {
return mmap_enabled_;
}

SPDLOG_INLINE bool file_helper::try_init_mmap() {
if (!mmap_enabled_ || fd_ == nullptr) {
return false;
}

// Get current file size BEFORE any modifications
size_t current_file_size = os::filesize(fd_);
size_t required_size = (std::max)(current_file_size + initial_mmap_size_, initial_mmap_size_);

#ifdef _WIN32
// Windows implementation
file_handle_ = reinterpret_cast<HANDLE>(_get_osfhandle(_fileno(fd_)));
if (file_handle_ == INVALID_HANDLE_VALUE) {
return false;
}

// Create file mapping
LARGE_INTEGER map_size;
map_size.QuadPart = required_size;

mapping_handle_ = CreateFileMapping(file_handle_, nullptr, PAGE_READWRITE,
map_size.HighPart, map_size.LowPart, nullptr);
if (mapping_handle_ == nullptr) {
return false;
}

// Map view of file
mmap_ptr_ = MapViewOfFile(mapping_handle_, FILE_MAP_WRITE, 0, 0, required_size);
if (mmap_ptr_ == nullptr) {
CloseHandle(mapping_handle_);
mapping_handle_ = nullptr;
return false;
}

#else
// Unix/Linux implementation
file_descriptor_ = fileno(fd_);
if (file_descriptor_ == -1) {
return false;
}

// Create memory mapping first (without extending file)
mmap_ptr_ = mmap(nullptr, required_size, PROT_READ | PROT_WRITE, MAP_SHARED,
file_descriptor_, 0);
if (mmap_ptr_ == MAP_FAILED) {

mmap_ptr_ = nullptr;
return false;
}

// Extend file if necessary (only after mmap succeeds)
if (ftruncate(file_descriptor_, static_cast<off_t>(required_size)) != 0) {
munmap(mmap_ptr_, required_size);
mmap_ptr_ = nullptr;
return false;
}
#endif

mmap_size_ = required_size;
mmap_offset_ = 0; // Always start from beginning for new mmap
mmap_active_ = true;

return true;
}

SPDLOG_INLINE bool file_helper::expand_mmap(size_t required_size) {
if (!mmap_active_ || required_size <= mmap_size_) {
return true;
}

// Calculate new size (double current size or required size, whichever is larger)
size_t new_size = (std::max)(mmap_size_ * 2, required_size);
new_size = (std::min)(new_size, max_mmap_size_);

if (new_size <= mmap_size_) {
// Cannot expand further, fallback to stdio
fallback_to_stdio();
return false;
}

// Save current offset before cleanup
size_t saved_offset = mmap_offset_;
cleanup_mmap();

#ifdef _WIN32
// Windows re-mapping
LARGE_INTEGER map_size;
map_size.QuadPart = new_size;

mapping_handle_ = CreateFileMapping(file_handle_, nullptr, PAGE_READWRITE,
map_size.HighPart, map_size.LowPart, nullptr);
if (mapping_handle_ == nullptr) {
fallback_to_stdio();
return false;
}

mmap_ptr_ = MapViewOfFile(mapping_handle_, FILE_MAP_WRITE, 0, 0, new_size);
if (mmap_ptr_ == nullptr) {
CloseHandle(mapping_handle_);
mapping_handle_ = nullptr;
fallback_to_stdio();
return false;
}

#else
// Unix/Linux re-mapping
if (ftruncate(file_descriptor_, static_cast<off_t>(new_size)) != 0) {
fallback_to_stdio();
return false;
}

mmap_ptr_ = mmap(nullptr, new_size, PROT_READ | PROT_WRITE, MAP_SHARED,
file_descriptor_, 0);
if (mmap_ptr_ == MAP_FAILED) {
mmap_ptr_ = nullptr;
fallback_to_stdio();
return false;
}
#endif

mmap_size_ = new_size;
mmap_offset_ = saved_offset; // Restore the saved offset
mmap_active_ = true;
return true;
}

SPDLOG_INLINE void file_helper::cleanup_mmap() {
if (!mmap_active_) {
return;
}

#ifdef _WIN32
if (mmap_ptr_ != nullptr) {
UnmapViewOfFile(mmap_ptr_);
mmap_ptr_ = nullptr;
}
if (mapping_handle_ != nullptr) {
CloseHandle(mapping_handle_);
mapping_handle_ = nullptr;
}
#else
if (mmap_ptr_ != nullptr) {
munmap(mmap_ptr_, mmap_size_);
mmap_ptr_ = nullptr;
}
#endif

mmap_active_ = false;
mmap_size_ = 0;
mmap_offset_ = 0;
}

SPDLOG_INLINE void file_helper::fallback_to_stdio() {
if (mmap_active_) {
// Sync any pending data
sync();
cleanup_mmap();
}
mmap_enabled_ = false; // Disable mmap for this file
}

} // namespace details
} // namespace spdlog
Loading