Apophis

 view release on metacpan or  search on metacpan

Apophis.xs  view on Meta::CPAN

/*
 * Windows compatibility:
 * perl.h on threaded Windows redefines mkdir, stat, unlink etc. as macros
 * requiring the interpreter context (my_perl).  Our static C helper functions
 * don't have pTHX, so we undo those overrides and use the real libc calls.
 */
#ifdef WIN32
#  undef mkdir
#  undef stat
#  undef unlink
#  undef rename
#  undef open
#  undef close
#  undef read
#  undef write
   /* Windows mkdir takes only one arg */
#  define apophis_mkdir(p, m) _mkdir(p)
   /*
    * Stat_t is struct w32_stat, the layout perl's own stat() fills in - but
    * that stat() is the macro undef'd just above, so what is left is the CRT
    * one, which writes a differently shaped struct.  Pair the call and the

Apophis.xs  view on Meta::CPAN

    */
#  define apophis_stat_t struct _stat64
#  define apophis_stat(p, b) _stat64((p), (b))
#else
#  define apophis_mkdir(p, m) mkdir(p, m)
#  define apophis_stat_t struct stat
#  define apophis_stat(p, b) stat((p), (b))
#endif

/*
 * The CRT rename() refuses a destination that already exists, so the second
 * store of the same content with metadata could not replace its sidecar.
 * MoveFileEx replaces, and stays atomic.
 */
static int
apophis_rename(const char *from, const char *to)
{
#ifdef WIN32
    if (MoveFileExA(from, to, MOVEFILE_REPLACE_EXISTING))
        return 0;
    errno = EACCES;
    return -1;
#else
    return rename(from, to);
#endif
}

/* Horus UUID library - pure C, no Perl deps */
#define HORUS_FATAL(msg) croak("%s", (msg))
#include "horus_core.h"

/* ------------------------------------------------------------------ */
/* Constants                                                           */
/* ------------------------------------------------------------------ */

Apophis.xs  view on Meta::CPAN


    memcpy(buf, file_path, len + 1);
    last_slash = strrchr(buf, '/');
    if (last_slash) {
        *last_slash = '\0';
        apophis_mkdir_p(buf);
    }
}

/* ------------------------------------------------------------------ */
/* Internal: atomic file write (temp + rename)                         */
/* ------------------------------------------------------------------ */

static void
apophis_atomic_write(pTHX_ const char *path,
                     const char *content, STRLEN content_len)
{
    char tmp_path[APOPHIS_PATH_MAX];
    PerlIO *fh;
    SSize_t written;

Apophis.xs  view on Meta::CPAN

        croak("Apophis: cannot write '%s': %s", tmp_path, strerror(errno));

    written = PerlIO_write(fh, content, content_len);
    PerlIO_close(fh);

    if (written != (SSize_t)content_len) {
        unlink(tmp_path);
        croak("Apophis: short write to '%s'", tmp_path);
    }

    if (apophis_rename(tmp_path, path) != 0) {
        unlink(tmp_path);
        croak("Apophis: cannot rename '%s' -> '%s': %s",
              tmp_path, path, strerror(errno));
    }
}

/* ------------------------------------------------------------------ */
/* Internal: metadata sidecar (key=value\n format)                     */
/* ------------------------------------------------------------------ */

static void
apophis_meta_write(pTHX_ const char *meta_path, HV *meta)

Apophis.xs  view on Meta::CPAN

        STRLEN vlen;
        const char *vstr = SvPV(val, vlen);
        PerlIO_write(fh, key, (SSize_t)klen);
        PerlIO_write(fh, "=", 1);
        PerlIO_write(fh, vstr, (SSize_t)vlen);
        PerlIO_write(fh, "\n", 1);
    }

    PerlIO_close(fh);

    if (apophis_rename(tmp_path, meta_path) != 0) {
        unlink(tmp_path);
        croak("Apophis: cannot rename metadata '%s': %s",
              tmp_path, strerror(errno));
    }
}

static HV *
apophis_meta_read(pTHX_ const char *meta_path)
{
    PerlIO *fh;
    HV *meta;
    apophis_stat_t st;

Apophis.xs  view on Meta::CPAN

 *
 * Stack input:  self_sv, content_ref_sv
 * Stack output: uuid_string_sv
 *
 * Fuses the entire store pipeline into a single op:
 *   1. Extract namespace bytes from object
 *   2. SHA-1 hash content → UUID v5
 *   3. Compute 2-level sharded path
 *   4. stat() for CAS dedup check
 *   5. mkdir -p parent directories
 *   6. Atomic write (temp + rename)
 *   7. Format and return UUID string
 */
static OP *
pp_apophis_store(pTHX) {
    dSP;
    SV *content_ref_sv = POPs;
    SV *self_sv = POPs;
    HV *hv;
    const unsigned char *ns;
    SV *content_sv;

Changes  view on Meta::CPAN

Revision history for Apophis

0.06    2026-08-31
        - Move Apophis.xs to the top of the dist and drop the XSMULTI
          build, which dlltool could not link on Strawberry.
        - Pair the stat call with its struct on Windows.
        - Replace an existing file on rename on Windows.

0.05    2026-08-20
        - ADDS: a public C ABI, ap_abi.h, reached through
          ExtUtils::Depends and Apophis::_abi_ptr.
        - Regenerate ppport.h with Devel::PPPort 3.73. The old copy
          undef'd perl's STMT_START/STMT_END to force the gcc
          brace-group form, which is what every XopENTRY_set warned
          about under clang. 102 warnings, none of them ours.

0.04	2026-07-11

PLAN.md  view on Meta::CPAN

horus_stamp_version_variant(uuid, 5);
```

### `apophis_path_for(store_dir, id) -> path string`
2-level hex sharding: `a3bb189e-...` → `store/a3/bb/a3bb189e-...`

### `apophis_mkdir_p(path)`
Recursive directory creation in C using `mkdir()`.

### `apophis_store_file(path, content, content_len)`
Atomic write: write to `path.tmp.$$`, then `rename()`.

### `apophis_meta_write(path, meta_hv)`
Simple key=value sidecar format (no JSON dependency). Writes `path.meta`.

### `apophis_meta_read(path) -> HV*`
Reads `.meta` sidecar back into a hash.

## XSUBs (Perl-visible API)

### `Apophis->new(namespace => $ns, store_dir => $dir)`

PLAN.md  view on Meta::CPAN


### 2-Level Hex Sharding
```
/store/a3/bb/a3bb189e-8bf9-5f18-b3f6-1b2f5f5c1e3a
```
256 x 256 = 65,536 shard directories. ~15 entries per leaf at 1M objects.

### Atomic Writes
```
write to: /store/a3/bb/a3bb189e-...tmp.PID
rename:   /store/a3/bb/a3bb189e-...
```

### Metadata Sidecars
```
/store/a3/bb/a3bb189e-....meta
```
Simple `key=value\n` format. No JSON dependency.

## Implementation Phases

include/ap_abi.h  view on Meta::CPAN

     * check; a consumer should.
     *
     * A consumer must never reimplement this. The layout is Apophis's to
     * change, and a second copy of the sharding rule means the day it changes
     * every blob already on disk becomes unreachable through the consumer
     * while staying perfectly findable through Apophis. */
    int (*build_path)(char *out, size_t out_size,
                      const char *dir, STRLEN dirlen,
                      const char *id, STRLEN id_len);

    /* Create the parent directory if needed, then write via temp-and-rename,
     * which is what makes a store idempotent under concurrency. Croaks on an
     * I/O error, the way Apophis's own store does - it is not a probe. */
    void (*write_atomic)(pTHX_ const char *path,
                         const char *content, STRLEN len);
} ap_abi;

#endif /* AP_ABI_H */

lib/Apophis.pm  view on Meta::CPAN


Apophis is a XS content addressable storage library built on the
B<Horus> UUID library (RFC 9562).  It generates deterministic UUID v5
identifiers for arbitrary content using SHA-1 namespace hashing.

Same content always produces the same UUID.  Different namespaces produce
different UUIDs for the same content.

Stored objects are sharded across a 2-level hex directory tree (65,536
directories) for efficient filesystem access at scale.  Writes are atomic
(temp + rename).  CAS is naturally idempotent — no locking required.

=head1 METHODS

=head2 new

    my $ca = Apophis->new(
        namespace => 'myapp-files',    # required
        store_dir => '/var/store',     # optional default store path
    );

lib/Apophis.pm  view on Meta::CPAN

C<identify()> would for the same content.

=head2 store

    my $id = $ca->store(\$content);
    my $id = $ca->store(\$content, store_dir => '/other');
    my $id = $ca->store(\$content, meta => { mime_type => 'image/png' });

Identifies the content and writes it to the sharded store.  Returns the
UUID.  If the content already exists, returns immediately (CAS dedup).
Writes are atomic via temp file + rename.

=head2 fetch

    my $data_ref = $ca->fetch($id);

Returns a scalar reference to the stored content, or C<undef> if not found.

=head2 exists

    if ($ca->exists($id)) { ... }

ppport.h  view on Meta::CPAN

KEY_qx|5.003007||Viu
KEY_rand|5.003007||Viu
KEY_read|5.003007||Viu
KEY_readdir|5.003007||Viu
KEY_readline|5.003007||Viu
KEY_readlink|5.003007||Viu
KEY_readpipe|5.003007||Viu
KEY_recv|5.003007||Viu
KEY_redo|5.003007||Viu
KEY_ref|5.003007||Viu
KEY_rename|5.003007||Viu
KEY_require|5.003007||Viu
KEY_reset|5.003007||Viu
KEY_return|5.003007||Viu
KEY_reverse|5.003007||Viu
KEY_rewinddir|5.003007||Viu
KEY_rindex|5.003007||Viu
KEY_rmdir|5.003007||Viu
KEY_s|5.003007||Viu
KEY_say|5.009003||Viu
KEY_scalar|5.003007||Viu

ppport.h  view on Meta::CPAN

M_PAT_MODS|5.009005||Viu
MPH_BUCKETS|5.027011||Viu
MPH_RSHIFT|5.027011||Viu
MPH_VALt|5.027011||Viu
mPUSHi|5.009002|5.003007|p
mPUSHn|5.009002|5.003007|p
mPUSHp|5.009002|5.003007|p
mPUSHs|5.010001|5.003007|p
mPUSHu|5.009002|5.003007|p
mro_clean_isarev|5.013007||Viu
mro_gather_and_rename|5.013007||Viu
mro_get_from_name|||u
mro_get_linear_isa|5.009005|5.009005|
mro_get_linear_isa_c3|||i
mro_get_linear_isa_dfs|5.009005||Vi
MRO_GET_PRIVATE_DATA|5.010001|5.010001|
mro_get_private_data|||cu
mro_isa_changed_in|5.009005||Vi
mro_meta_dup|5.009005||Viu
mro_meta_init|||ciu
mro_method_changed_in|5.009005|5.009005|

ppport.h  view on Meta::CPAN

PerlLIO_link|5.006000||Viu
PerlLIO_lseek|5.005000||Viu
PerlLIO_lstat|5.005000||Viu
PerlLIO_mktemp|5.005000||Viu
PerlLIO_open3|5.005000||Viu
PerlLIO_open3_cloexec|5.027008||Viu
PerlLIO_open|5.005000||Viu
PerlLIO_open_cloexec|5.027008||Viu
PerlLIO_read|5.005000||Viu
PerlLIO_readlink|5.033005||Viu
PerlLIO_rename|5.005000||Viu
PerlLIO_setmode|5.005000||Viu
PerlLIO_stat|5.005000||Viu
PerlLIO_symlink|5.033005||Viu
PerlLIO_tmpnam|5.005000||Viu
PerlLIO_umask|5.005000||Viu
PerlLIO_unlink|5.005000||Viu
PerlLIO_utime|5.005000||Viu
PerlLIO_write|5.005000||Viu
PERL_LOADMOD_DENY|5.006000|5.003007|
PERL_LOADMOD_IMPORT_OPS|5.006000|5.003007|

ppport.h  view on Meta::CPAN

reg_temp_copy|5.009005||cViu
REG_TOP_LEVEL_BRANCHES_SEEN|5.019009||Viu
regtry|5.005000||Viu
REG_UNBOUNDED_QUANTIFIER_SEEN|5.019009||Viu
REG_UNFOLDED_MULTI_SEEN|5.019009||Viu
REG_VERBARG_SEEN|5.019009||Viu
REG_ZERO_LEN_SEEN|5.019009||Viu
re_indentf|5.023009||vViu
re_intuit_start|5.006000||cVu
re_intuit_string|5.006000||cVu
rename|5.005000||Viu
Renew|5.003007|5.003007|
Renewc|5.003007|5.003007|
RENUM|5.005000||Viu
RENUM_t8|5.035004||Viu
RENUM_t8_p8|5.033003||Viu
RENUM_t8_pb|5.033003||Viu
RENUM_tb|5.035004||Viu
RENUM_tb_p8|5.033003||Viu
RENUM_tb_pb|5.033003||Viu
re_op_compile|5.017001||Viu

ppport.h  view on Meta::CPAN

    return IS_NUMBER_IN_UV;
  }
  return 0;
}
#endif
#endif

/*
 * The grok_* routines have been modified to use warn() instead of
 * Perl_warner(). Also, 'hexdigit' was the former name of PL_hexdigit,
 * which is why the stack variable has been renamed to 'xdigit'.
 */

#ifndef grok_bin
#if defined(NEED_grok_bin)
static UV DPPP_(my_grok_bin)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result);
static
#else
extern UV DPPP_(my_grok_bin)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result);
#endif



( run in 1.014 second using v1.01-cache-2.11-cpan-a49fcb8fa48 )