This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
A PHP extension (C) exposing bsdiff_diff() and bsdiff_patch() plus a BsdiffException class. Supports PHP 7.2 through 8.5 on Linux, macOS, and Windows. Requires libbz2. Distributed through PECL (package.xml) and PIE (composer.json).
phpize
./configure # add --with-bz2=DIR if bzlib.h is not auto-detected
make
make test # run all .phpt tests
make test TESTS="--show-diff tests" # what CI runs
make test TESTS=tests/002_basic.phpt # run a single testconfig.m4 looks for BZip2 in /opt/homebrew/opt/bzip2, /usr/local/opt/bzip2, /opt/local, /usr/local, and /usr. On macOS without Homebrew bzip2, CI uses --with-bz2=/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr. On Ubuntu CI uses --with-libdir=x86_64-linux-gnu.
To clean generated build files: phpize --clean (or make clean for objects only).
CI (.github/workflows/ci.yml) builds and tests every PHP version from 7.2 to 8.5 on Ubuntu, macOS, and Windows (NTS and TS, via php/php-windows-builder). Windows builds use config.w32, so build changes must be made in both config.m4 and config.w32.
bsdiff.c/bsdiff.h/bspatch.c/bspatch.h: the bundled bsdiff library from https://github.com/mendsley/bsdiff (BSD-2-Clause). It works on in-memory buffers and talks to the outside world only through thebsdiff_stream/bspatch_streamcallback structs. Keep changes here minimal. One local change:offtout()andofftin()are non-static sophp_bsdiff.ccan write and parse the patch header.php_bsdiff.c: the PHP binding. It reads input files intozend_strings with PHP streams, compresses or decompresses the diff data with libbz2'sbz_streamAPI on top of the diff file's PHP stream, and passes read/write callbacks into the library. The patch format is the 16-byte magicENDSLEY/BSDIFF43, an 8-byte new-file size, then the bzip2-compressed control/diff/extra data.php_bsdiff.stub.phpis the source of truth for function signatures.php_bsdiff_arginfo.his generated from it with php-src'sbuild/gen_stub.php. Edit the stub and regenerate; do not hand-edit the arginfo header.php_bsdiff.hholdsPHP_BSDIFF_VERSIONand aRETURN_THROWS()shim for PHP < 8.0.
These rules matter, and tests check them:
- All allocations, both the bsdiff library's and libbz2's, go through
emalloc/efree. The library gets them viastream.malloc/stream.free; libbz2 gets them viabzalloc/bzfreeon its low-levelbz_streamAPI. They therefore count againstmemory_limitand show up inmemory_get_usage()and PHP's leak detector, and PHP reclaims them if a fatal error (a longjmp) skips the cleanup code. Do not reintroduce libcmalloc/free, or theBZFILEAPI, which allocates withmalloc. - All file I/O goes through
php_streams (php_stream_read/php_stream_write; noFILE*), so stream wrappers andopen_basedirbehave the same on every platform. Do not use rawfopenor path-basedVCWD_*calls on user-supplied paths. - Open files with explicit binary modes (
"rb"/"wb") for Windows. - Each function uses one
cleanup:label that releases everything and ends withif (EG(exception)) RETURN_THROWS();. Errors throwBsdiffExceptionthroughzend_throw_exception_ex(ce_bsdiff_exception, ...)and thengoto cleanup. A failed run removes its partially written output file if it created that file; inbsdiff_diff(), azend_tryblock does this for fatal errors too. - Everything in a diff file is untrusted. The output size in the header is checked against
SIZE_MAXand (best effort)memory_limitbefore allocating, modeling how the Zend memory manager rounds blocks, together with the 3.4 MB block that bzip2's decompressor allocates later (php_bsdiff_fits_memory_limit()); the bzip2 stream must end exactly wherebspatch()stops reading; and the amount of databspatch()may read is capped, which stops control data that produces no output. - A local file written by
bsdiff_patch()gets the old file's permission bits (& 0777), set through the file descriptor before any data is written. An existing file gets them only if it is a regular file that the path names directly (not through a symbolic link) and that has no other hard links; devices, FIFOs, and link targets keep their permissions. - Output files are opened through
php_bsdiff_open_output(): local files are created with"xb"first, so the code knows whether it created them, and only files it created are removed on failure. Other wrappers are opened with"wb"; they get no permission changes and are never removed.
Tests are .phpt files in tests/. They include leak checks (the memory_get_usage() delta must be exactly 0, after warming up stream internals), memory_limit exhaustion checks that expect a fatal error, and binary round-trips. Temporary files use the .out suffix (gitignored) and are removed in --CLEAN--.
package.xmllists every shipped file explicitly. When you add or rename a source or test file, update<contents>there.- A version bump changes
PHP_BSDIFF_VERSIONinphp_bsdiff.h, the current release and<notes>inpackage.xml(and moves the previous release into<changelog>), andCHANGELOG.md. - License: the extension is BSD-3-Clause and the bundled bsdiff library is BSD-2-Clause. Both texts are in
LICENSE.