Data-CountMinSketch-Shared

 view release on metacpan or  search on metacpan

cms.h  view on Meta::CPAN

     * (shared-segment, attacker-mutable) reader_slots_off. validate_header
     * requires reader_slots_off to equal this for any file we open, so this is
     * identical for valid data and never trusts a poisoned offset. */
    h->reader_slots = (CmsReaderSlot *)((uint8_t *)base + cms_layout().reader_slots);
    h->mmap_size    = map_size;
    h->path         = path ? strdup(path) : NULL;
    h->backing_fd   = backing_fd;
    h->my_slot_idx  = UINT32_MAX;
    return h;
}

/* Validate a mapped header (shared by cms_create reopen and cms_open_fd). */
static inline int cms_validate_header(const CmsHeader *hdr, uint64_t file_size) {
    if (hdr->magic != CMS_MAGIC) return 0;
    if (hdr->version != CMS_VERSION) return 0;
    if (hdr->d < CMS_MIN_D || hdr->d > CMS_MAX_D) return 0;
    if (hdr->w < CMS_MIN_W || hdr->w > CMS_MAX_W) return 0;
    if ((hdr->w & (hdr->w - 1)) != 0) return 0;             /* power of two */
    if (hdr->mask != hdr->w - 1) return 0;
    if (hdr->total_size != file_size) return 0;
    if (hdr->total_size != cms_total_size(hdr->w, hdr->d)) return 0;
    CmsLayout L = cms_layout();
    if (hdr->reader_slots_off != L.reader_slots) return 0;
    if (hdr->counters_off != L.counters) return 0;
    return 1;
}

/* validate args + compute the geometry (w, d).
 *   w = next_pow2(ceil(M_E / epsilon)), floor CMS_MIN_W, cap CMS_MAX_W
 *   d = ceil(log(1 / delta)) clamped to [1, 32] */
static int cms_validate_create_args(double epsilon, double delta,
                                    uint64_t *w_out, uint32_t *d_out, char *errbuf) {
    if (errbuf) errbuf[0] = '\0';
    if (!(epsilon > 0.0 && epsilon < 1.0)) { CMS_ERR("epsilon must be between 0 and 1 (exclusive)"); return 0; }
    if (!(delta > 0.0 && delta < 1.0))     { CMS_ERR("delta must be between 0 and 1 (exclusive)"); return 0; }

    /* w = next_pow2(ceil(e / epsilon)), floor CMS_MIN_W. Reject (don't silently
       clamp) an epsilon so small the column count would exceed the cap, else the
       achieved error bound would be worse than requested (and the mapping huge). */
    double w_opt_d = ceil(M_E / epsilon);
    if (w_opt_d > (double)CMS_MAX_W) { CMS_ERR("epsilon too small for the column cap"); return 0; }
    uint64_t w = cms_next_pow2_u64((uint64_t)w_opt_d);

    /* d = ceil(log(1/delta)) clamped to [1, 32] */
    double d_d = ceil(log(1.0 / delta));
    long dl = (long)d_d;
    if (dl < CMS_MIN_D) dl = CMS_MIN_D;
    if (dl > CMS_MAX_D) dl = CMS_MAX_D;
    uint32_t d = (uint32_t)dl;

    *w_out = w;
    *d_out = d;
    return 1;
}

/* Securely obtain a fd: create exclusively (O_CREAT|O_EXCL|O_NOFOLLOW at mode,
 * default 0600), or attach an existing file (O_RDWR|O_NOFOLLOW, no O_CREAT). */
static int cms_secure_open(const char *path, mode_t mode, char *errbuf) {
    for (int attempt = 0; attempt < 100; attempt++) {
        int fd = open(path, O_RDWR|O_CREAT|O_EXCL|O_NOFOLLOW|O_CLOEXEC, mode);
        if (fd >= 0) { (void)fchmod(fd, mode); return fd; }   /* exact mode: umask narrowed the O_EXCL create */
        if (errno != EEXIST) { CMS_ERR("create %s: %s", path, strerror(errno)); return -1; }
        fd = open(path, O_RDWR|O_NOFOLLOW|O_CLOEXEC);
        if (fd >= 0) return fd;
        if (errno == ENOENT) continue;   /* creator unlinked between our two opens; retry */
        CMS_ERR("open %s: %s", path, strerror(errno));  /* ELOOP => symlink rejected */
        return -1;
    }
    CMS_ERR("open %s: create/attach kept racing", path);
    return -1;
}

static CmsHandle *cms_create(const char *path, double epsilon, double delta, mode_t mode, char *errbuf) {
    uint64_t w;
    uint32_t d;
    if (!cms_validate_create_args(epsilon, delta, &w, &d, errbuf)) return NULL;

    uint64_t total = cms_total_size(w, d);
    int anonymous = (path == NULL);
    int fd = -1;
    size_t map_size;
    void *base;

    if (anonymous) {
        map_size = (size_t)total;
        base = mmap(NULL, map_size, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
        if (base == MAP_FAILED) { CMS_ERR("mmap: %s", strerror(errno)); return NULL; }
    } else {
        fd = cms_secure_open(path, mode, errbuf);
        if (fd < 0) return NULL;
        if (flock(fd, LOCK_EX) < 0) { CMS_ERR("flock: %s", strerror(errno)); close(fd); return NULL; }
        struct stat st;
        if (fstat(fd, &st) < 0) { CMS_ERR("fstat: %s", strerror(errno)); flock(fd, LOCK_UN); close(fd); return NULL; }
        int is_new = (st.st_size == 0);
        if (!is_new && (uint64_t)st.st_size < sizeof(CmsHeader)) {
            CMS_ERR("%s: file too small (%lld)", path, (long long)st.st_size);
            flock(fd, LOCK_UN); close(fd); return NULL;
        }
        if (is_new && ftruncate(fd, (off_t)total) < 0) {
            CMS_ERR("ftruncate: %s", strerror(errno)); flock(fd, LOCK_UN); close(fd); return NULL;
        }
        map_size = is_new ? (size_t)total : (size_t)st.st_size;
        base = mmap(NULL, map_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
        if (base == MAP_FAILED) { CMS_ERR("mmap: %s", strerror(errno)); flock(fd, LOCK_UN); close(fd); return NULL; }
        if (!is_new) {
            if (!cms_validate_header((CmsHeader *)base, (uint64_t)st.st_size)) {
                CMS_ERR("invalid Count-Min sketch file"); munmap(base, map_size); flock(fd, LOCK_UN); close(fd); return NULL;
            }
            flock(fd, LOCK_UN); close(fd);
            return cms_setup(base, map_size, path, -1);
        }
    }
    cms_init_header(base, w, d, total);
    if (fd >= 0) { flock(fd, LOCK_UN); close(fd); }
    return cms_setup(base, map_size, path, -1);
}

static CmsHandle *cms_create_memfd(const char *name, double epsilon, double delta, char *errbuf) {
    uint64_t w;
    uint32_t d;
    if (!cms_validate_create_args(epsilon, delta, &w, &d, errbuf)) return NULL;



( run in 0.592 second using v1.01-cache-2.11-cpan-600a1bdf6e4 )