Data-Log-Shared

 view release on metacpan or  search on metacpan

log.h  view on Meta::CPAN

#endif

/* log_read / log_read_ex return codes */
#define LOG_READ_EMPTY     0  /* no entry at offset (end / uncommitted in flight) */
#define LOG_READ_OK        1  /* valid entry -- out_data, out_len, next_off set */
#define LOG_READ_ABANDONED 2  /* slot abandoned -- next_off set, no data */
#define LOG_READ_TRUNCATED 3  /* offset below truncation -- next_off = truncation, no data */

/* ================================================================
 * Header (128 bytes)
 * ================================================================ */

typedef struct {
    uint32_t magic;
    uint32_t version;
    uint64_t data_size;        /* 8: usable data region size */
    uint64_t total_size;       /* 16 */
    uint64_t data_off;         /* 24 */
    uint8_t  _pad0[32];        /* 32-63 */

    uint64_t tail;             /* 64: byte offset past last entry (CAS target) */
    uint64_t count;            /* 72: number of committed entries */
    uint32_t waiters;          /* 80: blocked tailers */
    uint32_t wake_seq;         /* 84: FUTEX_WAIT target (avoids 64-bit count wraparound) */
    uint64_t stat_appends;     /* 88 */
    uint64_t stat_waits;       /* 96 */
    uint64_t stat_timeouts;    /* 104 */
    uint64_t truncation;       /* 112: entries before this offset are invalid */
    uint8_t  _pad2[8];         /* 120-127 */
} LogHeader;

#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
_Static_assert(sizeof(LogHeader) == 128, "LogHeader must be 128 bytes");
#endif

typedef struct {
    LogHeader *hdr;
    uint8_t   *data;
    size_t     mmap_size;
    char      *path;
    int        notify_fd;
    int        backing_fd;
} LogHandle;

/* Layer B trusted bound: the true byte size of the mapped data region,
 * derived from the process-private mmap_size (NOT the attacker-writable
 * hdr->data_size). Every file-stored offset/length that indexes h->data is
 * bounded against this before use, so a local peer that corrupts the backing
 * file cannot drive an out-of-bounds read or write. For a valid log this
 * equals hdr->data_size exactly, so the added checks never fire on good data. */
static inline uint64_t log_region_size(const LogHandle *h) {
    return (h->mmap_size > sizeof(LogHeader)) ? (h->mmap_size - sizeof(LogHeader)) : 0;
}

/* ================================================================
 * Utility
 * ================================================================ */

static inline void log_make_deadline(double t, struct timespec *dl) {
    clock_gettime(CLOCK_MONOTONIC, dl);
    if (!(t < 1e9)) t = 1e9; /* clamp Inf/NaN/huge: avoid UB (time_t) cast -> instant spurious timeout */
    dl->tv_sec += (time_t)t;
    dl->tv_nsec += (long)((t - (double)(time_t)t) * 1e9);
    if (dl->tv_nsec >= 1000000000L) { dl->tv_sec++; dl->tv_nsec -= 1000000000L; }
}

static inline int log_remaining(const struct timespec *dl, struct timespec *rem) {
    struct timespec now;
    clock_gettime(CLOCK_MONOTONIC, &now);
    rem->tv_sec = dl->tv_sec - now.tv_sec;
    rem->tv_nsec = dl->tv_nsec - now.tv_nsec;
    if (rem->tv_nsec < 0) { rem->tv_sec--; rem->tv_nsec += 1000000000L; }
    return rem->tv_sec >= 0;
}

/* ================================================================
 * Append -- CAS reserve space, publish reserve_size, write data,
 * commit (len). reserve_size is published BEFORE data so a crashed
 * writer leaves a recoverable slot boundary for readers.
 * ================================================================ */

static inline int64_t log_append(LogHandle *h, const void *data, uint32_t len, int utf8) {
    if (len == 0) return -1;  /* 0 is the uncommitted marker */

    LogHeader *hdr = h->hdr;
    /* Bit 31 of the stored len field carries the UTF8 flag, so cap payloads at
     * < 2 GiB -- a single 2 GiB+ log entry is not a real use case. */
    if (len >= 0x80000000u) return -1;
    /* Pad total slot size up to 4-byte boundary so the next slot's
     * header words are naturally aligned for atomic ops on ARM64. */
    uint32_t entry_size = (LOG_ENTRY_HDR + len + 3U) & ~3U;

    uint64_t region = log_region_size(h);
    for (;;) {
        uint64_t t = __atomic_load_n(&hdr->tail, __ATOMIC_RELAXED);
        /* Layer B: tail is an attacker-writable shared-segment read used just
         * below as a pointer offset (h->data + t) and as the memcpy target.
         * Bound the slot within BOTH the logical data_size (the "log full"
         * limit) and the true mapped region (mmap_size-derived, not the
         * attacker-writable data_size), overflow-safe. For valid data
         * data_size == region, so this is identical to the original
         * full-check and the region test never fires early. */
        if (t > hdr->data_size || entry_size > hdr->data_size - t) return -1;
        if (t > region || entry_size > region - t) return -1;

        if (__atomic_compare_exchange_n(&hdr->tail, &t, t + entry_size,
                1, __ATOMIC_ACQ_REL, __ATOMIC_RELAXED)) {
            uint8_t *slot = h->data + t;
            /* Explicitly zero len before publishing reserve_size. In the
             * common case the slot is already zero (fresh log_init or
             * post-reset memset), but this defensive RELAXED store
             * tolerates user code that bypasses log_reset's zeroing or
             * concurrent edge cases. Release fence comes via the
             * subsequent reserve_size store. */
            __atomic_store_n((uint32_t *)(slot + sizeof(uint32_t)), 0U, __ATOMIC_RELAXED);
            /* Publish slot boundary so readers can skip past us on crash.
             * RELEASE so any reader observing reserve_size > 0 also sees
             * the len=0 store above and correctly classifies the slot. */
            __atomic_store_n((uint32_t *)slot, entry_size, __ATOMIC_RELEASE);
            /* Write payload. */
            memcpy(slot + LOG_ENTRY_HDR, data, len);



( run in 0.811 second using v1.01-cache-2.11-cpan-84e82930d8c )