Program Listing for File fifo_writer.cpp

Return to documentation for file (monitoring/fifo_writer.cpp)

#include "fifo_writer.hpp"

#include <format>
#include <sys/stat.h>
#include <ers/Severity.h>

namespace {

class ScopedFd {

public:
    explicit ScopedFd(int fd) : m_fd(fd) {}
    ~ScopedFd() { if (m_fd >= 0) ::close(m_fd); }

    ScopedFd(const ScopedFd&)            = delete;
    ScopedFd& operator=(const ScopedFd&) = delete;
    ScopedFd(const ScopedFd&&)            = delete;
    ScopedFd&& operator=(const ScopedFd&&) = delete;

    [[nodiscard]] bool valid()   const { return m_fd >= 0; }
    [[nodiscard]] int  get()     const { return m_fd; }
    [[nodiscard]] int  release() { int tmp = m_fd; m_fd = -1; return tmp; }

private:
    int m_fd {-1};
};

} // namespace


FIFOWriter::FIFOWriter(const std::string& fifoname) {
    try {
        validate_fifo(fifoname);
    } catch (const monitoring_log::fifo_issue& err) {
        ers::error(err);
        return;
    }

    // Suppressing SIGPIPE so that a broken reader yields to EPIPE on write()
    // instead of killing the process
    signal(SIGPIPE, SIG_IGN);

    // A write-only open on a FIFO blocks until a reader exists.
    // holding a temporary read-only open just to unblock the write-only one.
    ScopedFd tmp_fd{open(fifoname.c_str(), O_RDONLY | O_NDELAY)};
    if (not tmp_fd.valid()) {
        ers::error(monitoring_log::fifo_issue(
            std::format("Cannot open read end of FIFO {}, error {}: {}",
                        fifoname, errno, strerror(errno))));
        return;
    }

    ScopedFd write_fd{open(fifoname.c_str(), O_WRONLY | O_NDELAY | O_NONBLOCK)};
    if (not write_fd.valid()) {
        ers::error(monitoring_log::fifo_issue(
            std::format("Cannot open write end of FIFO {}, error {}: {}",
                        fifoname, errno, strerror(errno))));
        return;
    }

    constexpr int buffer_resize_1MB = 1048576;
    if (fcntl(write_fd.get(), F_SETPIPE_SZ, buffer_resize_1MB ) == -1) {
        ers::warning(monitoring_log::fifo_issue(
            std::format("Could not resize FIFO {} pipe buffer, error {}: {}",
                        fifoname, errno, strerror(errno))));
    }

    m_fifo_fd = write_fd.release();
    ERS_INFO(std::format("Monitoring FIFO {} opened, fd={}", fifoname, m_fifo_fd));
}

FIFOWriter::~FIFOWriter() {
    if (m_fifo_fd >= 0) {
        close(m_fifo_fd);
    }
}

void FIFOWriter::write_message(const nlohmann::json& message) {
    if (m_fifo_fd < 0) {
        return;
    }

    const std::string msg = std::format("{}\n", message.dump());
    std::string_view remaining{msg};

    while (!remaining.empty()) {
        const ssize_t result = write(m_fifo_fd, remaining.data(), remaining.size());
        if (result < 0) {
            if (errno == EINTR) continue; // interrupted by signal — retry
            if (errno != EPIPE) {         // EPIPE means the FIFO reader is gone
                ers::warning(monitoring_log::fifo_issue(
                    std::format("Error writing to FIFO. Code {}: {}", errno, strerror(errno))));
            }
            break;
        }
        remaining.remove_prefix(static_cast<size_t>(result));
    }
}

void FIFOWriter::validate_fifo(const std::string& fifoname) {
    if (fifoname.empty()) {
        throw monitoring_log::fifo_issue("FIFO path is empty");
    }

    // Check if file exists and is a FIFO
    struct stat file_stat{};
    if (stat(fifoname.c_str(), &file_stat) == -1) {
        throw monitoring_log::fifo_issue(
            std::format("Cannot stat FIFO {}, error {}: {}", fifoname, errno, strerror(errno)));
    }

    if (not S_ISFIFO(file_stat.st_mode)) {
        throw monitoring_log::fifo_issue(
            std::format("{} exists but is not a UNIX FIFO", fifoname));
    }
}