diff --git a/.gitignore b/.gitignore index e76ef7cc785ea..628b3afc50c7d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,13 @@ # Auxiliary files from local workflows, your preferred editor, etc. should # be ignored locally using $GIT_DIR/info/exclude or ~/.gitexclude. +# excludes for macOS +**/*.DS_Store + +# auto-edited makefiles +src/test/Makefile +src/test/isolation/Makefile + # Global excludes across all subdirectories *.o *.obj @@ -44,3 +51,9 @@ lib*.pc /tmp_install/ /portlock/ /dist + + +# build output symlinks +postgres-pglite +postgresql +postgresql-REL_17_5_WASM diff --git a/libpglite/LIBPGLITE_DESIGN.md b/libpglite/LIBPGLITE_DESIGN.md new file mode 100644 index 0000000000000..a0227379dc434 --- /dev/null +++ b/libpglite/LIBPGLITE_DESIGN.md @@ -0,0 +1,346 @@ +## libpglite host-callback design (draft) + +### Overview + +Objective: make PGlite embeddable uniformly across environments (mobile, WASM, native) via a stable, versioned C ABI “host ops” surface. The host (wrapper) provides callbacks for transport (wire) and logging, plus optional filesystem mediation. libpglite uses only these callbacks; it avoids platform-specific globals, ad-hoc shared-memory layouts, and deep #ifdefs. + +Benefits: + +- One API surface for all hosts (React Native, web/WASM, desktop CLI) +- Clear ownership and lifetime rules for buffers +- Stable evolution via abi_version and optional capabilities +- Higher reliability: eliminates brittle offset math and global state in core paths + +--- + +## Proposed Host Ops ABI + +Host registers a versioned vtable at init. libpglite never reaches to platform globals; all host interaction goes through ops. The transport is byte-stream oriented; zero-copy is an optimization the host can choose. + +Minimal surface (coarse-grained for performance, especially WASM): + +- Transport + - reserve_response(min, out_ptr, out_cap): reserve a contiguous writable window for a full message frame + - commit_response(n): commit n bytes to the transport + - flush: finalize a batch and make committed bytes visible +- Logging + - log(level, msg) +- Versioning + - abi_version (strict check at init) + +Sketch: + +```c +typedef struct { + uint32_t abi_version; + void* host_ctx; + + // transport (zero-copy write) + int (*reserve_response)(void* ctx, size_t min, uint8_t** out_ptr, size_t* out_cap); + int (*commit_response)(void* ctx, size_t n); + int (*flush)(void* ctx); + + // logging + void (*log)(void* ctx, int level, const char* msg); +} PGLiteHostOps; +``` + +libpglite entry points: + +```c +typedef struct PGLiteInstance PGLiteInstance; + +int pglite_init(const PGLiteHostOps*, const PGLiteOptions*, PGLiteInstance** out); +int pglite_exec_protocol(PGLiteInstance*, const uint8_t* req, size_t req_len); +void pglite_close(PGLiteInstance*); +``` + +Notes: + +- Ownership: host documents lifetime (req/resp buffers). For zero-copy, host ensures buffers remain valid until libpglite signals done (flush/return). +- Single-threaded model: hosts do not consume output during exec; reserve_response must never block for space. The transport maintains a growable append-only buffer per instance. +- Callbacks must be non-throwing and must not longjmp; libpglite wraps callbacks and converts failures to error codes. + +--- + +## Performance and data movement + +Goals: + +- Minimize WASM/host boundary crossings (1–few calls per command/batch) +- Avoid superfluous copies; enable zero-copy where safe + +Principles: + +- Batching: libpq already buffers writes; the PQcomm bridge should minimize crossings and call flush sparingly. +- Input: read vtable can return a contiguous view of request bytes (pointer/length) to avoid extra copies; copying is acceptable when required by the platform. +- Output: write via reserve/commit into a host-provided transport buffer. Header (1+4) written contiguously; payload may be chunked across commits with strict ordering. +- Parsing stays on the host: the host parses protocol frames (including notices and NOTIFY) from the same byte stream; no per-frame callbacks in the bridge. + +Zero-copy API: + +- Scope: zero-copy applies to the transport buffer. Many messages are constructed first into a StringInfo (PG convention) before pq_putmessage; transport avoids extra copies beyond that. + +- reserve\*response(size_t min, uint8_t\** ptr, size*t\* cap) +- commit_response(size_t n) + +### Message framing with reserve/commit + +- For each pq_putmessage(msgtype, payload, len): + + 1. Write header contiguously: reserve_response(5, &ptr, &cap); write [msgtype][pg_hton32(len+4)]; commit_response(5) + 2. Write payload in one or more commits: while remaining > 0, reserve_response(min_chunk, &ptr, &cap); n = min(cap, remaining); memcpy(ptr, payload+offset, n); commit_response(n) + 3. Defer flushes; the bridge coalesces until an explicit pq_flush or size threshold + +- Non-blocking semantics: reserve_response must not block for space; the transport grows internally (or uses a one-frame scratch on failure) to satisfy putmessage(\_noblock). +- Ordering: preserve message order as produced by libpq. +- Atomicity: header is atomic; payload may be chunked across commits with strict ordering. + +#### Edge cases and fallbacks + +- Huge frames: If total exceeds any single window the host can provide, the host should either enlarge its window or we temporarily allocate a scratch buffer for this frame, fill it once, then commit into a larger host window when available (one extra copy for this case only). +- Capacity policy: Hosts should size windows at least as large as PQ_SEND_BUFFER_SIZE and ideally larger to avoid fragmentation for common messages. +- Read side: If the host cannot present a contiguous span for a request, the read vtable may fill PqRecvBuffer (one copy) before pq_startmsgread; behavior remains correct. +- Backpressure: Reserve is allowed to fail transiently; the bridge retries only after the host signals space is available; no tight spin loops. + +--- + +- WASM specifics: flush marks committed bytes; JS reads after the call returns. If memory grew during exec, the host must refresh HEAP views before reading. + +## Alternative architectures (for exploration) + +1. PQcomm bridge + host-ops (proposed) + +- Reuse the frontend/backend wire protocol; the PQcomm bridge streams bytes to/from host ops. + +2. Socket FD emulation (fake socket backed by shared memory) + +- Emulate a socket Port over a ring buffer. Likely not viable on WASM; adds complexity. + +3. SQL engine via SPI (bypass wire protocol) + +- Direct SQL execution via SPI/Portal. Faster/simpler for embedded use, but divergent from protocol semantics. + +4. Dual-mode execution (protocol + SPI) + +- Support both protocol and direct SPI for flexibility, at the cost of more surface/testing. + +5. Upstream-style PQcomm read vtable + +- Add a read vtable similar to write; cleaner separation but touches pqcomm deeper. + +6. Out-of-process worker with IPC ring buffer + +- Separate process/worker with shared-memory IPC; isolation gains, much higher complexity. + +--- + +## Clean design (no compatibility constraints) + +We explicitly drop compatibility with interactive_one.c and current CMA/linear-memory shims. The intent is a small, principled interface in pqcomm and a library entrypoint that drives a single backend session. + +### Core abstractions + +- Host I/O vtable (read + write): + - write: keep existing PQcommMethods for send path + - read: introduce PQcommReadMethods (name TBD) with hooks for: + - start_msg (begin a message; provides a contiguous view or a reader) + - recvbuf (fill/read into PqRecvBuffer or return a span) + - end_msg + - getbyte_if_available (optional, fast path) +- lib entrypoints (library surface): + - pglite_init(host_ops, options) -> instance + - pglite_exec_protocol(instance, req_bytes) -> out via host_ops (streamed or single buffer) + - pglite_close(instance) + +### Minimal, localized Postgres edits (bounded to pqcomm) + +- Add read-side vtable alongside PQcommMethods in libpq.h/pqcomm.c +- Refactor pq_startmsgread/pq_recvbuf/pq_getbyte_if_available to go through the read vtable when installed; default to socket implementation otherwise +- No changes to executor/planner/storage, no touching SPI/Portal APIs; MyProcPort still exists but is not required to be a real socket + +### Removal/simplification + +### Send-side semantics parity with PQcomm + +- putmessage_noblock: must not block. Either the host returns a window that always grows to fit, or the bridge builds the frame in a scratch buffer and defers the reserve/commit until flush (one extra copy only for these calls). +- flush_if_writable and is_send_pending: implement via host transport state. flush_if_writable may behave like flush; is_send_pending checks if committed-but-unflushed bytes exist. +- PqCommBusy: avoid reentrancy; never invoke host callbacks that re-enter libpq while busy. +- No file fallback: the transport has a single sink (host buffer). Remove any “redirect to file” concept. + +### Read-side coverage + +- getbyte_if_available fast path: optional read vtable hook to return a byte without blocking. +- Startup frames: support SSLRequest/CancelRequest/StartupMessage which are length-prefixed without msgtype; the first read may be a raw startup frame. +- Contiguous span vs fill: if host can’t present a contiguous span, the read vtable fills PqRecvBuffer (one copy) and proceeds. + +### Zero-copy corner cases + +- Very large frames: prefer host to grow a single window >= frame size; otherwise build once in a scratch buffer and copy into a larger window when available; do not split header/payload across windows. +- Backpressure: avoid tight loops; reserve may fail transiently; retry after host signals space. +- Alignment: host must return buffers aligned to >=4 bytes for length header writes. +- WASM memory growth: host must keep reserved pointers stable until commit; avoid invalidating memory views mid-write. + +- Remove interactive_one.c and CMA-specific code paths from the build (WASM/mobile) +- Remove PGL_MOBILE / EMSCRIPTEN read-path conditionals inside pq_startmsgread; the read vtable handles platform differences +- Retain a default socket read implementation for regular builds + +### WASM feasibility + +- The read/write vtables can be implemented via WASM imports (JS provides the host ops) +- Boundary-crossing minimized by batching and by allowing the read vtable to expose a contiguous span for each message + +### Performance + +- Write path unchanged in spirit (buffer then flush). We can add optional reserve/commit to allow zero-copy writes into a host-provided region +- Read path can be zero-copy by pointing PqRecvBuffer at host memory or by returning spans from the read vtable; otherwise, one memcpy per batch +- Vtable indirections are negligible vs protocol processing; WASM crossing kept coarse (1–few calls per batch) + +### Advantages vs current + +- Eliminates fragile global CMA offset conventions +- No wrapper-specific code in backend except a small, well-defined vtable in pqcomm +- Clearer portability: host provides a single, stable I/O surface; core stays the same + +--- + +## Filesystem requirements and host responsibilities + +This clarifies what FS operations are needed across phases and what the host must provide. The goal is to keep Postgres internals unchanged while allowing portability from strict POSIX to constrained environments (WASM, RN). + +### Phases and required operations + +1. Initdb (cluster creation) + +- Reads: runtime assets from share/postgresql (SQL scripts, templates) + - fopen/fread/fclose, stat +- Create directory tree under PGDATA + - mkdir, possibly mkdir -p equivalent + - optional symlink for pg_wal when using -X (can be disabled to avoid symlink) +- Create/overwrite small text files + - open(O_CREAT|O_TRUNC), write, close; chmod-like permissions (can be relaxed) +- Durability (optional, depends on mode) + - fsync files and directories; fsync parent directories; durable_rename + - In constrained mode, pass --no-sync and treat fsync as no-op + +2. Bootstrap catalog/data (backend single-user creating system catalogs) + +- Heavy relation file IO under base/, global/, pg_xact/, etc. + - open(O_CREAT|O_RDWR), pread/pwrite or read/write at offsets, ftruncate + - rename/unlink for file lifecycle; directory listing to scan/reset + - opendir/readdir/closedir, stat/lstat +- Temp files + - create in pg_temp; unlink on close +- Durability (checkpoint/WAL interactions) + - fdatasync/fsync for relation segments and WAL, directory fsync for parent dirs + - durable_rename for WAL segment rotation and other atomic updates + +3. Normal operation + +- Same as bootstrap plus periodic checkpoints, relfilenode creation, truncation +- Temp files for sorts/hash; deletions +- Optional: tablespaces (pg_tblspc symlinks) + +### Default approach: standard C/Posix file APIs + +- Prefer the platform toolchain’s libc/Posix file APIs everywhere (open/close/read/write/rename/mkdir, etc.). +- This works on: Linux/macOS/Windows (native ports already exist in PG), Android (bionic), iOS (Darwin), WASM (via Emscripten’s VFS like MEMFS/IDBFS/OPFS), WASI (via WASI-libc preopens). +- Host responsibilities per platform: + - Provide real paths for PGDATA and runtime assets (share/postgresql) + - For WASM: mount/initialize Emscripten FS (e.g., IDBFS/OPFS) before starting PG; handle async sync if using IDBFS + - For mobile (iOS/Android): supply sandbox-safe directories; set permissions loosely if needed + - Set durability knobs: initdb --no-sync and enableFsync=off when durability is not guaranteed; avoid symlinks (-X) unless supported + +### Capability profiles and behavior + +- durability=strict (default for native platforms) + - fsync/fdatasync/fsync_dir work normally; durable_rename honored + - initdb may perform full sync of PGDATA; wal_sync_method chosen appropriately +- durability=relaxed (for constrained envs like WASM) + - initdb uses --no-sync; enableFsync=off at runtime (fsync becomes no-op) + - symlink not required; avoid -X so pg_wal is a real directory +- links=absent (for platforms without symlink support) + - disallow tablespace symlinks and WAL symlink; enforce via options/flags + +### Runtime assets (share/) + +- initdb needs read-only access to share/postgresql SQL files and templates +- Host must expose a path or virtual FS mount for these assets; the location is passed via options (e.g., runtimeDir/share) + +### Mapping to Postgres calls + +- fd.c: pg_fsync, pg_fdatasync, fsync_fname(\_ext), PathNameOpenFile, File\*, PathNameDeleteTemporaryFile, durable_rename +- file_utils.c: fsync_parent_path, durable_rename (frontend) +- initdb.c: mkdir, symlink (optional), fopen/fwrite, stat, recursive sync +- reinit.c: directory scans, unlink/reset flows +- postinit.c: ValidatePgVersion, database path checks + +### Suggested approach + +- Host provides PGDATA/runtime paths and sets durability options (initdb --no-sync, enableFsync) to match the environment. +- Add capability flags in host ops: can_fsync, has_symlink, atomic_rename to guide behavior. +- Use existing Postgres durability controls (enableFsync GUC, initdb --no-sync) rather than intercepting FS calls. + +--- + +## Concrete change list (high-level) + +1. Add PQcommReadMethods in src/include/libpq/libpq.h (or a sibling header): + - struct with function pointers: startmsg, recvbuf/fill, endmsg, getbyte_if_available + - global pointer PqCommReadMethods with a default socket-backed implementation +2. Modify src/backend/libpq/pqcomm.c: + - pq_startmsgread -> calls PqCommReadMethods->startmsg() + - pq_recvbuf -> calls PqCommReadMethods->recvbuf() + - pq_getbyte_if_available -> optionally calls vtable fast path + - maintain existing socket code as the default implementation +3. Provide host-ops to pqcomm bridge (in libpglite): + - A “socketless” read implementation that sources bytes from host-provided memory/buffers + - A write implementation bridged to host_ops->reserve_response/commit_response/flush +4. Library entry: create pglite_init/exec_protocol/close; set up Port and install vtables per-instance (guard with thread-local if needed) +5. Remove compatibility shims: interactive_one.c, CMA read-path conditionals; WASM/mobile adapters implement host ops only + +Risk containment: + +- All edits localized to pqcomm + new libpglite glue; rest of backend remains untouched +- Default builds (sockets) unaffected; host-less builds use the default vtables + +--- + +## Open design choices and risks + +Choices + +- Read vtable shape: span-based vs buffer-fill +- Zero-copy write interface: add reserve/commit now or later + +Risks and mitigations + +1. Error handling with longjmp + +- Ensure callbacks are invoked only in safe regions; wrap with PG_TRY/PG_CATCH; convert to error codes. + +2. WASM boundary overhead + +- Keep crossings coarse; avoid per-frame/row hooks; parse on host. + +3. Initialization (initdb/runtime assets) + +- Hosts differ in FS access; pass paths/mounts; pre-bundle assets for WASM. + +4. Buffer semantics and alignment + +- Provide flat buffers with explicit lengths; avoid offset conventions. + +5. ABI stability + +- C headers only; fixed-width types; explicit alignment; versioned abi_version; no exceptions. + +--- + +## Iteration plan + +1. Add PQcomm read-vtable and bridge write path to host ops; compile-time gated; default to sockets when not installed. +2. Implement libpglite entrypoints (init/exec/close) with transport-only host ops. +3. Remove interactive_one.c and platform-specific CMA paths from the build; delete mobile-build wrappers that are no longer needed. +4. Wire up logging; notices/notifications remain on the transport (no extra hooks). +5. Add capability flags and assertions; add cross-platform unit/integration tests. diff --git a/mobile-build/README.md b/mobile-build/README.md new file mode 100644 index 0000000000000..d4defd5b93960 --- /dev/null +++ b/mobile-build/README.md @@ -0,0 +1,37 @@ +# PGLite Mobile Cross-Compile (Android/iOS) + +This folder contains scripts to cross-compile PostgreSQL and the PGLite glue for Android (NDK) and iOS (Xcode toolchains), producing prebuilt static libraries and headers consumed by the React Native module. + +Artifacts layout (per plan): + +- dist/mobile/android//{include,lib} + - libpgcore_mobile.a + - libpglite_glue_mobile.a +- dist/mobile/ios/{arm64, x86_64-sim}/{include,lib} + - libpgcore_mobile.a + - libpglite_glue_mobile.a + +Notes: + +- These scripts are initial scaffolding; you may need to adjust paths (NDK, SDK versions, PG branch) and add additional PG libs to the merge step based on link errors. +- For iOS, consider building an XCFramework for distribution via CocoaPods. + +## Nix-based reproducible builds + +We provide a Nix flake that sets up the toolchain and host tools (GNU make, android ndk, perl, coreutils, etc.) for reproducible builds. + +Prereqs: + +- Install Nix (https://nixos.org/download) +- Optional: direnv + use flake (we include .envrc) + +Shells: + +- Android: + - nix develop .#android + - or build-mobile.sh will enter the shell automatically + - PLATFORM=android ABI=arm64-v8a PG_BRANCH=REL_17_5_WASM ./mobile-build/build-mobile.sh +- iOS (requires Xcode/CLT installed locally): + - iOS not yet working/tested + - nix develop .#ios + - PLATFORM=ios ARCH=arm64 ./build-mobile.sh diff --git a/mobile-build/build-mobile.sh b/mobile-build/build-mobile.sh new file mode 100755 index 0000000000000..3373b17bebf9f --- /dev/null +++ b/mobile-build/build-mobile.sh @@ -0,0 +1,646 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Unified mobile build for PGLite native prebuilt libs +# Platforms: ANDROID (arm64-v8a, x86_64) and iOS (arm64, arm64-sim, x86_64) +# +# Usage examples: +# PLATFORM=android ABI=arm64-v8a ANDROID_NDK=$HOME/Android/Sdk/ndk/27.1.12297006 \ +# bash postgres-pglite/mobile-build/build-mobile.sh +# +# PLATFORM=ios ARCH=arm64-sim \ +# bash postgres-pglite/mobile-build/build-mobile.sh # Apple Silicon simulator +# +# PLATFORM=ios ARCH=arm64 \ +# bash postgres-pglite/mobile-build/build-mobile.sh # iOS device +# +# Outputs: +# dist/mobile/android//{include,lib,runtime/share/postgresql} +# dist/mobile/ios//{include,lib,runtime/share/postgresql} + +PLATFORM=${PLATFORM:-android} # android|ios +ABI=${ABI:-arm64-v8a} # android ABI (arm64-v8a|x86_64) +ARCH=${ARCH:-arm64-sim} # ios arch (arm64|arm64-sim|x86_64) +API=${API:-24} # android min API for toolchain wrapper +PG_BRANCH=${PG_BRANCH:-REL_17_5_WASM} + +# Resolve paths +SCRIPT_ABS=$(cd "$(dirname "$0")" && pwd)/"$(basename "$0")" +REPO_ROOT=$(cd "$(dirname "$0")/.." && pwd) # postgres-pglite +FLAKE_ROOT=$(cd "$REPO_ROOT/.." && pwd) # repo root (has flake.nix) +PGSRC=${PGSRC:-"$REPO_ROOT/postgresql-${PG_BRANCH}"} +BUILD_BASE="$REPO_ROOT/out/mobile" +DIST_BASE="$REPO_ROOT/dist/mobile" + +# If nix is available and we are not already inside a nix devShell, re-enter via nix +maybe_enter_nix() { + if [ "${NO_NIX:-0}" != "1" ] && command -v nix >/dev/null 2>&1; then + if [ -z "${IN_NIX_SHELL:-}" ]; then + case "$PLATFORM" in + android) + ATTR="android" + echo "Re-executing inside Nix devShell: $ATTR" + exec nix develop "$FLAKE_ROOT#${ATTR}" --command bash -lc \ + "PLATFORM='$PLATFORM' ABI='$ABI' ARCH='$ARCH' API='$API' PG_BRANCH='$PG_BRANCH' NO_NIX=1 '$SCRIPT_ABS'" + ;; + ios) + # Skip Nix for iOS builds on macOS - use system tools instead + echo "Using system tools for iOS build (skipping Nix devShell)" + ;; + *) echo "Unknown PLATFORM '$PLATFORM' (expected android|ios)" >&2; exit 2 ;; + esac + fi + fi +} + +maybe_enter_nix + +# Ensure Perl (needed by PostgreSQL build) and GNU Make +require_tools() { + if ! command -v perl >/dev/null 2>&1; then + echo "Perl is required. On macOS: brew install perl" >&2 + exit 2 + fi + # Prefer GNU make under common names: gmake, gnumake, then make if GNU + if command -v gmake >/dev/null 2>&1; then + MAKE_BIN=$(command -v gmake) + elif command -v gnumake >/dev/null 2>&1; then + MAKE_BIN=$(command -v gnumake) + else + MAKE_BIN=$(command -v make) + if ! "$MAKE_BIN" --version 2>&1 | head -n1 | grep -qi "GNU Make"; then + echo "GNU make is required. On macOS: brew install make (use gmake) or rely on Nix devShell" >&2 + exit 2 + fi + fi + export PERL=$(command -v perl) + export MAKE="$MAKE_BIN" +} + +# Android toolchain setup (NDK) +setup_android() { + : "${ANDROID_NDK:?ANDROID_NDK must be set}" + case "$ABI" in + arm64-v8a) TRIPLE=aarch64-linux-android ;; + x86_64) TRIPLE=x86_64-linux-android ;; + *) echo "Unsupported ABI: $ABI" >&2; exit 2;; + esac + # Locate a valid NDK LLVM toolchain directory. Nix androidenv layouts can differ. + TOOL="" + if [ -d "$ANDROID_NDK/toolchains/llvm/prebuilt" ]; then + # Preferred: prebuilt host-tagged dirs + FIRST_PREBUILT="" + for d in "$ANDROID_NDK"/toolchains/llvm/prebuilt/*; do + [ -d "$d" ] || continue + [ -z "$FIRST_PREBUILT" ] && FIRST_PREBUILT="$d" + if [ -x "$d/bin/${TRIPLE}${API}-clang" ] && [ -x "$d/bin/${TRIPLE}${API}-clang++" ]; then + TOOL="$d" + break + fi + done + [ -z "$TOOL" ] && [ -n "$FIRST_PREBUILT" ] && TOOL="$FIRST_PREBUILT" + fi + if [ -z "$TOOL" ] && [ -d "$ANDROID_NDK/toolchains/llvm/bin" ]; then + # Some Nix NDKs expose llvm/bin directly without prebuilt/ + TOOL="$ANDROID_NDK/toolchains/llvm" + fi + if [ -z "$TOOL" ]; then + # Last resort: find a llvm/* dir with bin/clang + for d in "$ANDROID_NDK"/toolchains/llvm/*; do + [ -d "$d" ] || continue + if [ -x "$d/bin/clang" ]; then TOOL="$d"; break; fi + done + fi + if [ -z "$TOOL" ]; then + echo "No valid NDK LLVM toolchain found under $ANDROID_NDK/toolchains/llvm" >&2 + exit 2 + fi + echo "Using NDK toolchain at: $TOOL" + + WRAP_CC="$TOOL/bin/${TRIPLE}${API}-clang" + WRAP_CXX="$TOOL/bin/${TRIPLE}${API}-clang++" + if [ -x "$WRAP_CC" ] && [ -x "$WRAP_CXX" ]; then + export CC="$WRAP_CC" + export CXX="$WRAP_CXX" + else + export CC="$TOOL/bin/clang --target=${TRIPLE}${API} --sysroot=$TOOL/sysroot" + export CXX="$TOOL/bin/clang++ --target=${TRIPLE}${API} --sysroot=$TOOL/sysroot" + fi + export AR="$TOOL/bin/llvm-ar" + export RANLIB="$TOOL/bin/llvm-ranlib" + export STRIP="$TOOL/bin/llvm-strip" + export LD="$TOOL/bin/ld.lld" + export CFLAGS="-fPIC -O2 -DHAVE_SPINLOCKS" + export LDFLAGS="-fPIC" + + # Keep flags minimal; DSM selection is handled via config.site-android and configure + export CPPFLAGS="${CPPFLAGS:-} -DHAVE_SPINLOCKS -D__ANDROID__ -DPGL_MOBILE" + + BUILD_DIR="$BUILD_BASE/android/$ABI" + DIST_DIR="$DIST_BASE/android/$ABI" + CONFIG_SITE_FILE="$REPO_ROOT/mobile-build/config.site-android" +} + +# iOS toolchain setup (Xcode) +setup_ios() { + echo "DEBUG: Starting iOS setup for ARCH=$ARCH" + + if [[ "$ARCH" == "arm64" ]]; then + SDK=iphoneos + echo "DEBUG: Using iphoneos SDK for device" + echo "DEBUG: Getting clang path..." + CC_BIN=$(xcrun --sdk iphoneos -f clang) + echo "DEBUG: CC_BIN=$CC_BIN" + echo "DEBUG: Getting clang++ path..." + CXX_BIN=$(xcrun --sdk iphoneos -f clang++) + echo "DEBUG: CXX_BIN=$CXX_BIN" + TRIPLE=arm64-apple-darwin + ARCH_FLAG="-arch arm64" + MIN_FLAG="-miphoneos-version-min=13.0" + OUT_ARCH_DIR="arm64" + elif [[ "$ARCH" == "arm64-sim" ]]; then + SDK=iphonesimulator + echo "DEBUG: Using iphonesimulator SDK for Apple Silicon simulator" + echo "DEBUG: Getting clang path..." + CC_BIN=$(xcrun --sdk iphonesimulator -f clang) + echo "DEBUG: CC_BIN=$CC_BIN" + echo "DEBUG: Getting clang++ path..." + CXX_BIN=$(xcrun --sdk iphonesimulator -f clang++) + echo "DEBUG: CXX_BIN=$CXX_BIN" + TRIPLE=arm64-apple-darwin + ARCH_FLAG="-arch arm64" + MIN_FLAG="-mios-simulator-version-min=13.0" + OUT_ARCH_DIR="arm64-sim" + elif [[ "$ARCH" == "x86_64" ]]; then + SDK=iphonesimulator + echo "DEBUG: Using iphonesimulator SDK for Intel simulator" + echo "DEBUG: Getting clang path..." + CC_BIN=$(xcrun --sdk iphonesimulator -f clang) + echo "DEBUG: CC_BIN=$CC_BIN" + echo "DEBUG: Getting clang++ path..." + CXX_BIN=$(xcrun --sdk iphonesimulator -f clang++) + echo "DEBUG: CXX_BIN=$CXX_BIN" + TRIPLE=x86_64-apple-darwin + ARCH_FLAG="-arch x86_64" + MIN_FLAG="-mios-simulator-version-min=13.0" + OUT_ARCH_DIR="x86_64-sim" + else + echo "ERROR: Unsupported iOS ARCH: $ARCH (supported: arm64, arm64-sim, x86_64)" >&2 + exit 1 + fi + echo "DEBUG: Getting SDK root path..." + SDKROOT=$(xcrun --sdk "$SDK" --show-sdk-path) + echo "DEBUG: SDKROOT=$SDKROOT" + export CC="$CC_BIN $ARCH_FLAG -isysroot $SDKROOT $MIN_FLAG" + export CXX="$CXX_BIN $ARCH_FLAG -isysroot $SDKROOT $MIN_FLAG" + # Clear any existing sysroot flags completely and set iOS sysroot only + echo "DEBUG: Before cleanup - CPPFLAGS=${CPPFLAGS:-}" + echo "DEBUG: Before cleanup - LDFLAGS=${LDFLAGS:-}" + unset CPPFLAGS LDFLAGS CFLAGS + export CFLAGS="-fPIC -O2 -DHAVE_SPINLOCKS" + export CPPFLAGS="-isysroot $SDKROOT" + export LDFLAGS="-isysroot $SDKROOT" + echo "DEBUG: After cleanup - CFLAGS=$CFLAGS" + echo "DEBUG: After cleanup - CPPFLAGS=$CPPFLAGS" + echo "DEBUG: After cleanup - LDFLAGS=$LDFLAGS" + echo "DEBUG: Getting ar path..." + export AR=$(xcrun -f ar) + echo "DEBUG: AR=$AR" + echo "DEBUG: Getting ranlib path..." + export RANLIB=$(xcrun -f ranlib) + echo "DEBUG: RANLIB=$RANLIB" + echo "DEBUG: Getting strip path..." + export STRIP=$(xcrun -f strip) + echo "DEBUG: STRIP=$STRIP" + # Consolidate all flags here - don't override the cleanup above + export CFLAGS="$CFLAGS" # Keep the clean CFLAGS from above + export LDFLAGS="$LDFLAGS -fPIC" # Add -fPIC to the clean LDFLAGS + export CPPFLAGS="$CPPFLAGS -DHAVE_SPINLOCKS -DPGL_MOBILE -D_FORTIFY_SOURCE=0" # Add defines to clean CPPFLAGS + + BUILD_DIR="$BUILD_BASE/ios/$OUT_ARCH_DIR" + DIST_DIR="$DIST_BASE/ios/$OUT_ARCH_DIR" + CONFIG_SITE_FILE="$REPO_ROOT/mobile-build/config.site-ios" + echo "DEBUG: iOS setup completed successfully" +} + +# Common configure flags (aligned with wasm build spirit) +configure_flags() { + CNF_COMMON=( + "--disable-largefile" "--without-llvm" + "--without-pam" "--with-openssl=no" "--without-readline" + "--without-icu" "--without-libxml" "--without-zlib" + ) + # Add iOS-specific flags + if [ "$PLATFORM" = "ios" ]; then + CNF_COMMON+=( + "--disable-shared" + ) + fi +} + +# Build PostgreSQL and package artifacts +build_pg() { + mkdir -p "$BUILD_DIR" "$DIST_DIR/lib" "$DIST_DIR/include" + + require_tools + configure_flags + + echo "Configuring PostgreSQL (in-tree) in $PGSRC, install prefix=$BUILD_DIR/install ..." + pushd "$PGSRC" >/dev/null + CONFIG_SITE="$CONFIG_SITE_FILE" ./configure --host=${TRIPLE} \ + --prefix="$BUILD_DIR/install" "${CNF_COMMON[@]}" + + # Detect GNU make + if command -v gmake >/dev/null 2>&1; then MAKE_BIN=$(command -v gmake); else MAKE_BIN=$(command -v make); fi + NCPU=$( (sysctl -n hw.ncpu 2>/dev/null) || (nproc 2>/dev/null) || echo 1 ) + + # Clean any previous object files to avoid mixing different CFLAGS + "$MAKE_BIN" clean || true + + # Ensure a clean rebuild to avoid mixing objects compiled with different CFLAGS + "$MAKE_BIN" clean || true + find src -name '*.o' -type f -delete || true + + # Skip test directories (following WASM pattern) + if [ -d src/test ]; then + cat > src/test/Makefile < src/test/isolation/Makefile </dev/null + + # Headers and runtime + cp -R "$BUILD_DIR/install/include/"* "$DIST_DIR/include/" + + # Ensure the catalog assets (share/postgresql) are present and complete + # Prefer installed artifacts; handle cases where postgres.bki is under share/ (not share/postgresql) + INSTALL_SHARE_POSTGRESQL="$BUILD_DIR/install/share/postgresql" + INSTALL_SHARE="$BUILD_DIR/install/share" + if [ ! -d "$INSTALL_SHARE_POSTGRESQL" ]; then + mkdir -p "$INSTALL_SHARE_POSTGRESQL" + fi + # Ensure postgres.bki lands under share/postgresql + if [ ! -f "$INSTALL_SHARE_POSTGRESQL/postgres.bki" ]; then + if [ -f "$INSTALL_SHARE/postgres.bki" ]; then + cp -f "$INSTALL_SHARE/postgres.bki" "$INSTALL_SHARE_POSTGRESQL/" + elif [ -f "$PGSRC/src/backend/catalog/postgres.bki" ]; then + cp -f "$PGSRC/src/backend/catalog/postgres.bki" "$INSTALL_SHARE_POSTGRESQL/" + else + echo "Warning: postgres.bki not found in install or source; initdb will fail" >&2 + fi + fi + # Copy the known SQL/text inputs; prefer installed share dir if present + for f in \ + system_views.sql system_functions.sql system_constraints.sql \ + information_schema.sql sql_features.txt snowball_create.sql \ + postgresql.conf.sample pg_hba.conf.sample pg_ident.conf.sample + do + if [ ! -f "$INSTALL_SHARE_POSTGRESQL/$f" ]; then + if [ -f "$INSTALL_SHARE_POSTGRESQL/$f" ]; then + : # already there + elif [ -f "$INSTALL_SHARE/$f" ]; then + cp -f "$INSTALL_SHARE/$f" "$INSTALL_SHARE_POSTGRESQL/" + elif [ -f "$PGSRC/src/backend/catalog/$f" ]; then + cp -f "$PGSRC/src/backend/catalog/$f" "$INSTALL_SHARE_POSTGRESQL/" || true + elif [ -f "$PGSRC/src/backend/snowball/$f" ]; then + cp -f "$PGSRC/src/backend/snowball/$f" "$INSTALL_SHARE_POSTGRESQL/" || true + elif [ -f "$PGSRC/src/backend/utils/misc/$f" ]; then + cp -f "$PGSRC/src/backend/utils/misc/$f" "$INSTALL_SHARE_POSTGRESQL/" || true + fi + fi + done + + if [ -d "$INSTALL_SHARE_POSTGRESQL" ]; then + mkdir -p "$DIST_DIR/runtime/share" + cp -R "$INSTALL_SHARE_POSTGRESQL" "$DIST_DIR/runtime/share/" + fi + + # Also copy timezone data (share/timezone), abbreviation sets (share/timezonesets), + # text search dictionaries (share/tsearch_data), and extensions (share/extension) + local INSTALL_SHARE_TZ="$BUILD_DIR/install/share/timezone" + local INSTALL_SHARE_TZSETS="$BUILD_DIR/install/share/timezonesets" + local INSTALL_SHARE_TSEARCH="$BUILD_DIR/install/share/tsearch_data" + local INSTALL_SHARE_EXT="$BUILD_DIR/install/share/extension" + if [ -d "$INSTALL_SHARE_TZ" ]; then + mkdir -p "$DIST_DIR/runtime/share" + cp -R "$INSTALL_SHARE_TZ" "$DIST_DIR/runtime/share/" + else + echo "Warning: timezone directory not found at $INSTALL_SHARE_TZ" + fi + if [ -d "$INSTALL_SHARE_TZSETS" ]; then + mkdir -p "$DIST_DIR/runtime/share" + cp -R "$INSTALL_SHARE_TZSETS" "$DIST_DIR/runtime/share/" + else + echo "Warning: timezonesets directory not found at $INSTALL_SHARE_TZSETS" + fi + if [ -d "$INSTALL_SHARE_TSEARCH" ]; then + mkdir -p "$DIST_DIR/runtime/share" + cp -R "$INSTALL_SHARE_TSEARCH" "$DIST_DIR/runtime/share/" + else + echo "Warning: tsearch_data directory not found at $INSTALL_SHARE_TSEARCH" + # Fallback: copy snowball and tsearch_data from source directories if present + if [ -d "$PGSRC/src/backend/snowball" ]; then + mkdir -p "$DIST_DIR/runtime/share/tsearch_data" + cp -f "$PGSRC"/src/backend/snowball/stopwords/*.stop "$DIST_DIR/runtime/share/tsearch_data/" 2>/dev/null || true + cp -f "$PGSRC"/src/backend/snowball/snowball_create.sql "$DIST_DIR/runtime/share/postgresql/" 2>/dev/null || true + echo "Added snowball stopwords and snowball_create.sql from source" + fi + if [ -d "$PGSRC/src/backend/tsearch" ]; then + mkdir -p "$DIST_DIR/runtime/share/tsearch_data" + cp -f "$PGSRC"/src/backend/tsearch/*.dict "$DIST_DIR/runtime/share/tsearch_data/" 2>/dev/null || true + cp -f "$PGSRC"/src/backend/tsearch/*.affix "$DIST_DIR/runtime/share/tsearch_data/" 2>/dev/null || true + echo "Added tsearch dictionaries/affixes from source" + fi + fi + if [ -d "$INSTALL_SHARE_EXT" ]; then + mkdir -p "$DIST_DIR/runtime/share" + cp -R "$INSTALL_SHARE_EXT" "$DIST_DIR/runtime/share/" + else + echo "Note: extension directory not found at $INSTALL_SHARE_EXT (ok if not building contrib)" + # Fallback: copy core plpgsql extension files from source tree if present + if [ -f "$PGSRC/src/pl/plpgsql/src/plpgsql.control" ]; then + mkdir -p "$DIST_DIR/runtime/share/extension" + cp -f "$PGSRC/src/pl/plpgsql/src/plpgsql.control" "$DIST_DIR/runtime/share/extension/" + # Copy all versioned SQLs for plpgsql + for sql in "$PGSRC"/src/pl/plpgsql/src/plpgsql--*.sql; do + [ -f "$sql" ] && cp -f "$sql" "$DIST_DIR/runtime/share/extension/" + done + echo "Added plpgsql extension files from source tree" + fi + fi +} + + +# Build glue and merge libs +build_glue_and_merge() { + pushd "$BUILD_DIR" >/dev/null + + # Glue: mirror WASM by compiling pg_main.c (which includes interactive_one.c, pgl_mains.c, etc.) + $CC \ + -I"$BUILD_DIR/install/include" -I"$PGSRC/src/include" -I"$PGSRC/src" \ + -I"$PGSRC/src/interfaces/libpq" \ + -I"$REPO_ROOT/mobile-build" \ + -include "$REPO_ROOT/mobile-build/wasm_common_mobile.h" \ + -include "$REPO_ROOT/mobile-build/pgl_mobile_compat.h" \ + -DPGL_LIB_ONLY -DPGL_MOBILE -UHAVE_SHM_OPEN \ + -fPIC -c "$REPO_ROOT/pglite-wasm/pg_main.c" -o pg_main.o + # Glue extras: provide mobile alias and minimal helpers + $CC \ + -I"$BUILD_DIR/install/include" -I"$PGSRC/src/include" -I"$PGSRC/src" \ + -I"$REPO_ROOT/mobile-build" -DPGL_MOBILE -fPIC -c "$REPO_ROOT/mobile-build/pgl_mobile_shims.c" -o pgl_mobile_shims.o + # Backend weak stubs to satisfy backend-only globals when not linking postmaster + $CC \ + -I"$BUILD_DIR/install/include" -I"$PGSRC/src/include" -I"$PGSRC/src" \ + -I"$REPO_ROOT/mobile-build" -DPGL_MOBILE -fPIC -c "$REPO_ROOT/mobile-build/pgl_backend_stubs.c" -o pgl_backend_stubs.o + # Mobile CMA shim providing get_buffer_addr/get_buffer_size + $CC \ + -I"$BUILD_DIR/install/include" -I"$PGSRC/src/include" -I"$PGSRC/src" \ + -I"$REPO_ROOT/mobile-build" -DPGL_MOBILE -fPIC -c "$REPO_ROOT/mobile-build/sdk_port-mobile.c" -o sdk_port-mobile.o + # Mobile comm methods to route libpq I/O to CMA + $CC \ + -I"$BUILD_DIR/install/include" -I"$PGSRC/src/include" -I"$PGSRC/src" \ + -I"$REPO_ROOT/mobile-build" -DPGL_MOBILE -fPIC -c "$REPO_ROOT/mobile-build/pgl_mobile_comm.c" -o pgl_mobile_comm.o + + # Glue archive; includes pg_main and mobile shims/stubs + "$AR" -r -cs libpglite_glue_mobile.a pg_main.o pgl_mobile_shims.o pgl_backend_stubs.o sdk_port-mobile.o pgl_mobile_comm.o + + # Merge core PG libs (reuse the server build outputs like WASM) + # Prefer consuming installed archives if present; otherwise fall back to server variants in source tree + pushd "$BUILD_DIR" >/dev/null + + # Collect backend object files into MRI ADDMOD lines + BACKEND_MRI="$BUILD_DIR/backend_objs.mri" + : > "$BACKEND_MRI" + while IFS= read -r obj; do + echo "ADDMOD $obj" >> "$BACKEND_MRI" + done < <(find "$PGSRC/src/backend" -name '*.o' -type f | sort) + + # Also collect timezone objects (needed by timestamp/varlena etc.) + TZ_MRI="$BUILD_DIR/tz_objs.mri" + : > "$TZ_MRI" + while IFS= read -r obj; do + echo "ADDMOD $obj" >> "$TZ_MRI" + done < <(find "$PGSRC/src/timezone" -name '*.o' -type f | sort) + + # Since BSD ar doesn't support MRI scripts, we'll create the archive manually + # Start with the first library + cp "$PGSRC/src/common/libpgcommon_srv.a" libpgcore_mobile.a + + # Extract and add objects from other libraries + echo "Extracting and merging PostgreSQL libraries..." + mkdir -p temp_extract + + # Add libpgport_srv.a + (cd temp_extract && "$AR" -x "$PGSRC/src/port/libpgport_srv.a" && "$AR" -r -u ../libpgcore_mobile.a *.o && rm -f *.o) + + # Add backend objects from MRI files by extracting the library paths and object files + # This is a simplified approach - we'll add the main backend library if it exists + if [ -f "$PGSRC/src/backend/libpostgres.a" ]; then + (cd temp_extract && "$AR" -x "$PGSRC/src/backend/libpostgres.a" && "$AR" -r -u ../libpgcore_mobile.a *.o && rm -f *.o) + else + # If libpostgres.a doesn't exist, add all backend object files directly + echo "libpostgres.a not found, adding backend objects directly..." + find "$PGSRC/src/backend" -name '*.o' -type f -exec "$AR" -r -u libpgcore_mobile.a {} + + fi + + # Also add timezone objects which are needed + find "$PGSRC/src/timezone" -name '*.o' -type f -exec "$AR" -r -u libpgcore_mobile.a {} + + + rm -rf temp_extract + popd >/dev/null + + # Additional mobile-only weak stubs remain allowed if specific postmaster-only globals are unresolved + + mv -f libpglite_glue_mobile.a "$DIST_DIR/lib/" || true + mv -f libpgcore_mobile.a "$DIST_DIR/lib/" + + popd >/dev/null +} + +# Copy artifacts into the React Native module for Android +copy_into_rn_android() { + local RN_DIR="$FLAKE_ROOT/packages/pglite-react-native/android" + local ABI_DIR + case "$ABI" in + arm64-v8a) ABI_DIR="arm64-v8a" ;; + x86_64) ABI_DIR="x86_64" ;; + *) echo "Unsupported ABI: $ABI" >&2; return 1;; + esac + + echo "Installing artifacts into RN module at $RN_DIR" + mkdir -p "$RN_DIR/src/main/jni/$ABI_DIR" || true + cp -f "$DIST_DIR/lib/libpgcore_mobile.a" "$RN_DIR/src/main/jni/$ABI_DIR/" + cp -f "$DIST_DIR/lib/libpglite_glue_mobile.a" "$RN_DIR/src/main/jni/$ABI_DIR/" + + # Copy runtime catalogs (share/postgresql) and timezone assets into RN assets + local ASSET_ROOT="$RN_DIR/src/main/assets/pglite" + local ASSET_SHARE="$ASSET_ROOT/share" + # postgresql catalogs + if [ -d "$DIST_DIR/runtime/share/postgresql" ]; then + local ASSET_PG_DST="$ASSET_SHARE/postgresql" + mkdir -p "$ASSET_PG_DST" + rsync -a --delete "$DIST_DIR/runtime/share/postgresql/" "$ASSET_PG_DST/" 2>/dev/null || { + rm -rf "$ASSET_PG_DST"/* + cp -R "$DIST_DIR/runtime/share/postgresql/." "$ASSET_PG_DST/" + } + echo "Copied runtime catalogs to $ASSET_PG_DST" + else + echo "Warning: runtime catalogs not found at $DIST_DIR/runtime/share/postgresql" + fi + # timezone directory + if [ -d "$DIST_DIR/runtime/share/timezone" ]; then + local ASSET_TZ_DST="$ASSET_SHARE/timezone" + mkdir -p "$ASSET_TZ_DST" + rsync -a --delete "$DIST_DIR/runtime/share/timezone/" "$ASSET_TZ_DST/" 2>/dev/null || { + rm -rf "$ASSET_TZ_DST"/* + cp -R "$DIST_DIR/runtime/share/timezone/." "$ASSET_TZ_DST/" + } + echo "Copied timezone data to $ASSET_TZ_DST" + else + echo "Warning: timezone data directory not found at $DIST_DIR/runtime/share/timezone" + fi + # timezonesets directory + if [ -d "$DIST_DIR/runtime/share/timezonesets" ]; then + local ASSET_TZSETS_DST="$ASSET_SHARE/timezonesets" + mkdir -p "$ASSET_TZSETS_DST" + rsync -a --delete "$DIST_DIR/runtime/share/timezonesets/" "$ASSET_TZSETS_DST/" 2>/dev/null || { + rm -rf "$ASSET_TZSETS_DST"/* + cp -R "$DIST_DIR/runtime/share/timezonesets/." "$ASSET_TZSETS_DST/" + } + echo "Copied timezone abbreviation sets to $ASSET_TZSETS_DST" + else + echo "Warning: timezone abbreviation sets not found at $DIST_DIR/runtime/share/timezonesets" + fi + # tsearch_data directory + if [ -d "$DIST_DIR/runtime/share/tsearch_data" ]; then + local ASSET_TSEARCH_DST="$ASSET_SHARE/tsearch_data" + mkdir -p "$ASSET_TSEARCH_DST" + rsync -a --delete "$DIST_DIR/runtime/share/tsearch_data/" "$ASSET_TSEARCH_DST/" 2>/dev/null || { + rm -rf "$ASSET_TSEARCH_DST"/* + cp -R "$DIST_DIR/runtime/share/tsearch_data/." "$ASSET_TSEARCH_DST/" + } + echo "Copied tsearch_data to $ASSET_TSEARCH_DST" + else + echo "Note: tsearch_data not found at $DIST_DIR/runtime/share/tsearch_data" + fi + # extension directory + if [ -d "$DIST_DIR/runtime/share/extension" ]; then + local ASSET_EXT_DST="$ASSET_SHARE/extension" + mkdir -p "$ASSET_EXT_DST" + rsync -a --delete "$DIST_DIR/runtime/share/extension/" "$ASSET_EXT_DST/" 2>/dev/null || { + rm -rf "$ASSET_EXT_DST"/* + cp -R "$DIST_DIR/runtime/share/extension/." "$ASSET_EXT_DST/" + } + echo "Copied extensions to $ASSET_EXT_DST" + else + echo "Note: extensions directory not found at $DIST_DIR/runtime/share/extension" + fi + + # Copy base cluster snapshot tar into assets if available + local ASSET_ROOT="$RN_DIR/src/main/assets/pglite" + mkdir -p "$ASSET_ROOT" + local CANDIDATES=( + "${PGLITE_DATADIR_TAR:-}" + "$DIST_DIR/PGLiteDataDir.tar" + "$DIST_BASE/PGLiteDataDir.tar" + "$FLAKE_ROOT/packages/pglite/release/PGLiteDataDir.tar" + ) + local FOUND_TAR="" + for f in "${CANDIDATES[@]}"; do + if [ -n "$f" ] && [ -f "$f" ]; then FOUND_TAR="$f"; break; fi + done + if [ -n "$FOUND_TAR" ]; then + cp -f "$FOUND_TAR" "$ASSET_ROOT/PGLiteDataDir.tar" + echo "Copied base cluster snapshot to $ASSET_ROOT/PGLiteDataDir.tar (from $FOUND_TAR)" + else + echo "Note: base cluster snapshot (PGLiteDataDir.tar) not found; skipping copy. Set PGLITE_DATADIR_TAR to override." + fi +} + +# Copy artifacts into the React Native module for iOS +copy_into_rn_ios() { + local RN_DIR="$FLAKE_ROOT/packages/pglite-react-native/ios" + + echo "Installing artifacts into RN iOS module at $RN_DIR" + mkdir -p "$RN_DIR/dist" || true + + # Copy static libraries + cp -f "$DIST_DIR/lib/libpgcore_mobile.a" "$RN_DIR/dist/libpgcore_mobile.a" + cp -f "$DIST_DIR/lib/libpglite_glue_mobile.a" "$RN_DIR/dist/" + + # Copy runtime catalogs to iOS bundle format + local BUNDLE_ROOT="$RN_DIR/RuntimeResources/PGLiteRuntime" + if [ -d "$DIST_DIR/runtime/share" ]; then + mkdir -p "$BUNDLE_ROOT" + rsync -a --delete "$DIST_DIR/runtime/share/" "$BUNDLE_ROOT/share/" 2>/dev/null || { + rm -rf "$BUNDLE_ROOT/share" + mkdir -p "$BUNDLE_ROOT" + cp -R "$DIST_DIR/runtime/share" "$BUNDLE_ROOT/" + } + echo "Copied runtime catalogs to $BUNDLE_ROOT/share" + else + echo "Warning: runtime catalogs not found at $DIST_DIR/runtime/share" + fi +} + +main() { + echo "DEBUG: Starting main function with PLATFORM=$PLATFORM" + + case "$PLATFORM" in + android) + echo "DEBUG: Setting up Android toolchain" + setup_android + ;; + ios) + echo "DEBUG: Setting up iOS toolchain" + setup_ios + ;; + *) + echo "Unknown PLATFORM '$PLATFORM' (expected android|ios)" >&2 + exit 2 + ;; + esac + + echo "DEBUG: Platform setup completed" + echo "\n=== Building PGLite mobile core: PLATFORM=$PLATFORM ABI=$ABI ARCH=$ARCH ===\n" + echo "Using PGSRC=$PGSRC" + + echo "DEBUG: Starting PostgreSQL build" + build_pg + echo "DEBUG: PostgreSQL build completed, starting glue build" + build_glue_and_merge + echo "DEBUG: Glue build completed" + if [ "$PLATFORM" = "android" ]; then + echo "DEBUG: Copying artifacts to Android RN module" + copy_into_rn_android + echo "\nArtifacts at: $DIST_DIR\n - include/*\n - lib/libpgcore_mobile.a\n - lib/libpglite_glue_mobile.a\n - runtime/share/postgresql (if present)\nAlso installed into RN module: packages/pglite-react-native/android/src/main/{jni,assets}\n" + elif [ "$PLATFORM" = "ios" ]; then + echo "DEBUG: Copying artifacts to iOS RN module" + copy_into_rn_ios + echo "\nArtifacts at: $DIST_DIR\n - include/*\n - lib/libpgcore_mobile.a\n - lib/libpglite_glue_mobile.a\n - runtime/share/postgresql (if present)\nAlso installed into RN module: packages/pglite-react-native/ios/{dist,RuntimeResources}\n" + else + echo "\nArtifacts at: $DIST_DIR\n - include/*\n - lib/libpgcore_mobile.a\n - lib/libpglite_glue_mobile.a\n - runtime/share/postgresql (if present)\n" + fi +} + +main "$@" + diff --git a/mobile-build/config.site-android b/mobile-build/config.site-android new file mode 100644 index 0000000000000..f44588a86c582 --- /dev/null +++ b/mobile-build/config.site-android @@ -0,0 +1,39 @@ +# Android API 24+: vectored I/O exists, but some NDK/headers hide declarations. +# Tell PostgreSQL to not use preadv/pwritev directly in headers (HAVE_DECL_* = no) +# so pg_iovec.h takes the pg_pread/pg_pwrite fallback path. +ac_cv_func_preadv=yes +ac_cv_func_pwritev=yes +ac_cv_have_decl_preadv=no +ac_cv_have_decl_pwritev=no + +# Autotools cache for cross-compiling PostgreSQL for Android +# Prevent configure from trying to run target binaries and preseed common checks +ac_cv_func_getaddrinfo=yes +ac_cv_func_getpwuid_r=yes +ac_cv_header_pthread_h=yes +ac_cv_header_locale_h=yes +ac_cv_header_stdbool_h=yes +ac_cv_func_getservbyname_r=no +ac_cv_func_strlcat=no +ac_cv_func_strlcpy=no + +# Android bionic does not provide /nl_langinfo(CODESET) +# Force configure to avoid those code paths so chklocale.c doesn't use nl_langinfo +ac_cv_header_langinfo_h=no +ac_cv_func_nl_langinfo=no + +# Prefer to avoid SysV and POSIX shared memory on Android; force mmap fallback +ac_cv_header_sys_shm_h=no +ac_cv_header_sys_ipc_h=no +ac_cv_func_shmget=no +ac_cv_func_shmctl=no +ac_cv_func_shmat=no +ac_cv_func_shmdt=no +ac_cv_func_shm_open=no +ac_cv_func_shm_unlink=no +# Disable POSIX named semaphores +ac_cv_header_semaphore_h=no +ac_cv_func_sem_open=no + +# Add more as needed based on configure output + diff --git a/mobile-build/config.site-ios b/mobile-build/config.site-ios new file mode 100644 index 0000000000000..be2ccf077189c --- /dev/null +++ b/mobile-build/config.site-ios @@ -0,0 +1,26 @@ +# Autotools cache for cross-compiling PostgreSQL for iOS +# Prevent configure from trying to run target binaries and preseed common checks +ac_cv_func_getaddrinfo=yes +ac_cv_func_getpwuid_r=yes +ac_cv_header_pthread_h=yes +ac_cv_header_locale_h=yes +ac_cv_header_stdbool_h=yes +ac_cv_func_getservbyname_r=no + +# Follow Android pattern: enable preadv/pwritev but disable declarations +# This forces PostgreSQL to use pg_pread/pg_pwrite fallbacks like Android +ac_cv_func_preadv=yes +ac_cv_func_pwritev=yes +ac_cv_have_decl_preadv=no +ac_cv_have_decl_pwritev=no + +# Follow Android pattern: disable strlcat/strlcpy to avoid system conflicts +# Let PostgreSQL provide its own implementations +ac_cv_func_strlcat=no +ac_cv_func_strlcpy=no + +# iOS doesn't allow system() calls for security reasons +ac_cv_func_system=no + +# Add more as needed based on configure output + diff --git a/mobile-build/pgl_backend_stubs.c b/mobile-build/pgl_backend_stubs.c new file mode 100644 index 0000000000000..36aee6ffb974e --- /dev/null +++ b/mobile-build/pgl_backend_stubs.c @@ -0,0 +1,135 @@ +#include +#include +#include +#include +#include +#include +#include +#include "../pglite-wasm/pgl_os.h" + +#include "postgres.h" +#include "miscadmin.h" // BackendType, sig_atomic_t globals +#include "storage/latch.h" // Latch, MyLatch +#include "postmaster/bgworker.h"// BackgroundWorker +#include "libpq/libpq-be.h" // Port definition + +#include "utils/timeout.h" // TimeoutId, TimestampTz +#include "storage/shmem.h" // Size, ShmemInitStruct + +// Provide weak default definitions so real backend objects can override if linked +volatile sig_atomic_t InterruptPending __attribute__((weak)) = 0; +volatile sig_atomic_t QueryCancelPending __attribute__((weak)) = 0; +volatile sig_atomic_t ProcDiePending __attribute__((weak)) = 0; +volatile bool ClientAuthInProgress __attribute__((weak)) = false; +volatile bool notifyInterruptPending __attribute__((weak)) = false; +volatile bool catchupInterruptPending __attribute__((weak)) = 0; +volatile sig_atomic_t CheckClientConnectionPending __attribute__((weak)) = 0; +volatile sig_atomic_t ClientConnectionLost __attribute__((weak)) = 0; + +// Holdoff counters used by ProcessInterrupts (match miscadmin.h types) +volatile uint32 InterruptHoldoffCount __attribute__((weak)) = 0; +volatile uint32 CritSectionCount __attribute__((weak)) = 0; +volatile uint32 QueryCancelHoldoffCount __attribute__((weak)) = 0; + +// Timeout indicators used by ProcessInterrupts (match miscadmin.h types) +volatile sig_atomic_t IdleInTransactionSessionTimeoutPending __attribute__((weak)) = 0; +volatile sig_atomic_t TransactionTimeoutPending __attribute__((weak)) = 0; +volatile sig_atomic_t IdleSessionTimeoutPending __attribute__((weak)) = 0; +volatile sig_atomic_t IdleStatsUpdateTimeoutPending __attribute__((weak)) = 0; + +BackendType MyBackendType __attribute__((weak)) = B_BACKEND; +BackgroundWorker *MyBgworkerEntry __attribute__((weak)) = NULL; + +// Provide a basic latch instance so pointer is valid +static Latch s_MyLatch = {0}; +Latch *MyLatch __attribute__((weak)) = &s_MyLatch; + +// Parallel / barrier / logging related flags +volatile sig_atomic_t ProcSignalBarrierPending __attribute__((weak)) = 0; +bool ParallelMessagePending __attribute__((weak)) = false; +bool ParallelApplyMessagePending __attribute__((weak)) = false; +volatile sig_atomic_t LogMemoryContextPending __attribute__((weak)) = 0; + +// Protocol state variables used by interactive_one.c +volatile bool ignore_till_sync __attribute__((weak)) = false; +volatile bool send_ready_for_query __attribute__((weak)) = true; + +// Additional missing variables for mobile build +#ifndef EOF +#define EOF (-1) +#endif + +// Parser stats GUC +bool log_parser_stats __attribute__((weak)) = false; + +// Max backends (used by shared memory sizing helpers) +int MaxBackends __attribute__((weak)) = 64; + +// Minimal implementations +void __attribute__((weak)) ProcessNotifyInterrupt(bool flush) { (void)flush; } +void __attribute__((weak)) ProcessCatchupInterrupt(void) {} +void __attribute__((weak)) ProcessLogMemoryContextInterrupt(void) {} +void __attribute__((weak)) HandleParallelMessages(void) {} +void __attribute__((weak)) HandleParallelApplyMessages(void) {} + +bool __attribute__((weak)) pq_check_connection(void) { return true; } +bool __attribute__((weak)) IsTransactionOrTransactionBlock(void) { return false; } + +void __attribute__((weak)) enable_timeout_after(TimeoutId id, int delay) { (void)id; (void)delay; } +bool __attribute__((weak)) get_timeout_indicator(TimeoutId id, bool reset) { (void)id; (void)reset; return false; } +TimestampTz __attribute__((weak)) get_timeout_finish_time(TimeoutId id) { (void)id; return 0; } + +// Shared memory helpers stubs (satisfy link-time deps from sinval/procsignal) + +// Timeouts: provide safe no-op implementations used during startup/single-user +void disable_timeout(TimeoutId id, bool keep_indicator) { (void)id; (void)keep_indicator; } +void disable_all_timeouts(bool keep_indicator) { (void)keep_indicator; } +void reschedule_timeouts(void) {} + +void * __attribute__((weak)) ShmemInitStruct(const char *name, Size size, bool *found) +{ + (void)name; (void)size; if (found) *found = true; static int dummy; return &dummy; +} + +// Safe size helpers (normally in libpgcommon) +size_t __attribute__((weak)) add_size(size_t a, size_t b) { return a + b; } +size_t __attribute__((weak)) mul_size(size_t a, size_t b) { return a * b; } + +// Stats reporting stub +void __attribute__((weak)) pgstat_report_stat(bool force) { (void)force; } + +// Graceful exit hook used by pg_shutdown and bootstrap +// Intercept proc_exit during bootstrap by longjmping to pgl_boot_jmp if set +#include +extern volatile sigjmp_buf* pgl_boot_jmp; // defined in pg_main.c +void proc_exit(int code) { + (void)code; + if (pgl_boot_jmp) { + // Jump back to caller to avoid PANIC/exit in bootstrap context + siglongjmp(*(sigjmp_buf*)pgl_boot_jmp, 1); + } + // Outside bootstrap, do nothing — avoid exiting the host process. +} + +// Removed stub pq_init - the real pq_init in pqcomm.c handles PGL_MOBILE properly +// and initializes critical variables like PqRecvLength/PqRecvPointer that error +// recovery code depends on. Socket operations in pq_init are now properly guarded +// to skip on mobile platforms while preserving essential buffer initialization. + + +void pg_proc_exit(int code) { proc_exit(code); } + +// WASM pipe emulation symbols expected by interactive_one.c +FILE * __attribute__((weak)) SOCKET_FILE = NULL; +int __attribute__((weak)) SOCKET_DATA = 0; +void __attribute__((weak)) pq_recvbuf_fill(FILE* fp, int packetlen) { (void)fp; (void)packetlen; } + +// Removed pq_getbyte and pq_getbytes overrides - these should NOT be overridden! +// PostgreSQL's pq_getbyte/pq_getbytes work through PQcommMethods which we've +// already set up in pgl_mobile_comm.c. Overriding them directly breaks bootstrap +// mode where regular file I/O is needed. + +// Logical replication worker queries +bool __attribute__((weak)) IsLogicalWorker(void) { return false; } +bool __attribute__((weak)) IsLogicalLauncher(void) { return false; } + diff --git a/mobile-build/pgl_mobile_comm.c b/mobile-build/pgl_mobile_comm.c new file mode 100644 index 0000000000000..59e241c63c869 --- /dev/null +++ b/mobile-build/pgl_mobile_comm.c @@ -0,0 +1,229 @@ +#include +#include +#include +#include +#include "postgres.h" +#include "miscadmin.h" +#include "lib/stringinfo.h" +#include "libpq/libpq.h" +#include "libpq/pqformat.h" +#include "libpq/pqcomm.h" +#include "utils/guc.h" +#include "../pglite-wasm/pgl_os.h" + +// Mobile CMA shim +#include "sdk_port-mobile.h" + +/* External variables defined in pqcomm.c */ +extern volatile int pgl_mobile_cma_wsize; + +static StringInfoData mobileSendBuf; +static bool mobileCommInited = false; +int original_request_size = 0; /* Saved original request size for offset calculation (non-static for external access) */ + +static void mobile_comm_init_if_needed(void) +{ + if (!mobileCommInited) + { + initStringInfo(&mobileSendBuf); + mobileCommInited = true; + + PGL_LOG_INFO("mobile_comm_init_if_needed: initialized mobileSendBuf"); + } +} + +static void mobile_comm_reset(void) +{ + mobile_comm_init_if_needed(); + resetStringInfo(&mobileSendBuf); + original_request_size = 0; /* Reset for new connection */ + + /* Clear receive buffer state to prevent infinite retry loops */ + extern void pq_reset_buffer_state(void); + pq_reset_buffer_state(); + PGL_LOG_INFO("mobile_comm_reset: reset receive buffer state (PqRecvPointer=0, PqRecvLength=0)"); + + /* Clear the CMA buffer to prevent bootstrap data from leaking into queries */ + char *buf = (char*)(intptr_t)get_buffer_addr(1); + if (buf && get_buffer_size(1) > 0) { + memset(buf, 0, 256); /* Clear first 256 bytes */ + PGL_LOG_INFO("mobile_comm_reset: cleared buffer to prevent bootstrap data leakage"); + } +} + +static int mobile_flush(void) +{ + mobile_comm_init_if_needed(); + + /* Bootstrap/single-user queries should never reach here if REPL mode is working correctly */ + extern volatile int cma_rsize; + if (original_request_size == 0 && cma_rsize == 0) { + PGL_LOG_WARN("mobile_flush: called without request size set, likely bootstrap query in wire mode"); + /* Don't write anything to avoid buffer corruption */ + resetStringInfo(&mobileSendBuf); + return 0; + } + + // MLOGI("mobile_flush: mobileSendBuf.len=%d", mobileSendBuf.len); + if (mobileSendBuf.len > 0) + { + // Copy to CMA out buffer (fd 1) + int cap = get_buffer_size(1); + int n = mobileSendBuf.len < cap ? mobileSendBuf.len : cap; + // MLOGI("mobile_flush: cap=%d n=%d", cap, n); + if (n > 0) + { + char *dst = (char*)(intptr_t)get_buffer_addr(1); + + /* Clear separator region if this is a new query (pgl_mobile_cma_wsize == 0) */ + if (pgl_mobile_cma_wsize == 0 && original_request_size > 0) { + /* Clear the 2-byte separator region that sits between input and output */ + memset(dst + original_request_size, 0, 2); + PGL_LOG_INFO("mobile_flush: cleared separator region at offset %d", original_request_size); + } + + /* Calculate offset using original request size (matches WASM behavior) */ + int off = (original_request_size > 0) ? (original_request_size + 2 + pgl_mobile_cma_wsize) : pgl_mobile_cma_wsize; + if (off < 0) off = 0; + if (off > cap) off = cap; + + /* Validate that we won't write beyond buffer boundaries */ + int copyLen = (off + n <= cap) ? n : (cap - off); + if (copyLen > 0) { + memcpy(dst + off, mobileSendBuf.data, (size_t)copyLen); + PGL_LOG_INFO("mobile_flush: copied %d bytes to offset %d (original_req_size=%d, current_wsize=%d)", + copyLen, off, original_request_size, pgl_mobile_cma_wsize); + } + + /* Accumulate response size directly in pgl_mobile_cma_wsize */ + pgl_mobile_cma_wsize += copyLen; + channel = 1; + + PGL_LOG_INFO("flush: updated pgl_mobile_cma_wsize=%d, addr=%p", pgl_mobile_cma_wsize, (void*)&pgl_mobile_cma_wsize); + // MLOGI("flush: published chunk=%d cum=%d at off=%d (reqLen=%d)", copyLen, out_published, off, reqLen); + } + else { + PGL_LOG_INFO("flush: mobileSendBuf.len=%d but cap insufficient (cap=%d, original_req_size=%d)", mobileSendBuf.len, cap, original_request_size); + } + resetStringInfo(&mobileSendBuf); + } + else { + // MLOGI("flush: no pending bytes"); + } + return 0; +} + +static int mobile_flush_if_writable(void) +{ + // In-process, always writable; just delegate + return mobile_flush(); +} + +static bool mobile_is_send_pending(void) +{ + mobile_comm_init_if_needed(); + return mobileSendBuf.len > 0; +} + +static int mobile_putmessage(char msgtype, const char *s, size_t len) +{ + mobile_comm_init_if_needed(); + PGL_LOG_INFO("mobile_putmessage: msgtype='%c' len=%zu", msgtype, len); + + // For DataRow, parse first couple of field lengths to verify payload integrity + if (msgtype == 'D' && s != NULL && len >= 2) { + const unsigned char* p = (const unsigned char*)s; + // fieldCount is int16 big-endian + uint16_t fc = (uint16_t)((p[0] << 8) | p[1]); + PGL_LOG_INFO("mobile_putmessage: DataRow fieldCount=%u", (unsigned)fc); + p += 2; + size_t remaining = len - 2; + for (unsigned i = 0; i < fc && i < 3 && remaining >= 4; i++) { + uint32_t flen = ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | (uint32_t)p[3]; + PGL_LOG_INFO("mobile_putmessage: field[%u] len=%u", i, (unsigned)flen); + p += 4; + if ((int32_t)flen >= 0) { + if (remaining < 4 + flen) { + PGL_LOG_WARN("mobile_putmessage: field[%u] payload would exceed message: flen=%u remainingAfterLen=%zu", i, (unsigned)flen, remaining - 4); + break; + } + // Optional: dump a few bytes of payload + size_t pl = flen < 8 ? flen : 8; + char hex[3*8+1]; + size_t pos = 0; + for (size_t k = 0; k < pl; k++) { pos += snprintf(hex + pos, sizeof(hex) - pos, "%02x ", p[k]); } + if (pl > 0) hex[pos ? pos-1 : 0] = '\0'; + PGL_LOG_INFO("mobile_putmessage: payload[0..%zu)=%s", pl, pl ? hex : ""); + p += flen; + remaining -= (4 + flen); + } else { + remaining -= 4; + } + } + } + + // Format: type + length (int32 network) + payload + uint32 n32 = htonl((uint32)(len + 4)); + appendStringInfoCharMacro(&mobileSendBuf, msgtype); + appendBinaryStringInfo(&mobileSendBuf, (const char *)&n32, 4); + if (len > 0 && s) + appendBinaryStringInfo(&mobileSendBuf, s, (int)len); + /* Log notice/error messages content for debugging */ + if (msgtype == 'N' || msgtype == 'E') { + if (len > 0 && s) { + /* Log first 200 characters of the message */ + char preview[201]; + size_t preview_len = len < 200 ? len : 200; + memcpy(preview, s, preview_len); + preview[preview_len] = '\0'; + + /* Also log the raw bytes for debugging */ + char hex_preview[41]; + size_t hex_len = len < 20 ? len : 20; + for (size_t i = 0; i < hex_len; i++) { + snprintf(hex_preview + i*2, 3, "%02x", (unsigned char)s[i]); + } + hex_preview[hex_len*2] = '\0'; + + // MLOGI("putmessage: type=%c len=%zu total=%d content='%s' hex=%s", msgtype, len, mobileSendBuf.len, preview, hex_preview); + } else { + // MLOGI("putmessage: type=%c len=%zu total=%d (no content)", msgtype, len, mobileSendBuf.len); + } + } else if (msgtype == 'T' || msgtype == 'D' || msgtype == 'C' || msgtype == 'Z') { + /* Log important message types for query results */ + // MLOGI("putmessage: type=%c (%s) len=%zu total=%d", msgtype, + // msgtype == 'T' ? "rowDescription" : + // msgtype == 'D' ? "dataRow" : + // msgtype == 'C' ? "commandComplete" : + // msgtype == 'Z' ? "readyForQuery" : "unknown", + // len, mobileSendBuf.len); + } else { + // MLOGI("putmessage: type=%c len=%zu total=%d", msgtype, len, mobileSendBuf.len); + } + return 0; +} + +static void mobile_putmessage_noblock(char msgtype, const char *s, size_t len) +{ + (void)mobile_putmessage(msgtype, s, len); +} + +// Hook to install our methods +void pgl_install_mobile_comm(void) +{ + PGL_LOG_INFO("pgl_install_mobile_comm: Installing mobile comm methods"); + static const PQcommMethods MobileMethods = { + .comm_reset = mobile_comm_reset, + .flush = mobile_flush, + .flush_if_writable = mobile_flush_if_writable, + .is_send_pending = mobile_is_send_pending, + .putmessage = mobile_putmessage, + .putmessage_noblock = mobile_putmessage_noblock + }; + PqCommMethods = &MobileMethods; + PGL_LOG_INFO("pgl_install_mobile_comm: PqCommMethods set to %p", (void*)PqCommMethods); + + /* Ensure CMA buffer is initialized and external variables are set */ + get_buffer_size(0); /* This will trigger ensure_buf() */ +} + diff --git a/mobile-build/pgl_mobile_compat.h b/mobile-build/pgl_mobile_compat.h new file mode 100644 index 0000000000000..bfa937b77ba6f --- /dev/null +++ b/mobile-build/pgl_mobile_compat.h @@ -0,0 +1,68 @@ +#pragma once + +// Minimal compatibility header to build pglite glue (interactive_one.c) +// outside of the wasm toolchains, using Android NDK. We: +// - provide standard types and headers +// - define CMA_MB / CMA_FD defaults if not provided by caller +// - forward declare a few PG types used by pointer only +// - include Postgres server headers for struct/function prototypes + +#include +#include +#include +#include +#include +#include + +#include + + +// Forward declarations used by interactive_one.c + +// Ensure shm_* feature macros from pg_config.h don't pull in shm_open on Android glue +#ifdef HAVE_SHM_OPEN +#undef HAVE_SHM_OPEN +#endif +#ifdef HAVE_SHM_UNLINK +#undef HAVE_SHM_UNLINK +#endif + +// Forward declare proc_exit used in backend C units included by pg_main.c +#ifndef HAVE_PROC_EXIT_DECL +#define HAVE_PROC_EXIT_DECL 1 +extern void proc_exit(int code); +#endif + +#ifndef HAVE_PORT_FWD +#define HAVE_PORT_FWD 1 +typedef struct Port Port; +/* Do NOT typedef ClientSocket here; libpq-be.h defines it as a struct. */ +#endif + +// Pull in PostgreSQL server headers (installed by `make -C src/include install`) +// so we get definitions for StringInfoData, ereport, etc. +#include "postgres.h" +#include "lib/stringinfo.h" +#include "libpq/libpq.h" +#include "libpq/pqformat.h" +#include "tcop/dest.h" +#include "tcop/tcopprot.h" +#include "miscadmin.h" +#include "utils/timeout.h" + +// WASM glue uses PDEBUG for tracing; on mobile we compile it away + +// CMA defaults: let pg_main.c define them; do not override here. + +#ifndef PDEBUG +#define PDEBUG(...) do {} while(0) +#endif + + +// Android API 24+: preadv/pwritev provided by bionic; no fallbacks necessary. + + + +/* Note: On native builds, WASM export_name attributes are ignored. + Provide any symbol name shims in a separate TU (pgl_mobile_shims.c) + rather than using GCC alias attributes here. */ diff --git a/mobile-build/pgl_mobile_shims.c b/mobile-build/pgl_mobile_shims.c new file mode 100644 index 0000000000000..5bc85dafe4d5a --- /dev/null +++ b/mobile-build/pgl_mobile_shims.c @@ -0,0 +1,87 @@ +#include "pgl_mobile_compat.h" +#include +#include +#include +#include +#include "../pglite-wasm/pgl_os.h" + +// Provide a stable mobile symbol name expected by the RN bridge. +// Map pgl_shutdown() to the implementation in pgl_mains.c (pg_shutdown). +extern void pg_shutdown(void); +void pgl_shutdown(void) { pg_shutdown(); } + +static int file_exists(const char* p) { + struct stat st; return (p && stat(p, &st) == 0 && S_ISREG(st.st_mode)); +} +static int dir_exists(const char* p) { + struct stat st; return (p && stat(p, &st) == 0 && S_ISDIR(st.st_mode)); +} + +// Stub find_other_exec to avoid probing the filesystem for real binaries. +// We always "find" postgres next to PREFIX/bin/postgres. +int find_other_exec(const char *argv0, const char *target, const char *versionstr, char *retpath) { + (void)argv0; (void)versionstr; + const char* prefix = getenv("PREFIX"); + if (!prefix || !*prefix) prefix = getenv("ANDROID_DATA_DIR"); + if (!prefix || !*prefix) prefix = "/data/local/tmp/pglite"; + // Compose PREFIX/bin/ + char buf[1024]; + snprintf(buf, sizeof(buf), "%s/bin/%s", prefix, target ? target : "postgres"); + strcpy(retpath, buf); + PGL_LOG_INFO("find_other_exec: argv0=%s target=%s ret=%s prefix=%s", argv0?argv0:"", target?target:"", retpath, prefix); + return 0; // success +} + + +// Pretend we can always locate our own executable; set to PREFIX/bin/postgres +int find_my_exec(const char *argv0, char *retpath) { + (void)argv0; + const char* prefix = getenv("PREFIX"); + if (!prefix || !*prefix) prefix = getenv("ANDROID_DATA_DIR"); + if (!prefix || !*prefix) prefix = "/data/local/tmp/pglite"; + snprintf(retpath, 1024, "%s/bin/postgres", prefix); + PGL_LOG_INFO("find_my_exec: argv0=%s ret=%s prefix=%s", argv0?argv0:"", retpath, prefix); + return 0; // success +} + +// Make initdb use our packaged catalogs instead of deriving from executable path. +// Prefer PGSYSCONFDIR if set; otherwise derive from platform runtime directory. +void get_share_path(const char *my_exec_path, char *ret_path) { + (void)my_exec_path; + const char* conf = getenv("PGSYSCONFDIR"); +#ifdef __APPLE__ + const char* runtime = getenv("IOS_RUNTIME_DIR"); +#else + const char* runtime = getenv("ANDROID_RUNTIME_DIR"); +#endif + + // Candidates in order: PGSYSCONFDIR, runtime/share/postgresql, runtime/postgresql + char cand1[1024] = {0}, cand2[1024] = {0}, cand3[1024] = {0}; + if (conf && *conf) snprintf(cand1, sizeof(cand1), "%s", conf); + if (runtime && *runtime) { + snprintf(cand2, sizeof(cand2), "%s/share/postgresql", runtime); + snprintf(cand3, sizeof(cand3), "%s/postgresql", runtime); + } + // We need the directory that directly contains postgres.bki + char bki1[1200] = {0}, bki2[1200] = {0}, bki3[1200] = {0}; + if (*cand1) { snprintf(bki1, sizeof(bki1), "%s/postgres.bki", cand1); } + if (*cand2) { snprintf(bki2, sizeof(bki2), "%s/postgres.bki", cand2); } + if (*cand3) { snprintf(bki3, sizeof(bki3), "%s/postgres.bki", cand3); } + + PGL_LOG_INFO("get_share_path: conf=%s runtime=%s cand1=%s cand2=%s cand3=%s exists(bki1)=%d exists(bki2)=%d exists(bki3)=%d", + conf?conf:"", runtime?runtime:"", cand1, cand2, cand3, file_exists(bki1), file_exists(bki2), file_exists(bki3)); + + if (*cand1 && file_exists(bki1)) { strcpy(ret_path, cand1); goto done; } + if (*cand2 && file_exists(bki2)) { strcpy(ret_path, cand2); goto done; } + if (*cand3 && file_exists(bki3)) { strcpy(ret_path, cand3); goto done; } + // Fallback: use conf if set, else runtime/share/postgresql even if empty + if (*cand1) { strcpy(ret_path, cand1); goto done; } + if (*cand2) { strcpy(ret_path, cand2); goto done; } + if (*cand3) { strcpy(ret_path, cand3); goto done; } + // Last resort + strcpy(ret_path, "/data/local/tmp/pglite/share/postgresql"); + +done: + PGL_LOG_INFO("get_share_path: chosen=%s", ret_path); +} + diff --git a/mobile-build/sdk_port-mobile.c b/mobile-build/sdk_port-mobile.c new file mode 100644 index 0000000000000..10680b86ac1ec --- /dev/null +++ b/mobile-build/sdk_port-mobile.c @@ -0,0 +1,89 @@ +#include "sdk_port-mobile.h" +#include +#include +#include "../pglite-wasm/pgl_os.h" + +/* When included in PostgreSQL build, these variables are defined in postgres.c */ +#ifdef POSTGRES_H +extern volatile int cma_rsize; /* defined in postgres.c */ +#endif + +#ifndef POSTGRES_H +/* Standalone mobile build - define variables here */ +volatile int cma_rsize = 0; +#endif + +/* External variables defined in pqcomm.c, set by mobile SDK */ +extern volatile int pgl_mobile_cma_wsize; +extern int original_request_size; /* Defined in pgl_mobile_comm.c */ + +/* Mobile: Single definition point for these globals (used by interactive_one.c) */ +volatile int channel = 0; +volatile bool is_wire = false; /* Default to REPL mode for bootstrap */ +volatile bool is_repl = true; /* Start in REPL mode */ + +// Single channel buffer for now +#ifndef CMA_MB +#define CMA_MB 12 +#endif +#ifndef CMA_FD +#define CMA_FD 1 +#endif + +static uint8_t* g_buf = NULL; +static int g_cap = 0; + +/* External variables defined in pqcomm.c, set by mobile SDK */ +extern void* pgl_mobile_cma_buffer_addr; +extern int pgl_mobile_cma_buffer_size; + +static void ensure_buf() { + if (!g_buf) { + g_cap = (CMA_MB * 1024 * 1024) / CMA_FD; + void* p = NULL; + if (posix_memalign(&p, 16, (size_t)g_cap + 2) != 0) { + p = NULL; + } + g_buf = (uint8_t*)p; + if (g_buf) { + memset(g_buf, 0, (size_t)g_cap + 2); + /* Set external variables for PostgreSQL to access this buffer */ + pgl_mobile_cma_buffer_addr = g_buf + 1; /* Match WASM offset */ + pgl_mobile_cma_buffer_size = g_cap; + PGL_LOG_INFO("ensure_buf: set pgl_mobile_cma_buffer_addr=%p, size=%d, g_buf=%p", + pgl_mobile_cma_buffer_addr, pgl_mobile_cma_buffer_size, (void*)g_buf); + } + } +} + +int get_buffer_size(int fd) { + (void)fd; + ensure_buf(); + return g_cap; +} + +intptr_t get_buffer_addr(int fd) { + (void)fd; + ensure_buf(); + // Return native pointer to (buf + 1) to match WASM IO semantics + return (intptr_t)(g_buf + 1); +} + +int interactive_read(void) { + PGL_LOG_INFO("interactive_read: pgl_mobile_cma_wsize=%d, addr=%p", pgl_mobile_cma_wsize, (void*)&pgl_mobile_cma_wsize); + return pgl_mobile_cma_wsize; +} + +void use_wire(int state) { + is_wire = (state > 0); + extern volatile bool is_repl; + is_repl = !is_wire; + + /* When switching to REPL mode, clear request size to avoid confusion */ + if (!is_wire) { + original_request_size = 0; + pgl_mobile_cma_wsize = 0; + PGL_LOG_INFO("use_wire: switched to REPL mode, cleared request sizes"); + } +} + diff --git a/mobile-build/sdk_port-mobile.h b/mobile-build/sdk_port-mobile.h new file mode 100644 index 0000000000000..d7e15714e8c8f --- /dev/null +++ b/mobile-build/sdk_port-mobile.h @@ -0,0 +1,26 @@ +#pragma once +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// Simple mobile CMA-like buffer semantics used by interactive_one.c +int get_buffer_size(int fd); // capacity for channel fd +intptr_t get_buffer_addr(int fd); // native pointer to buffer base + 1 for IO[] +// interactive_write is defined in interactive_one.c for both WASM and mobile +int interactive_read(void); // return cma_wsize +void use_wire(int state); // >0 wire mode, <=0 repl + +// Expose variables similar to wasm build +extern volatile int pgl_mobile_cma_wsize; // External variable defined in pqcomm.c +extern volatile int cma_rsize; +extern volatile int channel; +extern volatile bool is_wire; +extern volatile bool is_repl; + +#ifdef __cplusplus +} +#endif + diff --git a/mobile-build/wasm_common_mobile.h b/mobile-build/wasm_common_mobile.h new file mode 100644 index 0000000000000..105f7ffa4ab21 --- /dev/null +++ b/mobile-build/wasm_common_mobile.h @@ -0,0 +1,42 @@ +#pragma once +// Provide macros compatible with wasm_common.h for file-mode paths on mobile +#ifndef PGS_ILOCK +#define PGS_ILOCK "/tmp/pglite/base/.s.PGSQL.5432.lock.in" +#endif +#ifndef PGS_IN +#define PGS_IN "/tmp/pglite/base/.s.PGSQL.5432.in" +#endif +#ifndef PGS_OLOCK +#define PGS_OLOCK "/tmp/pglite/base/.s.PGSQL.5432.lock.out" +#endif +#ifndef PGS_OUT +#define PGS_OUT "/tmp/pglite/base/.s.PGSQL.5432.out" +#endif + + +// Defaults mirroring wasm environment, used by pg_main.c and interactive_one.c +#ifndef WASM_PREFIX +#define WASM_PREFIX "/tmp/pglite" +#endif +#ifndef WASM_USERNAME +#define WASM_USERNAME "postgres" +#endif +#ifndef WASM_PGOPTS +#define WASM_PGOPTS "" +#endif +#ifndef CMA_MB +#define CMA_MB 12 +#endif + + +// Mobile builds are not WASM; do not inject __wasi__/__EMSCRIPTEN__ here. +// Keep only path/CMA constants in this header. + +// Emscripten-only macros: define as no-ops in case code references them +#ifndef EM_ASM +#define EM_ASM(...) ((void)0) +#endif +#ifndef EMSCRIPTEN_KEEPALIVE +#define EMSCRIPTEN_KEEPALIVE +#endif + diff --git a/package.json b/package.json new file mode 100644 index 0000000000000..0967ef424bce6 --- /dev/null +++ b/package.json @@ -0,0 +1 @@ +{} diff --git a/pglite-wasm/interactive_one.c b/pglite-wasm/interactive_one.c index fc3590961d5d6..9ca0da4d560e4 100644 --- a/pglite-wasm/interactive_one.c +++ b/pglite-wasm/interactive_one.c @@ -1,5 +1,15 @@ #include // access, unlink +#include +#include +#include +#include + +#include // fstat +#include #define PGL_LOOP +// Unified logging - PGL_LOG_INFO replaced with PGL_LOG_INFO +#include "pgl_os.h" // Provides unified logging macros (PGL_LOG_INFO, etc.) + #if defined(__wasi__) // volatile sigjmp_buf void*; #else @@ -13,10 +23,26 @@ volatile int canary_ex = 0; volatile int channel = 0; /* TODO : prevent multiple write and write while reading ? */ +#ifdef PGL_MOBILE +#include "sdk_port-mobile.h" +#include "pgl_os.h" +static bool mobile_auth_started = false; + +#define MOBILE_LOG_CALL(func_name) PGL_LOG_INFO("CALL: %s() at line %d", func_name, __LINE__) +#endif + +#ifndef PGL_MOBILE volatile int cma_wsize = 0; volatile int cma_rsize = 0; // also defined in postgres.c for pqcomm +#else +/* On mobile, these are external variables defined in pqcomm.c */ +extern volatile int pgl_mobile_cma_wsize; +extern volatile int cma_rsize; +#define cma_wsize pgl_mobile_cma_wsize +#endif volatile bool sockfiles = false; // also defined in postgres.c for pqcomm +#if !defined(PGL_MOBILE) __attribute__((export_name("get_buffer_size"))) int get_buffer_size(int fd) { @@ -29,6 +55,7 @@ int get_buffer_addr(int fd) { return 1 + ( get_buffer_size(fd) *fd); } +#endif __attribute__((export_name("get_channel"))) int @@ -172,12 +199,41 @@ ClientSocket dummy_sock; static void io_init(bool in_auth, bool out_auth) { ClientAuthInProgress = in_auth; #ifdef PG16 + pq_init(); /* initialize libpq to talk to client */ MyProcPort = (Port *) calloc(1, sizeof(Port)); #else +#ifdef PGL_MOBILE + if (is_wire && MyProcPort) { + PGL_LOG_INFO("PG17: CMA mode, no socket needed"); + MyProcPort->sock = -1; // No real socket in CMA mode + } else { + PGL_LOG_INFO("PG17: skipped assign sock, MyProcPort=%p CMA mode is_wire=%d", + (void*)MyProcPort, (int)is_wire); + } +#endif + +#ifdef PGL_MOBILE + /* Socketpair will be created in per-request setup */ + dummy_sock.sock = -1; +#endif MyProcPort = pq_init(&dummy_sock); + /* On mobile, pq_init may be a no-op; allocate Port if still NULL */ + if (!MyProcPort) { + MyProcPort = (Port *) calloc(1, sizeof(Port)); + /* If we created a socketpair, attach it */ +#ifdef PGL_MOBILE + if (is_wire) { + MyProcPort->sock = -1; // CMA mode, no real socket + } +#endif + } #endif whereToSendOutput = DestRemote; /* now safe to ereport to client */ +#ifdef PGL_MOBILE + PGL_LOG_INFO("io_init: MyProcPort=%p sock(pre)=%d is_wire=%d", (void*)MyProcPort, MyProcPort?MyProcPort->sock:-1, (int)is_wire); +#endif + PGL_LOG_INFO("io_init(univ): port=%p sock=%d", (void*)MyProcPort, MyProcPort?MyProcPort->sock:-1); if (!MyProcPort) { PDEBUG("# 155: io_init --------- NO CLIENT (oom) ---------"); @@ -189,11 +245,27 @@ static void io_init(bool in_auth, bool out_auth) { ClientAuthInProgress = out_auth; SOCKET_FILE = NULL; SOCKET_DATA = 0; +#ifdef PGL_MOBILE + if (is_wire) { + /* CMA mode: data is already in buffer, no socket writing needed */ + if (cma_rsize > 0) { + PGL_LOG_INFO("io_init: CMA buffer has %d bytes ready", cma_rsize); + } + } +#endif PDEBUG("\n\n\n# 165: io_init --------- Ready for CLIENT ---------"); } +#ifdef PGL_MOBILE +/* Mobile: these are defined in sdk_port-mobile.c */ +extern volatile bool is_wire; +extern volatile bool is_repl; +#else +/* WASM: define here */ volatile bool is_wire = true; +extern volatile bool is_repl; /* Defined in pg_main.c for WASM */ +#endif extern char * cma_port; extern void pq_startmsgread(void); @@ -201,7 +273,14 @@ __attribute__((export_name("interactive_write"))) void interactive_write(int size) { cma_rsize = size; +#ifndef PGL_MOBILE cma_wsize = 0; +#else + pgl_mobile_cma_wsize = 0; + extern int original_request_size; + original_request_size = size; /* Save for mobile_flush offset calculation */ + PGL_LOG_INFO("interactive_write: saved original_request_size=%d", original_request_size); +#endif } __attribute__((export_name("use_wire"))) @@ -291,8 +370,15 @@ void discard_input(){ void startup_auth() { + PGL_LOG_INFO("startup_auth: enter, port=%p sock=%d", (void*)MyProcPort, MyProcPort?MyProcPort->sock:-1); /* code is in handshake/auth domain so read whole msg now */ send_ready_for_query = false; +#ifdef PGL_MOBILE + /* CMA mode: data is already in buffer, no socket writing needed */ + if (cma_rsize > 0) { + PGL_LOG_INFO("startup_auth: CMA buffer has %d bytes ready", cma_rsize); + } +#endif if (ProcessStartupPacket(MyProcPort, true, true) != STATUS_OK) { PDEBUG("# 271: ProcessStartupPacket !OK"); @@ -369,14 +455,24 @@ extern void pg_startcma(); __attribute__((export_name("interactive_one"))) void interactive_one() { + PGL_LOG_INFO("interactive_one: ENTRY - function called!"); + PGL_LOG_INFO("interactive_one: cma_rsize=%d cma_wsize=%d", cma_rsize, cma_wsize); + PGL_LOG_INFO("interactive_one: is_wire=%d MyProcPort=%p", is_wire, (void*)MyProcPort); + int peek = -1; /* preview of firstchar with no pos change */ int firstchar = 0; /* character read from getc() */ bool pipelining = true; StringInfoData input_message; StringInfoData *inBuf; + +#ifdef PGL_MOBILE + PGL_LOG_INFO("interactive_one: ENTRY - backend is running and ready to process messages"); + PGL_LOG_INFO("interactive_one: MessageContext=%p", (void*)MessageContext); +#endif FILE *stream ; FILE *fp = NULL; int packetlen; + bool repl_from_file = false; /* mobile: track REPL data source */ bool had_notification = notifyInterruptPending; bool notified = false; @@ -384,11 +480,69 @@ interactive_one() { if (cma_rsize<0) goto resume_on_error; + + if (!MyProcPort) { PDEBUG("# 353: client created"); + PGL_LOG_INFO("interactive_one: calling io_init, port=%p", (void*)MyProcPort); io_init(is_wire, false); + PGL_LOG_INFO("interactive_one: after io_init, port=%p sock=%d", (void*)MyProcPort, MyProcPort?MyProcPort->sock:-1); } +#ifdef PGL_MOBILE + /* Mobile: Use CMA buffer directly like WASM, no sockets needed */ + PGL_LOG_INFO("interactive_one: Mobile section, is_wire=%d", is_wire); + if (is_wire) { + /* Ensure PostgreSQL has a valid port structure but no real socket */ + if (!MyProcPort) { + MyProcPort = (Port *) calloc(1, sizeof(Port)); + if (MyProcPort) { + MyProcPort->sock = -1; // No real socket, use CMA buffer directly + PGL_LOG_INFO("created MyProcPort with no socket (CMA mode)"); + } + } + /* Mobile: Write CMA buffer to temporary file for PostgreSQL to read */ + if (cma_rsize > 0) { + char *src = (char*)(intptr_t)get_buffer_addr(0); + unsigned char first_byte = (unsigned char)src[0]; + PGL_LOG_INFO("CMA buffer ready: %d bytes, first_byte=0x%02x ('%c')", + cma_rsize, first_byte, first_byte >= 32 && first_byte < 127 ? first_byte : '?'); + + /* Debug: log buffer content */ + PGL_LOG_INFO("CMA buffer content (first 20 bytes):"); + for (int i = 0; i < (cma_rsize < 20 ? cma_rsize : 20); i++) { + PGL_LOG_INFO(" [%d] = 0x%02x ('%c')", i, (unsigned char)src[i], + src[i] >= 32 && src[i] < 127 ? src[i] : '?'); + } + + /* Reset CMA read offset for new request */ + // No need to reset - PQcommMethods handles buffer management + + /* Ensure mobile comm is installed before any auth messages are sent */ + #ifdef PGL_MOBILE + extern void pgl_install_mobile_comm(void); + whereToSendOutput = DestRemote; /* now safe to ereport to client */ + pgl_install_mobile_comm(); + #endif + + /* Kick off startup/auth once after first inbound data */ + if (!mobile_auth_started && !ClientAuthInProgress) { + mobile_auth_started = true; + PGL_LOG_INFO("CMA setup: invoking startup_auth"); + startup_auth(); + /* Mobile: flush immediately after auth to publish AuthenticationOk/ParameterStatus */ + pq_flush(); + extern volatile int cma_wsize; + PGL_LOG_INFO("after startup_auth: cma_wsize=%d", cma_wsize); + /* Mark startup message as consumed to prevent SocketBackend from re-reading it */ + PGL_LOG_INFO("Mobile: startup_auth complete, marking input buffer as consumed (cma_rsize %d -> 0)", cma_rsize); + cma_rsize = 0; + } + } + // PGL_LOG_INFO("wire setup: port=%p sock=%d wrote=%zd first_byte=0x%02x", (void*)MyProcPort, MyProcPort?MyProcPort->sock:-1, wrote, first_byte); + } +#endif + #if PGDEBUG if (notifyInterruptPending) PDEBUG("# 371: has notification !"); @@ -402,19 +556,33 @@ if (cma_rsize<0) } if (!cma_rsize) { - // no cma : reading from file. writing to file. + // no CMA input +#ifndef PGL_MOBILE if (!SOCKET_FILE) { - SOCKET_FILE = fopen(PGS_OLOCK, "w") ; - MyProcPort->sock = fileno(SOCKET_FILE); + SOCKET_FILE = fopen(PGS_OLOCK, "w"); + if (SOCKET_FILE && MyProcPort) MyProcPort->sock = fileno(SOCKET_FILE); + else if (MyProcPort) MyProcPort->sock = -1; } +#else + /* Mobile: CMA-only; do not open socket files */ +#endif } else { - // prepare file reply queue, just in case of cma overflow - // if unused the file will be kept open till next query. +#ifndef PGL_MOBILE + // prepare file reply queue, just in case of overflow if (!SOCKET_FILE) { - SOCKET_FILE = fopen(PGS_OLOCK, "w") ; + SOCKET_FILE = fopen(PGS_OLOCK, "w"); } +#else + /* Mobile: CMA-only; no socket file reply queue */ +#endif } + /* Defensive: ensure MessageContext exists (mobile may re-enter without setup) */ + if (MessageContext == NULL) { + MessageContext = AllocSetContextCreate(TopMemoryContext, + "MessageContext", + ALLOCSET_DEFAULT_SIZES); + } MemoryContextSwitchTo(MessageContext); MemoryContextResetAndDeleteChildren(MessageContext); @@ -442,11 +610,24 @@ if (cma_rsize<0) // postgres.c 4627 DoingCommandRead = true; +#if defined(__ANDROID__) || defined(__APPLE__) + /* On mobile, avoid dereferencing address 1; use empty immutable buffer */ + #undef IO + #define IO ((char *)"") + /* Access CMA buffer address for peeking the first byte */ + #include "../mobile-build/sdk_port-mobile.h" +#endif + #if defined(EMUL_CMA) // temp fix for -O0 but less efficient than literal #define IO ((char *)(1+(int)cma_port)) #else + #if defined(__ANDROID__) || defined(__APPLE__) + // Mobile: do not reference CMA via IO; IO is unused on mobile. PQcomm reads CMA directly. + #define IO ((char *)"") + #else #define IO ((char *)(1)) + #endif #endif /* @@ -457,9 +638,28 @@ if (cma_rsize<0) * TODO: allow to redirect stdout for fully external repl. */ +#ifdef PGL_MOBILE + /* On mobile, peek directly from CMA input buffer to set firstchar correctly */ + peek = ((const unsigned char*)(intptr_t)get_buffer_addr(0))[0]; +#else peek = IO[0]; +#endif packetlen = cma_rsize; + +#if PGDEBUG && !defined(PGL_MOBILE) + /* Debug: log what PostgreSQL reads from IO buffer vs socket */ + if (packetlen > 0) { + PGL_LOG_INFO("PostgreSQL reads from IO[0]: peek=0x%02x ('%c'), packetlen=%d", + (unsigned char)peek, peek >= 32 && peek < 127 ? peek : '?', packetlen); + PGL_LOG_INFO("IO buffer content (first 10 bytes):"); + for (int i = 0; i < (packetlen < 10 ? packetlen : 10); i++) { + PGL_LOG_INFO(" IO[%d] = 0x%02x ('%c')", i, (unsigned char)IO[i], + IO[i] >= 32 && IO[i] < 127 ? IO[i] : '?'); + } + } +#endif + if (packetlen) { sockfiles = false; if (!is_repl) { @@ -472,93 +672,76 @@ if (cma_rsize<0) whereToSendOutput = DestDebug; } } else { +#ifndef PGL_MOBILE fp = fopen(PGS_IN, "r"); -puts("# 475:" PGS_IN "\r\n"); - // read file in socket buffer for SocketBackend to consumme. if (fp) { fseek(fp, 0L, SEEK_END); packetlen = ftell(fp); if (packetlen) { - // set to always true if no REPL. -// is_wire = true; resetStringInfo(inBuf); rewind(fp); /* peek on first char */ peek = getc(fp); rewind(fp); if (is_repl && !is_wire) { - // sql in buffer for (int i=0; i socketfiles -> repl #if PGDEBUG +#ifndef PGL_MOBILE if (packetlen) IO[packetlen]=0; // wire blocks are not zero terminated +#endif printf("\n# 524: fd=%d is_embed=%d is_repl=%d is_wire=%d fd %s,len=%d cma=%d peek=%d [%s]\n", MyProcPort->sock, is_embed, is_repl, is_wire, PGS_OLOCK, packetlen,cma_rsize, peek, IO); #endif - resetStringInfo(inBuf); + if (!repl_from_file) { + resetStringInfo(inBuf); + } +#ifndef PGL_MOBILE // when cma buffer is used to fake stdin, data is not read by socket/wire backend. - if (is_repl) { + if (is_repl && !repl_from_file) { for (int i=0; i 0)", cma_rsize); + cma_rsize = 0; break; } +#ifdef PGL_MOBILE + /* Mobile: flush output but continue processing batch until complete */ + pq_flush(); + if (cma_wsize > 0) { + channel = 1; + // mobile_log("flush(after SocketBackend): published %d bytes to CMA", cma_wsize); + } + /* CMA mode: output is already in CMA buffer via PQcommMethods */ + PGL_LOG_INFO("drain(after SocketBackend): CMA mode, cma_wsize=%d", cma_wsize); +#endif + if (peek==112) { PDEBUG("# 547: password"); startup_pass(true); + /* Mark password message as consumed */ + PGL_LOG_INFO("Mobile: startup_pass complete, marking input buffer as consumed (cma_rsize %d -> 0)", cma_rsize); + cma_rsize = 0; break; } + // PGL_LOG_INFO("interactive_one: before SocketBackend, port=%p sock=%d", (void*)MyProcPort, MyProcPort?MyProcPort->sock:-1); + // PGL_LOG_INFO("interactive_one: cma_rsize=%d peek=%d", cma_rsize, peek); + + /* CMA mode: no socket polling needed */ + + /* Mobile: Reset CMA read offset and use normal PostgreSQL flow */ + // No need to reset - PQcommMethods handles buffer management + PGL_LOG_INFO("Mobile CMA mode: reset read offset, using SocketBackend"); + + /* Let SocketBackend handle message reading, pq_getbyte will read from CMA buffer */ firstchar = SocketBackend(inBuf); + PGL_LOG_INFO("Mobile: SocketBackend returned firstchar=%d ('%c') inBuf->len=%d", + firstchar, firstchar > 0 && firstchar < 127 ? firstchar : '?', inBuf->len); + + /* Debug: log what SocketBackend read */ + if (inBuf->len > 0) { + PGL_LOG_INFO("SocketBackend read (first 20 bytes):"); + for (int i = 0; i < (inBuf->len < 20 ? inBuf->len : 20); i++) { + PGL_LOG_INFO(" inBuf[%d] = 0x%02x ('%c')", i, (unsigned char)inBuf->data[i], + inBuf->data[i] >= 32 && inBuf->data[i] < 127 ? inBuf->data[i] : '?'); + } + } + + /* Don't reset cma_rsize yet - let pipelining check handle remaining messages */ pipelining = pq_buffer_remaining_data()>0; } else { @@ -619,8 +853,22 @@ PDEBUG("# 507: NO DATA:" PGS_IN "\n"); appendStringInfoChar(inBuf, (char) '\0'); firstchar = 'Q'; } +#ifdef PGL_MOBILE + /* --- Mobile CMA mode: data is already in buffer --- */ + PGL_LOG_INFO("Mobile CMA: data ready in buffer, cma_rsize=%d", cma_rsize); + /* --------------------------------------------------------------- */ +#endif + } DoingCommandRead = false; +#ifdef PGL_MOBILE + // Drain server replies from client end into CMA out + if (MyProcPort && MyProcPort->sock > 0) { + // We wrote into sv[1]; recover its fd by duplicating MyProcPort->sock? + // We stored both ends in local sv[], so add a static to retain them across scope + } +#endif + if (!ignore_till_sync) { /* initially, or after error */ @@ -646,6 +894,16 @@ PDEBUG("# 507: NO DATA:" PGS_IN "\n"); } } } +#ifdef PGL_MOBILE + /* Mobile: Ensure all output is flushed before marking batch as consumed */ + if (cma_rsize > 0) { + /* Force a final flush to make sure all accumulated output is visible */ + pq_flush(); + PGL_LOG_INFO("Mobile: Pipelining complete, final flush done, cma_wsize=%d", cma_wsize); + PGL_LOG_INFO("Mobile: Pipelining complete, marking batch as consumed (cma_rsize %d -> 0)", cma_rsize); + cma_rsize = 0; + } +#endif resume_on_error: if (!is_repl) { wire_flush: @@ -662,18 +920,34 @@ PDEBUG("# 507: NO DATA:" PGS_IN "\n"); } else { PDEBUG("# 606: end packet - with no rfq\n"); } +#ifdef PGL_MOBILE + /* Mobile: flush output but let pipelining loop handle completion */ + pq_flush(); + if (cma_wsize > 0) { + channel = 1; + // mobile_log("flush(wire_flush): published %d bytes to CMA", cma_wsize); + } else { + // mobile_log("flush(wire_flush): no pending bytes"); + } +#endif } else { PDEBUG("# 609: end packet (ClientAuthInProgress - no rfq)\n"); } if (SOCKET_DATA>0) { +#ifdef PGL_MOBILE + if (cma_wsize > 0) { + /* Mobile PqComm already produced CMA output. Skip socket fallback. */ + } else +#endif + +#ifndef PGL_MOBILE if (sockfiles) { channel = -1; if (cma_wsize) { puts("ERROR: cma was not flushed before socketfile interface"); } } else { - /* wsize may have increased with previous rfq so assign here */ cma_wsize = SOCKET_DATA; channel = cma_rsize + 2; } @@ -682,33 +956,47 @@ PDEBUG("# 507: NO DATA:" PGS_IN "\n"); fclose(SOCKET_FILE); SOCKET_FILE = NULL; SOCKET_DATA = 0; - +#if PGDEBUG if (cma_wsize) { PDEBUG("# 672: cma and sockfile ???\n"); } - if (sockfiles) { -#if PGDEBUG printf("# 675: client:ready -> read(%d) " PGS_OLOCK "->" PGS_OUT"\n", outb); + } #endif + if (sockfiles) { rename(PGS_OLOCK, PGS_OUT); } - } else { + } +#else + /* Mobile: CMA-only; no socket fallback */ +#endif + { #if PGDEBUG +#ifdef PGL_MOBILE + if (cma_wsize == 0) +#endif + printf("\n# 681: in[%d] out[%d] flushed\n", cma_rsize, cma_wsize); #endif SOCKET_DATA = 0; } } else { +#ifndef PGL_MOBILE cma_wsize = 0; +#else + /* Mobile: do not clear cma_wsize here. If PQcommMethods flushed output, + RN will read it after this function returns. */ +#endif PDEBUG("# 698: no data, send empty ?"); // TODO: dedup 739 +#ifndef PGL_MOBILE if (sockfiles) { - fclose(SOCKET_FILE); - SOCKET_FILE = NULL; + if (SOCKET_FILE) { fclose(SOCKET_FILE); SOCKET_FILE = NULL; } rename(PGS_OLOCK, PGS_OUT); } +#endif } } else { pg_prompt(); @@ -721,8 +1009,7 @@ PDEBUG("# 507: NO DATA:" PGS_IN "\n"); } else { // TODO: dedup 723 if (sockfiles) { - fclose(SOCKET_FILE); - SOCKET_FILE = NULL; + if (SOCKET_FILE) { fclose(SOCKET_FILE); SOCKET_FILE = NULL; } rename(PGS_OLOCK, PGS_OUT); } } @@ -734,13 +1021,21 @@ return_early:; /* always FD CLEANUP */ if (fp) { fclose(fp); +#ifndef PGL_MOBILE unlink(PGS_IN); +#endif } // always free kernel buffer !!! cma_rsize = 0; +#ifndef PGL_MOBILE IO[0] = 0; +#endif + +#ifdef PGL_MOBILE + /* CMA mode: no cleanup needed, just reset buffer */ +#endif #undef IO diff --git a/pglite-wasm/pg_main.c b/pglite-wasm/pg_main.c index 65e5d7ac01d2c..1d3079d69e96c 100644 --- a/pglite-wasm/pg_main.c +++ b/pglite-wasm/pg_main.c @@ -16,12 +16,101 @@ #define IDB_PIPE_SINGLE "/tmp/initdb.single.txt" #include "pgl_os.h" +#ifdef __ANDROID__ +#include +#include +#include +static int pgl_stderr_pipe[2] = {-1, -1}; +static pthread_t pgl_stderr_thread; +static void* pgl_stderr_reader(void* arg) { + (void)arg; + if (pgl_stderr_pipe[0] < 0) { + PGL_LOG_ERROR("[pgl_stderr_reader] Invalid pipe fd, exiting thread"); + return NULL; + } + PGL_LOG_INFO("[pgl_stderr_reader] Thread started, entering read loop"); + char buf[1024]; + for (;;) { + ssize_t n = read(pgl_stderr_pipe[0], buf, sizeof(buf)-1); + if (n > 0) { + buf[n] = '\0'; + PGL_LOG_INFO("%s", buf); + continue; + } + if (n < 0) { + if (errno == EINTR) { + continue; + } + if (errno == EAGAIN || errno == EWOULDBLOCK) { + usleep(100000); // 100ms sleep to reduce spam + continue; + } + PGL_LOG_ERROR("[pgl_stderr_reader] Read error errno=%d, breaking", errno); + break; + } + // n == 0: pipe closed + PGL_LOG_INFO("[pgl_stderr_reader] Pipe closed (EOF), exiting thread"); + break; + } + PGL_LOG_INFO("[pgl_stderr_reader] Thread exiting"); + return NULL; +} +static void pgl_install_android_stderr_redirect(void) { + if (pgl_stderr_pipe[0] >= 0) { + PGL_DEBUG("[pgl_install_android_stderr_redirect] Already installed, skipping"); + return; + } + PGL_LOG_INFO("[pgl_install_android_stderr_redirect] Installing stderr redirect"); + if (pipe(pgl_stderr_pipe) == 0) { + PGL_LOG_INFO("[pgl_install_android_stderr_redirect] Pipe created: read_fd=%d write_fd=%d", pgl_stderr_pipe[0], pgl_stderr_pipe[1]); + + // Make read end non-blocking to prevent hangs + int flags = fcntl(pgl_stderr_pipe[0], F_GETFL); + if (fcntl(pgl_stderr_pipe[0], F_SETFL, flags | O_NONBLOCK) < 0) { + PGL_LOG_ERROR("[pgl_install_android_stderr_redirect] Failed to set non-blocking: errno=%d", errno); + } else { + PGL_LOG_INFO("%s", "[pgl_install_android_stderr_redirect] Set read end to non-blocking"); + } + + // Make stderr unbuffered and redirect + setvbuf(stderr, NULL, _IONBF, 0); + if (dup2(pgl_stderr_pipe[1], STDERR_FILENO) < 0) { + PGL_LOG_ERROR("[pgl_install_android_stderr_redirect] dup2 failed: errno=%d", errno); + } else { + PGL_LOG_INFO("%s", "[pgl_install_android_stderr_redirect] stderr redirected to pipe"); + } + + if (pthread_create(&pgl_stderr_thread, NULL, pgl_stderr_reader, NULL) != 0) { + PGL_LOG_ERROR("[pgl_install_android_stderr_redirect] pthread_create failed: errno=%d", errno); + } else { + PGL_LOG_INFO("%s", "[pgl_install_android_stderr_redirect] Reader thread created"); + } + } else { + PGL_LOG_ERROR("[pgl_install_android_stderr_redirect] pipe() failed: errno=%d", errno); + } +} +#endif + // ----------------------- pglite ---------------------------- #include "postgres.h" +#include "utils/elog.h" + #include "utils/memutils.h" #include "utils/pg_locale.h" #include "tcop/tcopprot.h" +#include "lib/stringinfo.h" + +/* Temporary log hook to surface bootstrap errors into stderr in C (no lambdas) */ +static void pgl_boot_emit_hook(ErrorData* ed) { + if (ed && ed->elevel >= ERROR) { + fprintf(stderr, "[pgl_boot] %s:%d %s: %s\n", + ed->filename ? ed->filename : "?", + ed->lineno, + ed->funcname ? ed->funcname : "?", + ed->message ? ed->message : ""); + } +} #include /* chdir */ #include /* mkdir */ @@ -43,7 +132,13 @@ volatile char *PGUSER; const char *progname; +#ifdef PGL_MOBILE +/* Mobile: defined in sdk_port-mobile.c */ +extern volatile bool is_repl; +#else +/* WASM: define here */ volatile bool is_repl = true; +#endif volatile bool is_node = true; volatile bool is_embed = false; volatile int pgl_idb_status; @@ -62,9 +157,54 @@ volatile int async_restart = 1; #define WASM_PGDATA WASM_PREFIX "/base" #define CMA_FD 1 +#include extern bool IsPostmasterEnvironment; +/* Global hook for intercepting proc_exit during bootstrap */ +volatile sigjmp_buf* pgl_boot_jmp = NULL; #define help(name) +#ifdef __ANDROID__ +#include +#include "utils/elog.h" +static void pgl_android_elog_hook(ErrorData* ed) { + if (!ed) return; + int prio = ANDROID_LOG_INFO; + if (ed->elevel >= ERROR) prio = ANDROID_LOG_ERROR; + else if (ed->elevel >= WARNING) prio = ANDROID_LOG_WARN; + else if (ed->elevel >= DEBUG1) prio = ANDROID_LOG_DEBUG; + switch(prio) { + case ANDROID_LOG_ERROR: + PGL_LOG_ERROR("%s:%d %s: %s", + ed->filename ? ed->filename : "?", + ed->lineno, + ed->funcname ? ed->funcname : "?", + ed->message ? ed->message : ""); + break; + case ANDROID_LOG_WARN: + PGL_LOG_WARN("%s:%d %s: %s", + ed->filename ? ed->filename : "?", + ed->lineno, + ed->funcname ? ed->funcname : "?", + ed->message ? ed->message : ""); + break; + case ANDROID_LOG_DEBUG: + PGL_DEBUG("%s:%d %s: %s", + ed->filename ? ed->filename : "?", + ed->lineno, + ed->funcname ? ed->funcname : "?", + ed->message ? ed->message : ""); + break; + default: + PGL_LOG_INFO("%s:%d %s: %s", + ed->filename ? ed->filename : "?", + ed->lineno, + ed->funcname ? ed->funcname : "?", + ed->message ? ed->message : ""); + break; + } +} +#endif + #define BREAKV(x) { printf("BREAKV : %d\n",__LINE__);return x; } #define BREAK { printf("BREAK : %d\n",__LINE__);return; } @@ -101,9 +241,15 @@ pg_free(void *ptr) { #include "../backend/tcop/postgres.c" + // initdb + start on fd (pipe emulation) +#ifdef __ANDROID__ +static void pgl_install_android_stderr_redirect_wrapper(void) { pgl_install_android_stderr_redirect(); } +static void __attribute__((constructor)) pgl_install_android_stderr_redirect_ctor(void) { pgl_install_android_stderr_redirect_wrapper(); } +#endif + static bool force_echo = false; @@ -117,13 +263,24 @@ static bool force_echo = false; // interactive_one, heart of the async loop. +// On mobile, avoid calling pq_init() which expects a real socket; we operate in-process +// Return the existing MyProcPort so sites that assign from pq_init() remain valid + + +// Install mobile comm methods early when building for mobile +#ifdef PGL_MOBILE +extern void pgl_install_mobile_comm(void); +#endif #include "./interactive_one.c" static void main_pre(int argc, char *argv[]) { - +#ifdef __ANDROID__ + /* Ensure stderr is redirected to logcat early */ + pgl_install_android_stderr_redirect(); +#endif char key[256]; int i = 0; @@ -165,6 +322,7 @@ main_pre(int argc, char *argv[]) { // get default or set default if not set PREFIX = setdefault("PREFIX", WASM_PREFIX); + fprintf(stderr, "[pgl_main] PREFIX=%s PGDATA=%s PGSYSCONFDIR=%s ANDROID_RUNTIME_DIR=%s\n", PREFIX, PGDATA?PGDATA:"", getenv("PGSYSCONFDIR")?getenv("PGSYSCONFDIR"):"", getenv("ANDROID_RUNTIME_DIR")?getenv("ANDROID_RUNTIME_DIR"):""); argv[0] = strcat_alloc(PREFIX, "/bin/postgres"); @@ -285,6 +443,7 @@ main_pre(int argc, char *argv[]) { void main_post() { + PGL_LOG_ERROR("[main_post] *** ENTRY: main_post() function called ***"); PDEBUG("# 280: main_post()"); /* * Fire up essential subsystems: error and memory management @@ -293,12 +452,30 @@ main_post() { * localization of messages may not work right away, and messages won't go * anywhere but stderr until GUC settings get loaded. */ + PGL_LOG_ERROR("[main_post] *** About to call MemoryContextInit() ***"); MemoryContextInit(); + PGL_LOG_ERROR("[main_post] *** MemoryContextInit() completed ***"); /* * Set up locale information */ - set_pglocale_pgservice(g_argv[0], PG_TEXTDOMAIN("postgres")); + /* Guard against NULL g_argv when running as a library (mobile) */ + const char *argv0_local = NULL; + PGL_LOG_ERROR("[main_post] *** About to determine argv0_local, g_argv=%p ***", (void*)g_argv); + if (g_argv && g_argv[0] && g_argv[0][0]) { + argv0_local = g_argv[0]; + PGL_LOG_ERROR("[main_post] *** Using g_argv[0]: %s ***", argv0_local); + } else { + static char __argv0_buf[STROPS_BUF]; + const char *pr = (PREFIX && ((const char*)PREFIX)[0]) ? (const char*)PREFIX : WASM_PREFIX; + strconcat(__argv0_buf, pr, "/bin/postgres"); + argv0_local = __argv0_buf; + fprintf(stderr, "[pgl_main] main_post fallback argv0=%s (g_argv missing)\n", argv0_local); + PGL_LOG_ERROR("[main_post] *** Using fallback argv0: %s ***", argv0_local); + } + PGL_LOG_ERROR("[main_post] *** About to call set_pglocale_pgservice with argv0=%s ***", argv0_local ? argv0_local : "NULL"); + set_pglocale_pgservice(argv0_local, PG_TEXTDOMAIN("postgres")); + PGL_LOG_ERROR("[main_post] *** set_pglocale_pgservice completed ***"); /* * In the postmaster, absorb the environment values for LC_COLLATE and @@ -335,8 +512,31 @@ main_post() { } // main_post + __attribute__ ((export_name("pgl_backend"))) void pgl_backend() { +#ifdef PGL_MOBILE + PGL_LOG_ERROR("%s", "[pgl_backend] *** ENTRY: pgl_backend function called ***"); + PGL_LOG_ERROR("%s", "[pgl_backend] *** This confirms we reached pgl_backend after pgl_initdb ***"); +#endif + fprintf(stderr, "[pgl_backend] *** ENTRY: pgl_backend function called ***\n"); +#ifdef __ANDROID__ + static int pgl_android_log_inited = 0; + if (!pgl_android_log_inited) { + pgl_install_android_stderr_redirect(); + emit_log_hook = pgl_android_elog_hook; /* direct ereport to logcat */ + pgl_android_log_inited = 1; + } +#endif + /* Guard required env/vars even if pgl_initdb didn't set globals as expected */ + const char *pr = (PREFIX && ((const char*)PREFIX)[0]) ? (const char*)PREFIX : WASM_PREFIX; + if (!PGUSER || !((const char*)PGUSER)[0]) PGUSER = (volatile char*) setdefault("PGUSER", WASM_USERNAME); + if (!PGDATA || !((const char*)PGDATA)[0]) { + static char __pgbuf[STROPS_BUF]; + strconcat(__pgbuf, pr, "/base"); + PGDATA = (volatile char*) setdefault("PGDATA", __pgbuf); + } + fprintf(stderr, "[pgl_backend] guards: PREFIX=%s PGDATA=%s PGUSER=%s\n", pr, PGDATA? (const char*)PGDATA : "", PGUSER? (const char*)PGUSER : ""); #if PGDEBUG print_bits(sizeof(pgl_idb_status), &pgl_idb_status); #endif @@ -345,8 +545,48 @@ __attribute__ ((export_name("pgl_backend"))) //abort(); } +#ifdef PGL_MOBILE + /* Initialize critical globals for mobile library mode */ + if (!progname) { + progname = "postgres"; // Safe fallback + } + + /* Ensure critical environment variables are set */ + if (!getenv("PGSYSCONFDIR")) { + setenv("PGSYSCONFDIR", pr, 1); + } + if (!getenv("PGCLIENTENCODING")) setenv("PGCLIENTENCODING", "UTF8", 1); + if (!getenv("LC_CTYPE")) setenv("LC_CTYPE", "en_US.UTF-8", 1); + if (!getenv("TZ")) setenv("TZ", "UTC", 1); + if (!getenv("PGTZ")) setenv("PGTZ", "UTC", 1); + if (!getenv("PGDATABASE")) setenv("PGDATABASE", "template1", 1); + + PGL_LOG_INFO("[pgl_backend] Mobile environment initialization complete"); +#endif + +#ifdef PGL_MOBILE + /* Mobile communication methods will be installed after backend initialization */ + PGL_LOG_ERROR("[pgl_backend] *** Deferring mobile comm installation until backend is ready ***"); + + /* MOBILE: Initialize g_argv equivalent for library mode if not already done */ + /* TEMPORARILY DISABLED - this may be causing invalidation message corruption + if (!g_argv) { + static char* mobile_argv[4]; + static char mobile_argv0[STROPS_BUF]; + const char *pr = (PREFIX && ((const char*)PREFIX)[0]) ? (const char*)PREFIX : WASM_PREFIX; + strconcat(mobile_argv0, pr, "/bin/postgres"); + mobile_argv[0] = mobile_argv0; + mobile_argv[1] = NULL; + g_argv = mobile_argv; + g_argc = 1; + PGL_LOG_INFO("[pgl_backend] Mobile: initialized g_argv[0]=%s", mobile_argv0); + } + */ +#endif + if (async_restart) { // old 487 + PGL_LOG_ERROR("[pgl_backend] *** Taking async_restart=1 path (new DB or mobile) ***"); #if PGDEBUG fprintf(stdout, "\n\n\n\n" @@ -354,21 +594,49 @@ __attribute__ ((export_name("pgl_backend"))) "# 346: FIXME: restarting in single mode after initdb with user '%s' instead of %s\n", PGUSER, getenv("PGUSER")); // main_post(); #endif - setenv("PGUSER", PGUSER, 1); - char *single_argv[] = { - WASM_PREFIX "/bin/postgres", - "--single", - "-d", "1", - "-B", "16", "-S", "512", "-f", "siobtnmh", - "-D", PGDATA, - "-F", "-O", "-j", - WASM_PGOPTS, - "template1", - NULL - }; - int single_argc = sizeof(single_argv) / sizeof(char *) - 1; - optind = 1; - RePostgresSingleUserMain(single_argc, single_argv, PGUSER); + // Optionally skip initdb single-user replay (still start backend later) + const char* skip_single = getenv("PGL_SKIP_SINGLE"); + if (!(skip_single && skip_single[0] == '1')) { + setenv("PGUSER", PGUSER, 1); + // Build single-user argv dynamically to avoid empty args (e.g., empty WASM_PGOPTS) + char *single_argv[24]; + int single_argc = 0; + single_argv[single_argc++] = WASM_PREFIX "/bin/postgres"; + single_argv[single_argc++] = "--single"; + single_argv[single_argc++] = "-d"; single_argv[single_argc++] = "1"; + single_argv[single_argc++] = "-B"; single_argv[single_argc++] = "16"; + single_argv[single_argc++] = "-S"; single_argv[single_argc++] = "512"; + single_argv[single_argc++] = "-f"; single_argv[single_argc++] = "siobtnmh"; + single_argv[single_argc++] = "-D"; single_argv[single_argc++] = (char*)PGDATA; + // Disable startup progress timers to avoid timeout machinery during single-user replay + single_argv[single_argc++] = "-c"; single_argv[single_argc++] = "log_startup_progress_interval=0"; + single_argv[single_argc++] = "-F"; single_argv[single_argc++] = "-O"; single_argv[single_argc++] = "-j"; + if (WASM_PGOPTS[0] != '\0') { + single_argv[single_argc++] = (char*)WASM_PGOPTS; + } + single_argv[single_argc++] = "template1"; + single_argv[single_argc] = NULL; // argc must NOT include NULL terminator + optind = 1; + // Log control file presence pre-single-user + { + char ctrl_path[1024]; + snprintf(ctrl_path, sizeof(ctrl_path), "%s/global/pg_control", PGDATA); + struct stat st; int rc = stat(ctrl_path, &st); + fprintf(stderr, "[pgl_main] pre-single ctrl=%s rc=%d errno=%d size=%lld\n", + ctrl_path, rc, errno, (long long)((rc==0)?st.st_size:0)); + } + RePostgresSingleUserMain(single_argc, single_argv, PGUSER); + // Log control file presence post-single-user + { + char ctrl_path[1024]; + snprintf(ctrl_path, sizeof(ctrl_path), "%s/global/pg_control", PGDATA); + struct stat st; int rc = stat(ctrl_path, &st); + fprintf(stderr, "[pgl_main] post-single ctrl=%s rc=%d errno=%d size=%lld\n", + ctrl_path, rc, errno, (long long)((rc==0)?st.st_size:0)); + } + } else { + fprintf(stderr, "[pgl_main] skipping initdb single-user replay due to PGL_SKIP_SINGLE=1\n"); + } // AsyncPostgresSingleUserMain(single_argc, single_argv, PGUSER, async_restart); PDEBUG("# 365: initdb faking shutdown to complete WAL/OID states in single mode"); @@ -376,34 +644,105 @@ __attribute__ ((export_name("pgl_backend"))) } + PGL_LOG_ERROR("[pgl_backend] *** About to enter main_post() for existing database ***"); + fprintf(stderr, "[pgl_main] entering main_post (before single-user resume) g_argv=%p g_argv0=%s DataDir=%s\n", + (void*)g_argv, + (g_argv && g_argv[0]) ? g_argv[0] : "", + DataDir ? DataDir : ""); + PGL_LOG_ERROR("[pgl_backend] *** Calling main_post() now ***"); main_post(); - - char *single_argv[] = { - g_argv[0], - "--single", - "-d", "1", - "-B", "16", "-S", "512", "-f", "siobtnmh", - "-D", PGDATA, - "-F", "-O", "-j", - WASM_PGOPTS, - getenv("PGDATABASE"), - NULL - }; - int single_argc = sizeof(single_argv) / sizeof(char *) - 1; + PGL_LOG_ERROR("[pgl_backend] *** main_post() returned successfully ***"); + fprintf(stderr, "[pgl_main] returned from main_post\n"); + + // Build resuming single-user argv dynamically to avoid empty args + char *single_argv[24]; + char __argv0_buf[STROPS_BUF]; + const char* single_argv0; + if (g_argv && g_argv[0] && g_argv[0][0]) { + single_argv0 = g_argv[0]; + } else { + const char* pr = (PREFIX && ((const char*)PREFIX)[0]) ? (const char*)PREFIX : WASM_PREFIX; + strconcat(__argv0_buf, pr, "/bin/postgres"); + single_argv0 = __argv0_buf; + fprintf(stderr, "[pgl_backend] Using fallback argv0: %s\n", single_argv0); + } + int single_argc = 0; + single_argv[single_argc++] = (char*)single_argv0; + single_argv[single_argc++] = "--single"; + single_argv[single_argc++] = "-d"; single_argv[single_argc++] = "1"; + single_argv[single_argc++] = "-B"; single_argv[single_argc++] = "16"; + single_argv[single_argc++] = "-S"; single_argv[single_argc++] = "512"; + single_argv[single_argc++] = "-f"; single_argv[single_argc++] = "siobtnmh"; + single_argv[single_argc++] = "-D"; single_argv[single_argc++] = (char*)PGDATA; + single_argv[single_argc++] = "-F"; single_argv[single_argc++] = "-O"; single_argv[single_argc++] = "-j"; + // Disable startup progress timers to avoid timeout machinery during existing db resume + single_argv[single_argc++] = "-c"; single_argv[single_argc++] = "log_startup_progress_interval=0"; + if (WASM_PGOPTS[0] != '\0') { + single_argv[single_argc++] = (char*)WASM_PGOPTS; + } + const char *db_for_resume = getenv("PGDATABASE"); + if (!db_for_resume || !db_for_resume[0]) db_for_resume = "template1"; + single_argv[single_argc++] = (char*)db_for_resume; + single_argv[single_argc] = NULL; + int single_argc_save = single_argc; optind = 1; #if PGDEBUG fprintf(stdout, "\n\n\n# 387: resuming db with user '%s' instead of %s\n", PGUSER, getenv("PGUSER")); #endif setenv("PGUSER", PGUSER, 1); +#ifdef PGL_MOBILE + /* Single-user mode runs in REPL mode by default (is_wire=false, is_repl=true) */ + PGL_LOG_INFO("[pgl_backend] Using default REPL mode for AsyncPostgresSingleUserMain"); +#endif - - AsyncPostgresSingleUserMain(single_argc, single_argv, PGUSER, async_restart); + AsyncPostgresSingleUserMain(single_argc_save, single_argv, PGUSER, async_restart); backend_started:; + PGL_LOG_INFO("[pgl_backend] Reached backend_started label"); IsPostmasterEnvironment = true; - if (TransamVariables->nextOid < ((Oid) FirstNormalObjectId)) { + +#ifdef PGL_MOBILE + PGL_LOG_INFO("[pgl_mobile] Starting mobile-specific backend state initialization"); + + /* Mobile: Initialize critical backend state that must persist across interactive_one() calls */ + /* These are normally set up in PostgresMain() but mobile needs them for wire protocol */ + extern MemoryContext row_description_context; + extern StringInfoData row_description_buf; + + PGL_LOG_INFO("[pgl_mobile] row_description_context = %p", (void*)row_description_context); + + if (row_description_context == NULL) { + PGL_LOG_INFO("[pgl_mobile] Initializing row_description_context for wire protocol"); + row_description_context = AllocSetContextCreate(TopMemoryContext, + "RowDescriptionContext", + ALLOCSET_DEFAULT_SIZES); + MemoryContext oldcontext = MemoryContextSwitchTo(row_description_context); + initStringInfo(&row_description_buf); + MemoryContextSwitchTo(oldcontext); + PGL_LOG_INFO("[pgl_mobile] row_description_context created at %p", (void*)row_description_context); + } + + /* Ensure MessageContext exists for protocol message handling */ + PGL_LOG_INFO("[pgl_mobile] MessageContext = %p", (void*)MessageContext); + + if (MessageContext == NULL) { + PGL_LOG_INFO("[pgl_mobile] Initializing MessageContext for protocol handling"); + MessageContext = AllocSetContextCreate(TopMemoryContext, + "MessageContext", + ALLOCSET_DEFAULT_SIZES); + PGL_LOG_INFO("[pgl_mobile] MessageContext created at %p", (void*)MessageContext); + } + /* Initialize mobile communication methods before any backend processing */ + PGL_LOG_INFO("[pgl_mobile] Installing mobile communication methods"); + pgl_install_mobile_comm(); + PGL_LOG_INFO("[pgl_mobile] Mobile communication methods installed successfully"); + + PGL_LOG_INFO("[pgl_mobile] Mobile backend state initialization complete"); +#endif + + if (TransamVariables && TransamVariables->nextOid < ((Oid) FirstNormalObjectId)) { /* IsPostmasterEnvironment is now true these will be executed when required in varsup.c/GetNewObjectId TransamVariables->nextOid = FirstNormalObjectId; @@ -413,6 +752,9 @@ __attribute__ ((export_name("pgl_backend"))) puts("# 403: initdb done, oid base too low but OID range will be set because IsPostmasterEnvironment"); #endif } +#ifdef PGL_MOBILE + PGL_LOG_INFO("[pgl_backend] EXIT: function completing successfully"); +#endif } #if defined(__EMSCRIPTEN__) @@ -421,18 +763,46 @@ __attribute__ ((export_name("pgl_backend"))) __attribute__ ((export_name("pgl_initdb"))) #endif int pgl_initdb() { + PGL_LOG_INFO("[pgl_initdb] ENTRY: function called"); PDEBUG("# 412: pg_initdb()"); + /* Ensure PREFIX/PGDATA/PGUSER defaults like wasm main_pre */ + if (!PREFIX || !*PREFIX) { + PREFIX = setdefault("PREFIX", WASM_PREFIX); + } + if (!getenv("PGDATABASE")) setenv("PGDATABASE", "template1", 0); + PGUSER = setdefault("PGUSER", WASM_USERNAME); + setenv("PGUSER", WASM_USERNAME, 1); + char _pgbuf[STROPS_BUF]; + strconcat(_pgbuf, (const char*)PREFIX, "/base"); + if (!PGDATA || !*PGDATA) { + PGDATA = setdefault("PGDATA", _pgbuf); + } + fprintf(stderr, "[pgl_initdb] PREFIX=%s PGDATA=%s PGUSER=%s PGSYSCONFDIR=%s\n", PREFIX? (const char*)PREFIX : "", PGDATA? (const char*)PGDATA : "", PGUSER? (const char*)PGUSER : "", getenv("PGSYSCONFDIR")?getenv("PGSYSCONFDIR"):""); + + /* Initialize progname for mobile library mode */ + if (!progname) { + progname = "postgres"; + } + optind = 1; pgl_idb_status |= IDB_FAILED; + /* Allow forcing initdb even if PG_VERSION exists */ + const char* __force_env = getenv("PGL_FORCE_INITDB"); + bool __force_initdb = (__force_env && __force_env[0] == '1'); + PGL_LOG_INFO("[pgl_initdb] About to check if database exists at PGDATA=%s", PGDATA? (const char*)PGDATA : ""); if (!chdir(PGDATA)) { - if (access("PG_VERSION", F_OK) == 0) { + int __has_pgversion = (access("PG_VERSION", F_OK) == 0); + fprintf(stderr, "[pgl_initdb] chdir PGDATA ok; PG_VERSION=%s force=%d\n", __has_pgversion ? "yes" : "no", __force_initdb ? 1 : 0); + PGL_LOG_INFO("[pgl_initdb] Database exists check: PG_VERSION=%s force=%d", __has_pgversion ? "yes" : "no", __force_initdb ? 1 : 0); + if (__has_pgversion && !__force_initdb) { chdir("/"); pgl_idb_status |= IDB_HASDB; /* assume auth success for now */ pgl_idb_status |= IDB_HASUSER; + PGL_LOG_INFO("[pgl_initdb] Database already exists, skipping initdb"); #if PGDEBUG fprintf(stdout, "# 427: pg_initdb: db exists at : %s TODO: test for db name : %s \n", PGDATA, getenv("PGDATABASE")); #endif // PGDEBUG @@ -441,60 +811,351 @@ __attribute__ ((export_name("pgl_backend"))) goto initdb_done; } chdir("/"); + PGL_LOG_INFO("[pgl_initdb] No existing database found, will run initdb"); #if PGDEBUG fprintf(stderr, "# 435: pg_initdb no db found at : %s\n", PGDATA); #endif // PGDEBUG } else { + fprintf(stderr, "[pgl_initdb] chdir PGDATA failed (dir missing?) path=%s errno=%d\n", PGDATA ? (const char*)PGDATA : "", errno); + PGL_LOG_INFO("[pgl_initdb] chdir PGDATA failed, will run initdb"); #if PGDEBUG fprintf(stderr, "# 439: pg_initdb db folder not found at : %s\n", PGDATA); #endif // PGDEBUG } + PGL_LOG_INFO("[pgl_initdb] Calling pgl_initdb_main()..."); int initdb_rc = pgl_initdb_main(); + fprintf(stderr, "[pgl_main] pgl_initdb_main rc=%d\n", initdb_rc); + PGL_LOG_INFO("[pgl_initdb] pgl_initdb_main() returned %d", initdb_rc); + const char* skip_replay = getenv("PGL_SKIP_REPLAY"); + if (skip_replay && skip_replay[0] == '1') { + fprintf(stderr, "[pgl_main] skipping boot replay due to PGL_SKIP_REPLAY=1\n"); + goto initdb_done; + } #if PGDEBUG fprintf(stderr, "\n\n# 444: " __FILE__ "pgl_initdb_main = %d\n", initdb_rc); #endif // PGDEBUG PDEBUG("# 448:" __FILE__); + // Log control file and bki presence pre-bootstrap + { + const char* sysconf = getenv("PGSYSCONFDIR"); + char ctrl_path[1024]; + snprintf(ctrl_path, sizeof(ctrl_path), "%s/global/pg_control", PGDATA); + struct stat st; int rc = stat(ctrl_path, &st); + fprintf(stderr, "[pgl_main] pre-boot ctrl=%s rc=%d errno=%d size=%lld\n", + ctrl_path, rc, errno, (long long)((rc==0)?st.st_size:0)); + if (sysconf) { + char bki_path[1024]; + snprintf(bki_path, sizeof(bki_path), "%s/postgres.bki", sysconf); + struct stat sb; int rcb = stat(bki_path, &sb); + fprintf(stderr, "[pgl_main] pre-boot bki=%s rc=%d errno=%d size=%lld\n", + bki_path, rcb, errno, (long long)((rcb==0)?sb.st_size:0)); + } else { + fprintf(stderr, "[pgl_main] pre-boot PGSYSCONFDIR not set\n"); + } + } /* save stdin and use previous initdb output to feed boot mode */ +#ifdef __ANDROID__ + /* On Android, STDIN_FILENO may not be properly initialized or may block. + * Create a dummy stdin that points to /dev/null to avoid hanging. */ + int saved_stdin = open("/dev/null", O_RDONLY); + if (saved_stdin < 0) { + fprintf(stderr, "[pgl_main] open(/dev/null) failed: %d\n", errno); + return pgl_idb_status; + } + fprintf(stderr, "[pgl_main] Android: using /dev/null as saved_stdin=%d\n", saved_stdin); +#else int saved_stdin = dup(STDIN_FILENO); + if (saved_stdin < 0) { + fprintf(stderr, "[pgl_main] dup(STDIN) failed: %d\n", errno); + return pgl_idb_status; + } +#endif { PDEBUG("# 450: restarting in boot mode for initdb"); - freopen(IDB_PIPE_BOOT, "r", stdin); +#ifdef PGL_MOBILE + char __pipe_path[1024]; + extern void pgl_get_pipe_path(int stage, char* out, size_t outsz); + pgl_get_pipe_path(0, __pipe_path, sizeof(__pipe_path)); + fprintf(stderr, "[pgl_main] boot pipe path=%s\n", __pipe_path); + struct stat __bp_st; int __bp_rc = stat(__pipe_path, &__bp_st); + fprintf(stderr, "[pgl_main] boot pipe stat rc=%d errno=%d size=%lld\n", __bp_rc, errno, (long long)((__bp_rc==0)?__bp_st.st_size:0)); + FILE* fr = freopen(__pipe_path, "r", stdin); +#else + FILE* fr = freopen(IDB_PIPE_BOOT, "r", stdin); +#endif + if (!fr) { +#ifdef PGL_MOBILE + fprintf(stderr, "[pgl_main] freopen boot failed for %s errno=%d\n", __pipe_path, errno); +#else + fprintf(stderr, "[pgl_main] freopen boot failed for %s errno=%d\n", IDB_PIPE_BOOT, errno); +#endif + // attempt to restore STDIN before returning + dup2(saved_stdin, STDIN_FILENO); + close(saved_stdin); + stdin = fdopen(STDIN_FILENO, "r"); + return pgl_idb_status; + } - char *boot_argv[] = { - g_argv[0], - "--boot", - "-D", PGDATA, - "-d", "3", - WASM_PGOPTS, - "-X", "1048576", - NULL - }; - int boot_argc = sizeof(boot_argv) / sizeof(char *) - 1; + const char* boot_argv0; + char __argv0_buf[STROPS_BUF]; + if (g_argv && g_argv[0] && g_argv[0][0]) { + boot_argv0 = g_argv[0]; + } else { + const char* pr = (PREFIX && ((const char*)PREFIX)[0]) ? (const char*)PREFIX : WASM_PREFIX; + strconcat(__argv0_buf, pr, "/bin/postgres"); + boot_argv0 = __argv0_buf; + } + fprintf(stderr, "[pgl_main] boot argv0=%s\n", boot_argv0); + + // Build argv dynamically to avoid inserting empty arguments (e.g., when WASM_PGOPTS is "") + char *boot_argv[32]; + int boot_argc = 0; + boot_argv[boot_argc++] = (char*)boot_argv0; + boot_argv[boot_argc++] = "--boot"; + boot_argv[boot_argc++] = "-F"; + boot_argv[boot_argc++] = "-c"; boot_argv[boot_argc++] = "log_checkpoints=false"; + boot_argv[boot_argc++] = "-c"; boot_argv[boot_argc++] = "log_min_messages=error"; + boot_argv[boot_argc++] = "-c"; boot_argv[boot_argc++] = "client_min_messages=error"; + boot_argv[boot_argc++] = "-c"; boot_argv[boot_argc++] = "log_error_verbosity=terse"; + // Disable startup progress timers to avoid timeout machinery during bootstrap + boot_argv[boot_argc++] = "-c"; boot_argv[boot_argc++] = "log_startup_progress_interval=0"; + // keep resource usage minimal and avoid dynamic shared memory on Android + boot_argv[boot_argc++] = "-c"; boot_argv[boot_argc++] = "shared_buffers=16"; + // Single-user: keep minimal connections; 1 is sufficient for bootstrap + boot_argv[boot_argc++] = "-c"; boot_argv[boot_argc++] = "max_connections=1"; + // Avoid GUCs that may be unavailable in bootstrap/mobile builds + // Disable huge pages on mobile; fallback mmap is fine + boot_argv[boot_argc++] = "-c"; boot_argv[boot_argc++] = "huge_pages=off"; + // boot_argv[boot_argc++] = "-c"; boot_argv[boot_argc++] = "wal_level=minimal"; + boot_argv[boot_argc++] = "-D"; boot_argv[boot_argc++] = (char*)PGDATA; + boot_argv[boot_argc++] = "-d"; boot_argv[boot_argc++] = "3"; + if (WASM_PGOPTS[0] != '\0') { + boot_argv[boot_argc++] = (char*)WASM_PGOPTS; + } + boot_argv[boot_argc++] = "-X"; boot_argv[boot_argc++] = "1048576"; + boot_argv[boot_argc] = NULL; // argc must NOT count the NULL terminator set_pglocale_pgservice(boot_argv[0], PG_TEXTDOMAIN("initdb")); - optind = 1; - BootstrapModeMain(boot_argc, boot_argv, false); + // Reset getopt() globals defensively before calling into backend parsers + optind = 1; opterr = 1; optopt = 0; optarg = NULL; + + // Log argv for diagnosis + fprintf(stderr, "[pgl_main] boot argc=%d argv:", boot_argc); + // Ensure working directory is PGDATA for any relative paths during bootstrap. + // Also set backend DataDir explicitly so ChangeToDataDir() and path resolvers match. + if (chdir((const char*)PGDATA) != 0) { + fprintf(stderr, "[pgl_main] chdir(PGDATA) failed errno=%d\n", errno); + } + SetDataDir((const char*)PGDATA); + + for (int i = 0; i < boot_argc; i++) { + fprintf(stderr, " %s", boot_argv[i]); + } + fputc('\n', stderr); + + // Append bootstrap stderr to the same initdb log used on Android, if set + const char* appendLog = getenv("PGL_INITDB_LOG"); + FILE* __boot_log = NULL; + // Install a temporary emit_log_hook to capture bootstrap errors + emit_log_hook_type prev_hook = emit_log_hook; + emit_log_hook = pgl_boot_emit_hook; + int bootstrap_stderr_fd = -1; + + if (appendLog && appendLog[0]) { + __boot_log = freopen(appendLog, "a", stderr); + if (__boot_log) { + setvbuf(stderr, NULL, _IONBF, 0); // unbuffer stderr so crashes don't lose logs + fprintf(stderr, "[pgl_main] appending bootstrap logs to %s\n", appendLog); + } + } + + + // Protect against ereport(FATAL)/ERROR inside BootstrapModeMain from exiting the process + sigjmp_buf __boot_jmp; + + bool __boot_err = false; + if (sigsetjmp(__boot_jmp, 1) != 0) { + /* Ensure we clear PG_exception_stack to avoid dangling pointer after longjmp */ + PG_exception_stack = NULL; + __boot_err = true; + fprintf(stderr, "[pgl_main] BootstrapModeMain exited via error (longjmp), continuing without proc_exit\n"); + } else { + /* Defensive: ensure basic subsystems are initialized before calling BootstrapModeMain */ + if (CurrentMemoryContext == NULL) + MemoryContextInit(); + const char *argv0_boot = (boot_argv && boot_argv[0] && boot_argv[0][0]) ? boot_argv[0] : (g_argv && g_argv[0] ? g_argv[0] : "postgres"); + set_pglocale_pgservice(argv0_boot, PG_TEXTDOMAIN("initdb")); + PG_exception_stack = &__boot_jmp; + fprintf(stderr, "[pgl_main] calling BootstrapModeMain\n"); + PGL_LOG_INFO("%s", "[pgl_main] About to call BootstrapModeMain"); + // Also redirect stderr to initdb.stderr.log so ereport lands there + char errlog[1024]; + snprintf(errlog, sizeof(errlog), "%s/initdb.stderr.log", PREFIX ? (const char*)PREFIX : WASM_PREFIX); + bootstrap_stderr_fd = open(errlog, O_WRONLY | O_CREAT | O_APPEND, 0644); + if (bootstrap_stderr_fd >= 0) { + PGL_LOG_INFO("[pgl_main] Redirecting stderr to %s (fd=%d)", errlog, bootstrap_stderr_fd); + dup2(bootstrap_stderr_fd, STDERR_FILENO); + setvbuf(stderr, NULL, _IONBF, 0); // unbuffer stderr + } else { + PGL_LOG_ERROR("[pgl_main] Failed to open stderr log %s: errno=%d", errlog, errno); + } + /* Intercept proc_exit during bootstrap to avoid PANIC in child context */ + sigjmp_buf __boot_exit_jmp; + pgl_boot_jmp = &__boot_exit_jmp; + if (sigsetjmp(__boot_exit_jmp, 1) != 0) { + pgl_boot_jmp = NULL; + fprintf(stderr, "[pgl_boot] proc_exit intercepted during bootstrap, continuing\n"); + PGL_LOG_INFO("%s", "[pgl_boot] proc_exit intercepted during bootstrap"); + } else { + PGL_LOG_INFO("%s", "[pgl_main] Entering BootstrapModeMain"); + BootstrapModeMain(boot_argc, boot_argv, false); + PGL_LOG_INFO("[pgl_main] BootstrapModeMain completed successfully"); + } + pgl_boot_jmp = NULL; + fprintf(stderr, "[pgl_main] BootstrapModeMain returned normally\n"); + PGL_LOG_ERROR("%s", "[pgl_main] *** BootstrapModeMain phase completed ***"); + } + PGL_LOG_ERROR("%s", "[pgl_main] *** About to clear PG_exception_stack ***"); + PG_exception_stack = NULL; + PGL_LOG_ERROR("%s", "[pgl_main] *** About to restore emit_log_hook ***"); + emit_log_hook = prev_hook; + PGL_LOG_ERROR("%s", "[pgl_main] *** Hooks restored, about to restore stderr ***"); + +#ifdef __ANDROID__ + PGL_LOG_ERROR("%s", "[pgl_main] *** About to restore stderr after bootstrap ***"); + // CRITICAL: Restore stderr to Android pipe after bootstrap to prevent hang + if (bootstrap_stderr_fd >= 0) { + PGL_LOG_ERROR("%s", "[pgl_main] *** About to close bootstrap stderr fd ***"); + close(bootstrap_stderr_fd); + PGL_LOG_ERROR("%s", "[pgl_main] *** Closed bootstrap stderr log file ***"); + // Restore stderr to the Android pipe for continued logging + if (pgl_stderr_pipe[1] >= 0) { + PGL_LOG_ERROR("%s", "[pgl_main] *** About to dup2 stderr back to pipe ***"); + if (dup2(pgl_stderr_pipe[1], STDERR_FILENO) < 0) { + PGL_LOG_ERROR("[pgl_main] Failed to restore stderr to pipe: errno=%d", errno); + } else { + PGL_LOG_ERROR("%s", "[pgl_main] *** dup2 successful, about to setvbuf ***"); + setvbuf(stderr, NULL, _IONBF, 0); + PGL_LOG_ERROR("%s", "[pgl_main] *** Successfully restored stderr to Android pipe ***"); + } + } else { + PGL_LOG_ERROR("%s", "[pgl_main] Android stderr pipe not available for restoration"); + } + } else { + PGL_LOG_ERROR("%s", "[pgl_main] *** bootstrap_stderr_fd was not opened ***"); + } + PGL_LOG_ERROR("%s", "[pgl_main] *** Stderr restoration completed ***"); + + // close the file stream, then restore the original FD 0 and stdin + fprintf(stderr, "[pgl_main] about to fclose(stdin)\n"); + PGL_LOG_ERROR("%s", "[pgl_main] *** About to close stdin stream ***"); fclose(stdin); -#if PGDEBUG - puts("BOOT FILE:"); - puts(IDB_PIPE_BOOT); + fprintf(stderr, "[pgl_main] fclose(stdin) completed\n"); + PGL_LOG_ERROR("%s", "[pgl_main] *** stdin stream closed successfully ***"); + /* On Android, we used /dev/null as saved_stdin, so just reopen /dev/null for stdin */ + fprintf(stderr, "[pgl_main] Android: reopening /dev/null for stdin\n"); + PGL_LOG_INFO("%s", "[pgl_main] Reopening /dev/null for stdin"); + stdin = fopen("/dev/null", "r"); + if (!stdin) { + fprintf(stderr, "[pgl_main] fopen(/dev/null) for stdin failed errno=%d\n", errno); + PGL_LOG_ERROR("[pgl_main] fopen(/dev/null) for stdin failed errno=%d", errno); + } else { + PGL_LOG_INFO("%s", "[pgl_main] Successfully reopened /dev/null for stdin"); + } + close(saved_stdin); + PGL_LOG_INFO("%s", "[pgl_main] Closed saved_stdin fd"); #else - remove(IDB_PIPE_BOOT); + if (dup2(saved_stdin, STDIN_FILENO) < 0) { + fprintf(stderr, "[pgl_main] dup2 restore STDIN failed errno=%d\n", errno); + close(saved_stdin); + return pgl_idb_status; + } + close(saved_stdin); +#endif + fprintf(stderr, "[pgl_main] about to fdopen(STDIN_FILENO)\n"); + PGL_LOG_INFO("%s", "[pgl_main] Skipping fdopen on Android"); +#ifndef __ANDROID__ + stdin = fdopen(STDIN_FILENO, "r"); + if (!stdin) { + fprintf(stderr, "[pgl_main] fdopen(STDIN) restore failed errno=%d\n", errno); + return pgl_idb_status; + } #endif - stdin = fdopen(saved_stdin, "r"); + fprintf(stderr, "[pgl_main] stdin restoration completed\n"); + PGL_LOG_INFO("%s", "[pgl_main] stdin restoration phase completed"); + // Do NOT exit the process; just continue to allow backend to start + if (__boot_err) { + fprintf(stderr, "[pgl_main] initdb boot replay completed with errors\n"); + // Diagnostics: current cwd, DataDir, and first few lines of boot file + char cwd_buf[1024]; + if (getcwd(cwd_buf, sizeof(cwd_buf))) { + fprintf(stderr, "[pgl_main] cwd=%s DataDir=%s\n", cwd_buf, DataDir ? DataDir : ""); + } + // Log global dir existence + { + char gpath[1024]; struct stat gst; + snprintf(gpath, sizeof(gpath), "%s/global", PGDATA); + int grc = stat(gpath, &gst); + fprintf(stderr, "[pgl_main] global dir stat rc=%d errno=%d\n", grc, errno); + } + // Dump first 25 lines of boot stream +#ifdef PGL_MOBILE + { + char pipe_path[1024]; extern void pgl_get_pipe_path(int stage, char* out, size_t outsz); + pgl_get_pipe_path(0, pipe_path, sizeof(pipe_path)); + FILE* f = fopen(pipe_path, "r"); + if (f) { + fprintf(stderr, "[pgl_main] boot head:\n"); + char line[256]; int n=0; while (n<25 && fgets(line, sizeof(line), f)) { fputs(line, stderr); n++; } + if (!feof(f)) fprintf(stderr, "[pgl_main] ... (truncated)\n"); + fclose(f); + } else { + fprintf(stderr, "[pgl_main] cannot open boot file for head: %s errno=%d\n", pipe_path, errno); + } + } +#else + { + FILE* f = fopen(IDB_PIPE_BOOT, "r"); + if (f) { + fprintf(stderr, "[pgl_main] boot head:\n"); + char line[256]; int n=0; while (n<25 && fgets(line, sizeof(line), f)) { fputs(line, stderr); n++; } + if (!feof(f)) fprintf(stderr, "[pgl_main] ... (truncated)\n"); + fclose(f); + } else { + fprintf(stderr, "[pgl_main] cannot open boot file for head: %s errno=%d\n", IDB_PIPE_BOOT, errno); + } + } +#endif + } else { + PDEBUG("# 479: initdb boot replay done"); + PGL_LOG_INFO("%s", "[pgl_main] initdb boot replay completed successfully"); + } + // Log control file presence post-bootstrap + { + char ctrl_path[1024]; + snprintf(ctrl_path, sizeof(ctrl_path), "%s/global/pg_control", PGDATA); + struct stat st; int rc = stat(ctrl_path, &st); + fprintf(stderr, "[pgl_main] post-boot ctrl=%s rc=%d errno=%d size=%lld\n", + ctrl_path, rc, errno, (long long)((rc==0)?st.st_size:0)); + PGL_LOG_INFO("[pgl_main] post-boot ctrl=%s rc=%d errno=%d size=%lld", + ctrl_path, rc, errno, (long long)((rc==0)?st.st_size:0)); + } + fprintf(stderr, "[pgl_main] bootstrap section completed successfully\n"); + PGL_LOG_ERROR("%s", "[pgl_main] *** BOOTSTRAP SECTION COMPLETED SUCCESSFULLY ***"); + PGL_LOG_ERROR("%s", "[pgl_main] *** EXITING BOOTSTRAP BLOCK ***"); - PDEBUG("# 479: initdb faking shutdown to complete WAL/OID states"); - pg_proc_exit(66); } + PGL_LOG_ERROR("%s", "[pgl_initdb] *** PAST BOOTSTRAP SECTION, CONTINUING TO CLEANUP ***"); + /* use previous initdb output to feed single mode */ /* or resume a previous db */ //IsPostmasterEnvironment = true; - if (TransamVariables->nextOid < ((Oid) FirstNormalObjectId)) { + if (TransamVariables && TransamVariables->nextOid < ((Oid) FirstNormalObjectId)) { #if PGDEBUG puts("# 482: warning oid base too low, will need to set OID range after initdb(bootstrap/single)"); #endif @@ -509,6 +1170,7 @@ __attribute__ ((export_name("pgl_backend"))) "--single", "-d", "1", "-B", "16", "-S", "512", "-f", "siobtnmh", "-D", PGDATA, + "-c", "log_startup_progress_interval=0", "-F", "-O", "-j", WASM_PGOPTS, "template1", @@ -523,6 +1185,7 @@ PDEBUG("# 498: initdb faking shutdown to complete WAL/OID states in single mode" */ async_restart = 1; initdb_done:; + PGL_LOG_INFO("[pgl_initdb] Reached initdb_done label"); pgl_idb_status |= IDB_CALLED; if (optind > 0) { @@ -534,6 +1197,11 @@ PDEBUG("# 498: initdb faking shutdown to complete WAL/OID states in single mode" PDEBUG("# 524: exiting on initdb-single error"); // TODO raise js exception } + PGL_LOG_INFO("[pgl_initdb] EXIT: returning %d", pgl_idb_status); + PGL_LOG_ERROR("%s", "[pgl_initdb] *** FINAL: About to return from pgl_initdb function ***"); + PGL_LOG_ERROR("%s", "[pgl_initdb] *** If you see this message, pgl_initdb completed successfully ***"); + fprintf(stderr, "[pgl_initdb] *** RETURNING FROM pgl_initdb WITH STATUS %d ***\n", pgl_idb_status); + PGL_LOG_ERROR("%s", "[pgl_initdb] *** ABOUT TO EXECUTE RETURN STATEMENT ***"); return pgl_idb_status; } // pgl_initdb @@ -552,6 +1220,7 @@ PDEBUG("# 498: initdb faking shutdown to complete WAL/OID states in single mode" PGOPTIONS */ +#if !defined(PGL_LIB_ONLY) // __attribute__((export_name("main"))) int main(int argc, char **argv) { int exit_code = 0; @@ -592,3 +1261,4 @@ PDEBUG("# 498: initdb faking shutdown to complete WAL/OID states in single mode" #endif return exit_code; } +#endif // !PGL_LIB_ONLY diff --git a/pglite-wasm/pg_proto.c b/pglite-wasm/pg_proto.c index fd21210f420d3..8ac08bb2134de 100644 --- a/pglite-wasm/pg_proto.c +++ b/pglite-wasm/pg_proto.c @@ -15,6 +15,11 @@ query_string = pq_getmsgstring(&input_message); pq_getmsgend(&input_message); +#ifdef PGL_MOBILE + /* Use NOTICE level to ensure it gets through to client */ + ereport(NOTICE, (errmsg("MOBILE DEBUG: exec_simple_query starting query='%s'", query_string))); +#endif + if (am_walsender) { if (!exec_replication_command(query_string)) exec_simple_query(query_string); @@ -23,6 +28,10 @@ // valgrind_report_error_query(query_string); +#ifdef PGL_MOBILE + ereport(NOTICE, (errmsg("MOBILE DEBUG: exec_simple_query completed query='%s'", query_string))); +#endif + send_ready_for_query = true; } break; diff --git a/pglite-wasm/pgl_initdb.c b/pglite-wasm/pgl_initdb.c index 19bf7fd405457..bca6ed7d52ea0 100644 --- a/pglite-wasm/pgl_initdb.c +++ b/pglite-wasm/pgl_initdb.c @@ -1,9 +1,12 @@ #pragma once #include // FILE+fprintf +#include +#include #ifndef PGL_INITDB_MAIN #define PGL_INITDB_MAIN #endif +#include /* * and now popen will return predefined slot from a file list @@ -42,13 +45,82 @@ pg_chmod(const char * path, int mode_t) { #include "interfaces/libpq/pqexpbuffer.c" +// On Android/mobile glue, ensure initdb does not attempt POSIX shm APIs +#undef HAVE_SHM_OPEN +#undef HAVE_SHM_UNLINK + + #define sync_pgdata(...) #define icu_language_tag(loc_str) icu_language_tag_idb(loc_str) #define icu_validate_locale(loc_str) icu_validate_locale_idb(loc_str) +#ifdef PGL_MOBILE +// Force initdb to use our mobile discovery functions instead of probing real binaries +#define find_other_exec find_other_exec_mobile +#define get_share_path get_share_path_mobile + +static int find_other_exec_mobile(const char *argv0, const char *target, const char *versionstr, char *retpath) { + (void)argv0; (void)versionstr; + const char* prefix = getenv("PREFIX"); + if (!prefix || !*prefix) prefix = getenv("ANDROID_DATA_DIR"); + if (!prefix || !*prefix) prefix = "/data/local/tmp/pglite"; + const char* tgt = target && *target ? target : "postgres"; + snprintf(retpath, MAXPGPATH, "%s/bin/%s", prefix, tgt); + return 0; // success +} + +static void get_share_path_mobile(const char *my_exec_path, char *ret_path) { + (void)my_exec_path; + const char* conf = getenv("PGSYSCONFDIR"); +#ifdef __APPLE__ + const char* runtime = getenv("IOS_RUNTIME_DIR"); +#else + const char* runtime = getenv("ANDROID_RUNTIME_DIR"); +#endif + // Candidates in order: PGSYSCONFDIR/share/postgresql, runtime/share/postgresql, runtime/postgresql + if (conf && *conf) { + snprintf(ret_path, MAXPGPATH, "%s/share/postgresql", conf); + return; + } + if (runtime && *runtime) { + // Prefer share/postgresql but accept postgresql fallback + char cand[MAXPGPATH]; + snprintf(cand, sizeof(cand), "%s/share/postgresql", runtime); + // Don't check for existence here; initdb will validate inputs shortly + snprintf(ret_path, MAXPGPATH, "%s", cand); + return; + } + // Last resort + snprintf(ret_path, MAXPGPATH, "/data/local/tmp/pglite/share/postgresql"); +} +#endif + +#ifdef PGL_CATCH_EXIT +// Catch initdb's exit() and convert into a longjmp back to pgl_initdb_safe +static jmp_buf g_initdb_jmp; +static int g_initdb_status = 0; +static void pglite_initdb_exit(int code) { + g_initdb_status = code; + longjmp(g_initdb_jmp, 1); +} +#define exit(code) pglite_initdb_exit(code) +#endif + #include "bin/initdb/initdb.c" +#ifdef PGL_CATCH_EXIT +// Safe wrapper that prevents process termination on initdb failures +int pgl_initdb_safe(void) { + g_initdb_status = 0; + if (setjmp(g_initdb_jmp) == 0) { + (void)pgl_initdb_main(); + return 0; + } + return g_initdb_status ? g_initdb_status : -1; +} +#endif + void use_socketfile(void) { is_repl = true; is_embed = false; diff --git a/pglite-wasm/pgl_mains.c b/pglite-wasm/pgl_mains.c index 98f425a6e4116..d6952de7bc170 100644 --- a/pglite-wasm/pgl_mains.c +++ b/pglite-wasm/pgl_mains.c @@ -1,4 +1,6 @@ #include +#include +#include volatile int sf_connected = 0; FILE * single_mode_feed = NULL; @@ -135,17 +137,41 @@ RePostgresSingleUserMain(int single_argc, char *single_argv[], const char *usern #if PGDEBUG printf("# 123: RePostgresSingleUserMain progname=%s for %s feed=%s\n", progname, single_argv[0], IDB_PIPE_SINGLE); #endif - single_mode_feed = fopen(IDB_PIPE_SINGLE, "r"); + // On mobile, the single-user script is emitted under PREFIX/runtime + char idb_single_path[1024]; + snprintf(idb_single_path, sizeof(idb_single_path), "%s/initdb.single.txt", PREFIX ? (const char*)PREFIX : WASM_PREFIX); + single_mode_feed = fopen(idb_single_path, "r"); + if (!single_mode_feed) { + fprintf(stderr, "[pgl_single] failed to open %s (errno=%d)\n", idb_single_path, errno); + return; // nothing to replay; continue to backend startup + } // should be template1. const char *dbname = NULL; + // Reset getopt() state to ensure proper parsing after prior BootstrapModeMain + optind = 1; opterr = 1; optopt = 0; optarg = NULL; + + // Log argv for diagnosis + fprintf(stderr, "[pgl_single] argc=%d argv:", single_argc); + for (int i = 0; i < single_argc; i++) { + fprintf(stderr, " %s", single_argv[i]); + } + fputc('\n', stderr); /* Parse command-line options. */ process_postgres_switches(single_argc, single_argv, PGC_POSTMASTER, &dbname); #if PGDEBUG printf("# 134: dbname=%s\n", dbname); #endif + /* Log DataDir and control file presence before reading it */ + { + char ctrl_path[1024]; + snprintf(ctrl_path, sizeof(ctrl_path), "%s/global/pg_control", DataDir); + struct stat st; int rc = stat(ctrl_path, &st); + fprintf(stderr, "[pgl_single] DataDir=%s PGDATA(env)=%s ctrl=%s rc=%d errno=%d size=%lld\n", + DataDir, getenv("PGDATA"), ctrl_path, rc, errno, (long long)((rc==0)?st.st_size:0)); + } LocalProcessControlFile(false); process_shared_preload_libraries(); diff --git a/pglite-wasm/pgl_os.h b/pglite-wasm/pgl_os.h index a8a30e543e164..02cacb88916ac 100644 --- a/pglite-wasm/pgl_os.h +++ b/pglite-wasm/pgl_os.h @@ -5,6 +5,41 @@ #include // FILE +#include +#include + +// Unified logging macros for all platforms +#ifdef PGL_MOBILE + #ifdef __ANDROID__ + #include + #define PGL_LOG(level, ...) __android_log_print(level, "PGLite", __VA_ARGS__) + #define PGL_LOG_INFO(...) __android_log_print(ANDROID_LOG_INFO, "PGLite", __VA_ARGS__) + #define PGL_LOG_ERROR(...) __android_log_print(ANDROID_LOG_ERROR, "PGLite", __VA_ARGS__) + #define PGL_LOG_WARN(...) __android_log_print(ANDROID_LOG_WARN, "PGLite", __VA_ARGS__) + #else // iOS and other mobile + #define PGL_LOG(level, ...) do { \ + fprintf(stderr, "[PGLite] "); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n"); \ + fflush(stderr); \ + } while(0) + #define PGL_LOG_INFO(...) PGL_LOG(0, __VA_ARGS__) + #define PGL_LOG_ERROR(...) PGL_LOG(0, __VA_ARGS__) + #define PGL_LOG_WARN(...) PGL_LOG(0, __VA_ARGS__) + #endif +#else // WASM + #define PGL_LOG(level, ...) fprintf(stderr, __VA_ARGS__) + #define PGL_LOG_INFO(...) fprintf(stderr, __VA_ARGS__) + #define PGL_LOG_ERROR(...) fprintf(stderr, __VA_ARGS__) + #define PGL_LOG_WARN(...) fprintf(stderr, __VA_ARGS__) +#endif + +// Debug logging controlled by PGDEBUG flag (like PDEBUG in WASM) +#if PGDEBUG + #define PGL_DEBUG(...) PGL_LOG_INFO(__VA_ARGS__) +#else + #define PGL_DEBUG(...) // no-op +#endif FILE* IDB_PIPE_FP = NULL; int IDB_STAGE = 0; @@ -15,7 +50,23 @@ int IDB_STAGE = 0; * as file handle in initdb.c */ +#ifdef PGL_MOBILE +static const char* get_env_or(const char* k, const char* d) { const char* v = getenv(k); return (v && *v) ? v : d; } +static void build_pipe_path(int stage, char* out, size_t outsz) { + const char* runtime = getenv("ANDROID_RUNTIME_DIR"); +#ifdef __APPLE__ + if (!runtime || !*runtime) runtime = getenv("IOS_RUNTIME_DIR"); +#endif + const char* base = (runtime && *runtime) ? runtime : get_env_or("PGDATA", "pglite/pgdata"); + const char* fname = stage==0 ? "initdb.boot.txt" : "initdb.single.txt"; + snprintf(out, outsz, "%s/%s", base, fname); +} +// Expose for readers (pg_main.c) to reopen the same files we wrote via pgl_popen +void pgl_get_pipe_path(int stage, char* out, size_t outsz) { build_pipe_path(stage, out, outsz); } +#endif + FILE *pgl_popen(const char *command, const char *type) { + (void)type; if (IDB_STAGE>1) { fprintf(stderr,"# popen[%s]\n", command); return stderr; @@ -23,11 +74,25 @@ FILE *pgl_popen(const char *command, const char *type) { if (!IDB_STAGE) { fprintf(stderr,"# popen[%s] (BOOT)\n", command); + #ifdef PGL_MOBILE + char path[1024]; build_pipe_path(0, path, sizeof(path)); + IDB_PIPE_FP = fopen(path, "w"); + fprintf(stderr, "# pgl_popen BOOT file=%s\n", path); + #else IDB_PIPE_FP = fopen( IDB_PIPE_BOOT, "w"); + fprintf(stderr, "# pgl_popen BOOT file=%s\n", IDB_PIPE_BOOT); + #endif IDB_STAGE = 1; } else { fprintf(stderr,"# popen[%s] (SINGLE)\n", command); + #ifdef PGL_MOBILE + char path[1024]; build_pipe_path(1, path, sizeof(path)); + IDB_PIPE_FP = fopen(path, "w"); + fprintf(stderr, "# pgl_popen SINGLE file=%s\n", path); + #else IDB_PIPE_FP = fopen( IDB_PIPE_SINGLE, "w"); + fprintf(stderr, "# pgl_popen SINGLE file=%s\n", IDB_PIPE_SINGLE); + #endif IDB_STAGE = 2; } @@ -38,10 +103,11 @@ FILE *pgl_popen(const char *command, const char *type) { int pgl_pclose(FILE *stream) { + (void)stream; if (IDB_STAGE==1) - fprintf(stderr,"# pg_pclose(%s) 133:" __FILE__ "\n" , IDB_PIPE_BOOT); + fprintf(stderr,"# pg_pclose(BOOT) 133:" __FILE__ "\n"); if (IDB_STAGE==2) - fprintf(stderr,"# pg_pclose(%s) 135:" __FILE__ "\n" , IDB_PIPE_SINGLE); + fprintf(stderr,"# pg_pclose(SINGLE) 135:" __FILE__ "\n"); if (IDB_PIPE_FP) { fflush(IDB_PIPE_FP); diff --git a/pglite-wasm/pgl_tools.h b/pglite-wasm/pgl_tools.h index 295586f60ef14..33ce633093400 100644 --- a/pglite-wasm/pgl_tools.h +++ b/pglite-wasm/pgl_tools.h @@ -24,36 +24,49 @@ bool startswith(const char *str, const char *prefix) { } #endif -void -strconcat(char*p, const char *head, const char *tail) { - int len; - - len = strnlen(head, STROPS_BUF ); - p = memcpy(p, head, len); - p += len; +// Safe concatenation: tolerate NULL inputs and cap to STROPS_BUF +static inline void +strconcat(char *p, const char *head, const char *tail) { + size_t len = 0; + + if (head && head[0] && len < STROPS_BUF) { + size_t l = strnlen(head, STROPS_BUF - len); + memcpy(p + len, head, l); + len += l; + } - len = strnlen(tail, STROPS_BUF - len); - p = memcpy(p, tail, len); - p += len; - *p = '\0'; + if (tail && tail[0] && len < STROPS_BUF) { + size_t l = strnlen(tail, STROPS_BUF - len); + memcpy(p + len, tail, l); + len += l; + } + if (len >= STROPS_BUF) + len = STROPS_BUF - 1; + p[len] = '\0'; } -char * +// getenv fallback that never returns NULL +static inline char * setdefault(const char* key, const char *value) { - setenv(key, value, 0); - return strdup(getenv(key)); + // Set only if not already set + if (value) + setenv(key, value, 0); + const char *res = getenv(key); + if (!res) + res = (value ? value : ""); + return strdup(res); } -char * +static inline char * strcat_alloc(const char *head, const char *tail) { char buf[STROPS_BUF]; - strconcat( &buf[0], head, tail); + strconcat(&buf[0], head, tail); return strdup((const char *)&buf[0]); } -void -mksub_dir(const char *dir,const char *sub) { +static inline void +mksub_dir(const char *dir, const char *sub) { char buf[STROPS_BUF]; strconcat(&buf[0], dir, sub); mkdirp(&buf[0]); diff --git a/react-native.md b/react-native.md new file mode 100644 index 0000000000000..44bf090d55897 --- /dev/null +++ b/react-native.md @@ -0,0 +1,340 @@ +# PGLite React Native Module Implementation + +## Overview + +This document describes the architecture and implementation of PGLite for React Native, which provides a native PostgreSQL database engine for iOS and Android applications. The implementation maintains exact API compatibility with the web/WASM version of PGLite while using native compilation for better performance and platform integration. + +## Architecture + +### Core Principles + +1. **API Parity**: Maintain exactly the same JavaScript/TypeScript API as the web/WASM version +2. **Native Performance**: Compile PostgreSQL natively for ARM64 (iOS/Android) and x86_64 (Android) +3. **Single-User Mode**: Run PostgreSQL in single-user mode without client-server architecture +4. **Wire Protocol**: Use PostgreSQL's wire protocol for all communication between JavaScript and the native backend +5. **Minimal Native Bridge**: Keep the native bridge thin - only handle byte buffer marshalling and filesystem operations + +### Major Components + +- **PostgreSQL Core**: Native compilation of PostgreSQL 17 for mobile platforms +- **PGLite Glue**: Adaptation layer (pg_main.c, interactive_one.c, pgl_mains.c) that provides single-user mode operation +- **Nitro Module (autolinked)**: React Native bridge using Nitro Modules (JSI) with autolinking; no manually written/native bridges are required +- **TypeScript Adapter**: JavaScript layer that implements the PGLite API on top of the wire protocol + +## Execution Model + +### Startup Phase + +1. **Environment Setup** + + - Set PGDATA to app sandbox directory (e.g., `/data/user/0/com.app/files/pglite/pgdata`) + - Set PGSYSCONFDIR to runtime resources directory + - Extract bundled PostgreSQL runtime files (share/postgresql/\*) if first run + +2. **Database Initialization** + + - Call `pgl_initdb()` to check if database exists or needs creation + - If no database exists, run initdb to create the initial database cluster + - Execute bootstrap SQL to create system catalogs (via `bootstrap_template1()`) + - Run single-user replay of initialization scripts + +3. **Backend Setup** + - Call `pgl_backend()` once to initialize the backend + - Set up critical memory contexts (MessageContext, row_description_context) + - Install mobile communication methods via `pgl_install_mobile_comm()` + - Transition to normal backend operation mode + +### Query Processing + +1. **Request Flow** + + - JavaScript calls query/exec/transaction methods + - TypeScript adapter converts to PostgreSQL wire protocol messages + - Messages passed to native via `execProtocolRaw()` + - Native writes message to CMA (Contiguous Memory Area) buffer + - Calls `interactive_one()` to process the message + +2. **Backend Processing** + + - `interactive_one()` reads from the CMA buffer + - Processes via the backend wire protocol machinery (libpq/PQcomm) + - Executes SQL via `exec_simple_query()` or extended protocol + - Results written back to the CMA buffer via mobile PQcomm methods + +3. **Response Flow** + - Native reads response from CMA buffer + - Returns raw bytes to JavaScript + - TypeScript adapter parses protocol messages + - Converts to PGLite Results format + +### Communication Pattern + +The mobile implementation uses a CMA buffer for efficient communication: + +``` +JavaScript -> Wire Protocol -> CMA Buffer -> PostgreSQL Backend + ^ | + | v + +-- Response --+ +``` + +- Request written to buffer offset 1 +- Response written to buffer offset (request_size + 2) + +### Memory Management + +- Single shared buffer for request/response via CMA (size controlled by build-time CMA_MB macro; current default is 12MB) +- Conservative PostgreSQL memory settings via single-user flags (-B 16, -S 512) +- Memory contexts properly initialized and persisted across calls +- No memory leaks between successive queries + +## Status + +### What's Working + +- ✅ Database initialization (pgl_initdb) completes successfully +- ✅ Backend startup (pgl_backend) initializes properly +- ✅ Memory contexts and mobile communication methods are installed +- ✅ CMA buffer communication is set up and is the only path on mobile +- ✅ Nitro Modules autolinking loads the native module; execProtocolRaw round-trips + +### Historical issue (resolved) + +Earlier, PostgreSQL and React Native accessed different buffer instances (function-based access to `get_buffer_addr()` introduced duplicate static buffers). This caused invalid message errors. + +Resolution: Use external variables set by the mobile glue and consumed by PostgreSQL (e.g., `pgl_mobile_cma_buffer_addr`, `pgl_mobile_cma_buffer_size`). pqcomm.c (PGL_MOBILE) reads these in `pq_startmsgread()`, matching the WASM pattern of direct pointer usage. + +Notes: + +- `interactive_one.c` is the primary loop. Mobile uses its PGL_MOBILE CMA path exclusively; web/WASM continues to use the non-mobile paths. +- Verbose logging is intentionally kept to aid diagnostics. + +## Data Flow + +### Initialization Data Flow + +``` +Application Start + | + v +RuntimeResources::ensureResourcesExtracted() + | + v +Set PGDATA/PGSYSCONFDIR environment variables + | + v +pgl_initdb() - Check/create database + | + v +pgl_backend() - Initialize backend + | + v +Ready for queries +``` + +### Query Execution Data Flow + +``` +JavaScript query() + | + v +Build wire protocol message + | + v +execProtocolRaw() [Native] + | + v +Write to CMA buffer + | + v +interactive_one() + | + v +PostgreSQL processes query + | + v +Write response to CMA + | + v +Read response buffer + | + v +Parse protocol messages [JavaScript] + | + v +Return Results object +``` + +## Major Functions + +### Native Functions (C/C++) + +- `pgl_initdb()` - Initialize database cluster if needed +- `pgl_backend()` - Start PostgreSQL backend in single-user mode +- `interactive_one()` - Process one wire protocol message +- `pgl_install_mobile_comm()` - Install mobile-specific communication methods +- `get_buffer_addr()/get_buffer_size()` - CMA buffer management +- `interactive_write()/interactive_read()` - Set message sizes in buffer + +### JavaScript/TypeScript Functions + +- `PGLite.create()` - Create and initialize a database instance +- `query()` - Execute SQL query with parameters +- `exec()` - Execute SQL statements +- `transaction()` - Run queries in a transaction +- `execProtocolRaw()` - Send raw wire protocol messages +- `listen()/unlisten()` - LISTEN/NOTIFY support + +### Implementation Notes + +**Why We Don't Extend BasePGlite** + +The React Native adapter implements its own `query()` method instead of extending `BasePGlite` from `@electric-sql/pglite` due to web-specific dependencies that don't exist in React Native: + +- `File` and `Blob` types in abstract method signatures (`_handleBlob`, `_getWrittenBlob`) +- Filesystem imports (`interface.ts` imports from `fs/base.js`) +- WASM-specific utilities and memory management +- Browser-specific APIs and polyfills + +Additionally, we miss out on BasePGlite's sophisticated parameter serialization system: + +- **OID-based type detection**: Uses PostgreSQL `describe` statements to determine parameter types +- **Comprehensive type serializers**: Proper handling of arrays, JSON, bytea, dates, etc. +- **PostgreSQL-specific formats**: Boolean serialization as 't'/'f', array escaping, etc. +- **Type validation and error handling**: Catches invalid inputs before sending to PostgreSQL + +Our current React Native implementation uses a simplified serialization approach that handles basic types but may not cover all edge cases that the web version handles. + +In the future, we may separate the base class and serialization logic into platform-agnostic packages without web-specific dependencies to reduce code duplication and ensure consistent type handling across implementations. + +## Build & Initialization + +The build uses a two-stage approach on mobile: + +1. **PostgreSQL Compilation** (mobile-build/build-mobile.sh) + + - Cross-compile PostgreSQL using Autotools for each platform/ABI + - Builds static libraries: `libpgcore_mobile.a`, `libpglite_glue_mobile.a` + - Copies artifacts into the RN module under platform-specific locations + +2. **Module Linking** + - RN uses Nitro Modules with autolinking; the native module is discovered/linked automatically + +### Android initialization + +- On app process start, a ContentProvider (InitProvider) ensures the native library is loaded and environment variables are applied via NativeEnv +- Runtime resources (share/postgresql/\*) are copied into app storage if needed +- Environment variables set include (examples): + - PGDATA: `/pglite/pgdata` + - PGSYSCONFDIR: `/pglite/runtime` + - PGROOT/PREFIX: runtime root +- On first run, `pgl_initdb()` initializes the cluster, then `pgl_backend()` prepares the backend + +### iOS initialization + +- On app launch, Swift (NativeEnv) sets environment variables and prepares Application Support paths: + - PGDATA: `/PGLite/pgdata` + - PGSYSCONFDIR + PGROOT + PREFIX: `/PGLite/runtime` +- Runtime resources are bundled in the Pod and copied on first run +- The Nitro module is autolinked; no manual bridge code is required +- Then `pgl_initdb()` and `pgl_backend()` run similarly to Android + +## How to build and test + +### Integrated Build Script (Recommended) + +The `rebuild-mobile.sh` script in `packages/pglite-react-native/example/` provides an integrated workflow that automates the entire React Native mobile development process: + +**Purpose**: This script orchestrates the complete build chain from native PostgreSQL compilation to app deployment, eliminating the need to run multiple commands manually. + +**Functionality**: + +- Builds native PostgreSQL static libraries for the target platform +- Compiles the React Native module TypeScript code +- Generates platform-specific native projects (Android/iOS) +- Builds and deploys the app to devices/simulators +- Provides comprehensive error checking and progress reporting + +**Usage Examples**: + +```bash +cd packages/pglite-react-native/example + +# Build Android with defaults (arm64-v8a, API 27) +./rebuild-mobile.sh + +# Build iOS simulator for Apple Silicon +./rebuild-mobile.sh -p ios + +# Build iOS for physical device +./rebuild-mobile.sh -p ios --arch arm64 + +# Skip native build, just rebuild app +./rebuild-mobile.sh --skip-build + +# Build specific Android ABI +./rebuild-mobile.sh -p android -a arm64-v8a +``` + +**Configuration Options**: + +- `--platform`: Target platform (android, ios) +- `--abi`: Android ABI (arm64-v8a, armeabi-v7a) +- `--arch`: iOS architecture (arm64-sim, arm64, x86_64) +- `--pg-branch`: PostgreSQL branch to build +- `--skip-build`: Skip native library compilation +- `--skip-install`: Skip npm/pnpm install steps + +### Manual Build Process + +For advanced users or debugging, you can run the individual steps manually: + +- in `postgres-pglite` run `PLATFORM=android ABI=arm64-v8a PG_BRANCH=REL_17_5_WASM ./mobile-build/build-mobile.sh` to build pglite static libs for android. You need nix installed. +- build-mobile.sh copies static libs into pglite-react-native project +- in `packages/pglite-react-native` do `pnpm install` +- in `packages/pglite-react-native/example` + - `npm install` to install deps (first run only) + - `npx expo run:android` to start android sim (first run only) + - `rm -rf android` to clean generated android project (if needed) + - `npx expo prebuild -p android --clean` to generate native android project + - `cd android` + - `./gradlew assembleDebug` to build the apk + - `adb install app/build/outputs/apk/debug/app-debug.apk` to install on device + - `cd ..` + - `npx expo start -c` to start the react native dev server + - Use `adb logcat` to view android system logs for more detail. + - Open app on the emulator to test. + - To view more logs + - `adb root` to get root adb + - `adb shell` to open a shell on device + - `run-as com.evelant.example` to run as app user + - `cat files/pglite/runtime/initdb.stderr.log` to see backend logs + - You may need to `rm -rf files/pglite/pgdata` to clear data for another run after a crash + +## Production Readiness Status + +**⚠️ NOT PRODUCTION READY** - This React Native implementation is currently in active development and requires significant work before it can be considered production-ready. + +### Current Status (August 2025) + +**✅ What's Working:** + +- Basic CRUD operations (CREATE TABLE, INSERT, SELECT, UPDATE, DELETE) +- Rudimentary serialization with correct PostgreSQL OIDs +- PostgreSQL extended wire protocol communication via CMA buffers +- Android/ios build compilation and basic query execution +- Memory context initialization for mobile environment + +### Remaining Work + +- ~~**iOS Implementation** - Port Android work to iOS with proper build system integration~~ +- ~~**Extract Base Package** - Create shared `@electric-sql/pglite-base` without web dependencies so serialization and other logic can be shared with react native~~ +- **Replace Custom Serialization** - Use extracted base serialization methods for consistency +- **Review PGL_MOBILE vs **EMSCRIPTEN\*\*\*\* - Find places where mobile is missing conditional compilation it might need +- **Cleanup** - Remove debug logging, temporary code +- **Add Extensions Support** - Load and use PostgreSQL extensions + +### TODOS + +- pgl_install_mobile_comm is getting run on every request, should only be necessary on the first one? +- diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c index 8e3b1f04c1d56..cb42a8affcf0c 100644 --- a/src/backend/access/transam/xlogarchive.c +++ b/src/backend/access/transam/xlogarchive.c @@ -32,8 +32,13 @@ #include "storage/fd.h" #include "storage/ipc.h" +#if defined(__wasi__) || (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_IPHONE_SIMULATOR)) #if defined(__wasi__) #define system(cmd) system_wasi(cmd) +#else +/* For iOS builds where system() is unavailable, return failure */ +#define system(cmd) (-1) +#endif #endif /* diff --git a/src/backend/archive/shell_archive.c b/src/backend/archive/shell_archive.c index 506c5a30ad209..36db7b2ab4b0a 100644 --- a/src/backend/archive/shell_archive.c +++ b/src/backend/archive/shell_archive.c @@ -23,6 +23,11 @@ #include "common/percentrepl.h" #include "pgstat.h" +#if defined(__APPLE__) && (TARGET_OS_IOS || TARGET_IPHONE_SIMULATOR) +/* For iOS builds where system() is unavailable, return failure */ +#define system(cmd) (-1) +#endif + static bool shell_archive_configured(ArchiveModuleState *state); static bool shell_archive_file(ArchiveModuleState *state, const char *file, diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c index 8b226b212f5b3..277c6547025bc 100644 --- a/src/backend/bootstrap/bootstrap.c +++ b/src/backend/bootstrap/bootstrap.c @@ -180,7 +180,11 @@ static IndexList *ILHead = NULL; static void CheckerModeMain(void) { +#if defined(PGL_MOBILE) + return; +#else proc_exit(0); +#endif } /* @@ -195,7 +199,7 @@ CheckerModeMain(void) * to shared memory sizing, options work (or at least do not cause an error * up to shared memory creation). */ -#if !defined(__EMSCRIPTEN__) && !defined(__wasi__) +#if !defined(__EMSCRIPTEN__) && !defined(__wasi__) && defined(PGL_MOBILE) void #else int @@ -207,12 +211,19 @@ BootstrapModeMain(int argc, char *argv[], bool check_only) int flag; char *userDoption = NULL; + fprintf(stderr, "[pgl_boot] enter argc=%d argv0=%s\n", argc, argv[0] ? argv[0] : ""); + for (i = 0; i < argc && i < 20; i++) { + fprintf(stderr, "[pgl_boot] argv[%d]=%s\n", i, argv[i] ? argv[i] : ""); + } + Assert(!IsUnderPostmaster); InitStandaloneProcess(argv[0]); + fprintf(stderr, "[pgl_boot] after InitStandaloneProcess\n"); /* Set defaults, to be overridden by explicit options below */ InitializeGUCOptions(); + fprintf(stderr, "[pgl_boot] after InitializeGUCOptions\n"); /* an initial --boot or --check should be present */ Assert(argc > 1 @@ -220,9 +231,17 @@ BootstrapModeMain(int argc, char *argv[], bool check_only) || strcmp(argv[1], "--check") == 0)); argv++; argc--; + fprintf(stderr, "[pgl_boot] after --boot adjust argc=%d\n", argc); + + /* reset getopt's global state, as this process is long-lived */ + opterr = 0; optind = 1; optreset = 1; while ((flag = getopt(argc, argv, "B:c:d:D:Fkr:X:-:")) != -1) { + fprintf(stderr, "[pgl_boot] getopt flag=%c optarg=%s\n", flag, optarg ? optarg : ""); + if (flag == '?' || flag == ':') { + fprintf(stderr, "[pgl_boot] getopt error: flag='?' or ':' -> invalid or missing option value\n"); + } switch (flag) { case 'B': @@ -235,6 +254,7 @@ BootstrapModeMain(int argc, char *argv[], bool check_only) *value; ParseLongOption(optarg, &name, &value); + fprintf(stderr, "[pgl_boot] -c parsed name=%s value=%s\n", name ? name : "", value ? value : ""); if (!value) { if (flag == '-') @@ -249,13 +269,33 @@ BootstrapModeMain(int argc, char *argv[], bool check_only) optarg))); } - SetConfigOption(name, value, PGC_POSTMASTER, PGC_S_ARGV); + /* Wrap SetConfigOption in PG_TRY to capture error details for this GUC */ + PG_TRY(); + { + SetConfigOption(name, value, PGC_POSTMASTER, PGC_S_ARGV); + fprintf(stderr, "[pgl_boot] -c applied %s=%s\n", name ? name : "", value ? value : ""); + } + PG_CATCH(); + { + ErrorData *edata = CopyErrorData(); + FlushErrorState(); + fprintf(stderr, "[pgl_boot] -c SetConfigOption failed for %s=%s: %s", name ? name : "", value ? value : "", edata && edata->message ? edata->message : ""); + if (edata && edata->detail) + fprintf(stderr, " detail=%s", edata->detail); + if (edata && edata->filename) + fprintf(stderr, " file=%s line=%d", edata->filename, edata->lineno); + fputc('\n', stderr); + FreeErrorData(edata); + PG_RE_THROW(); + } + PG_END_TRY(); pfree(name); pfree(value); break; } case 'D': userDoption = pstrdup(optarg); + fprintf(stderr, "[pgl_boot] saw -D %s\n", userDoption); break; case 'd': { @@ -285,7 +325,11 @@ BootstrapModeMain(int argc, char *argv[], bool check_only) default: write_stderr("Try \"%s --help\" for more information.\n", progname); - proc_exit(1); + #if defined(PGL_MOBILE) + return; +#else + proc_exit(1); +#endif break; } } @@ -297,24 +341,46 @@ BootstrapModeMain(int argc, char *argv[], bool check_only) } /* Acquire configuration parameters */ + fprintf(stderr, "[pgl_boot] calling SelectConfigFiles D=%s PGDATA(env)=%s\n", userDoption ? userDoption : "", getenv("PGDATA") ? getenv("PGDATA") : ""); if (!SelectConfigFiles(userDoption, progname)) +#if defined(PGL_MOBILE) + { + fprintf(stderr, "[pgl_boot] SelectConfigFiles failed (D=%s)\n", userDoption ? userDoption : ""); + return; + } +#else proc_exit(1); +#endif + fprintf(stderr, "[pgl_boot] SelectConfigFiles ok DataDir=%s\n", DataDir ? DataDir : ""); /* * Validate we have been given a reasonable-looking DataDir and change * into it */ + fprintf(stderr, "[pgl_boot] before checkDataDir\n"); checkDataDir(); + fprintf(stderr, "[pgl_boot] checkDataDir ok (DataDir=%s)\n", DataDir ? DataDir : ""); + fprintf(stderr, "[pgl_boot] before ChangeToDataDir\n"); ChangeToDataDir(); + { + char cwd[1024]; if (getcwd(cwd, sizeof(cwd))) fprintf(stderr, "[pgl_boot] cwd after ChangeToDataDir=%s\n", cwd); + } + fprintf(stderr, "[pgl_boot] before CreateDataDirLockFile\n"); CreateDataDirLockFile(false); + fprintf(stderr, "[pgl_boot] CreateDataDirLockFile ok\n"); + fprintf(stderr, "[pgl_boot] before SetProcessingMode(BootstrapProcessing)\n"); SetProcessingMode(BootstrapProcessing); IgnoreSystemIndexes = true; + fprintf(stderr, "[pgl_boot] before InitializeMaxBackends\n"); InitializeMaxBackends(); + fprintf(stderr, "[pgl_boot] after InitializeMaxBackends\n"); + fprintf(stderr, "[pgl_boot] before CreateSharedMemoryAndSemaphores\n"); CreateSharedMemoryAndSemaphores(); + fprintf(stderr, "[pgl_boot] after CreateSharedMemoryAndSemaphores\n"); /* * XXX: It might make sense to move this into its own function at some @@ -336,7 +402,9 @@ BootstrapModeMain(int argc, char *argv[], bool check_only) BaseInit(); bootstrap_signals(); + fprintf(stderr, "[pgl_boot] before BootStrapXLOG\n"); BootStrapXLOG(); + fprintf(stderr, "[pgl_boot] after BootStrapXLOG\n"); /* * To ensure that src/common/link-canary.c is linked into the backend, we @@ -353,24 +421,37 @@ BootstrapModeMain(int argc, char *argv[], bool check_only) attrtypes[i] = NULL; Nulls[i] = false; } + fprintf(stderr, "[pgl_boot] after bootstrap-file processing init\n"); + /* * Process bootstrap input. */ StartTransactionCommand(); + fprintf(stderr, "[pgl_boot] after StartTransactionCommand\n"); + boot_yyparse(); + fprintf(stderr, "[pgl_boot] after boot_yyparse\n"); CommitTransactionCommand(); + fprintf(stderr, "[pgl_boot] after CommitTransactionCommand\n"); /* * We should now know about all mapped relations, so it's okay to write * out the initial relation mapping files. */ RelationMapFinishBootstrap(); + fprintf(stderr, "[pgl_boot] after RelationMapFinishBootstrap\n"); /* Clean up and exit */ cleanup(); + fprintf(stderr, "[pgl_boot] after cleanup\n"); #if !defined(__EMSCRIPTEN__) && !defined(__wasi__) +#if defined(PGL_MOBILE) + fprintf(stderr, "[pgl_boot] returning nothing on mobile\n"); + return; +#else proc_exit(0); +#endif #else puts("# 338 cleanup(boot): " __FILE__); return 0; diff --git a/src/backend/libpq/pqcomm.c b/src/backend/libpq/pqcomm.c index ff5277a682852..f35e4db9e4a77 100644 --- a/src/backend/libpq/pqcomm.c +++ b/src/backend/libpq/pqcomm.c @@ -122,7 +122,12 @@ static char *PqSendBuffer; static int PqSendBufferSize; /* Size send buffer */ static size_t PqSendPointer; /* Next index to store a byte in PqSendBuffer */ static size_t PqSendStart; /* Next index to send a byte in PqSendBuffer */ -#if !defined(__EMSCRIPTEN__) && !defined(__wasi__) +#ifdef PGL_MOBILE +static char PqRecvBuffer_static[PQ_RECV_BUFFER_SIZE]; +static char *PqRecvBuffer; +static int PqRecvPointer; /* Next index to read a byte from PqRecvBuffer */ +static int PqRecvLength; /* End of data available in PqRecvBuffer */ +#elif !defined(__EMSCRIPTEN__) && !defined(__wasi__) static char PqRecvBuffer[PQ_RECV_BUFFER_SIZE]; static int PqRecvPointer; /* Next index to read a byte from PqRecvBuffer */ static int PqRecvLength; /* End of data available in PqRecvBuffer */ @@ -136,9 +141,16 @@ volatile FILE* queryfp = NULL; #endif /* pglite specific */ -extern int cma_rsize; +extern volatile int cma_rsize; extern bool sockfiles; +#ifdef PGL_MOBILE +/* Mobile CMA buffer - set by mobile SDK, used by PostgreSQL */ +void* pgl_mobile_cma_buffer_addr = NULL; +int pgl_mobile_cma_buffer_size = 0; +volatile int pgl_mobile_cma_wsize = 0; +#endif + /* * Message status @@ -196,7 +208,7 @@ pq_init(ClientSocket *client_sock) port->sock = client_sock->sock; memcpy(&port->raddr.addr, &client_sock->raddr.addr, client_sock->raddr.salen); port->raddr.salen = client_sock->raddr.salen; -#if !defined(__EMSCRIPTEN__) && !defined(__wasi__) +#if !defined(__EMSCRIPTEN__) && !defined(__wasi__) && !defined(PGL_MOBILE) /* fill in the server (local) address */ port->laddr.salen = sizeof(port->laddr.addr); if (getsockname(port->sock, @@ -296,7 +308,7 @@ pq_init(ClientSocket *client_sock) PqSendPointer = PqSendStart = PqRecvPointer = PqRecvLength = 0; PqCommBusy = false; PqCommReadingMsg = false; -#if !defined(__EMSCRIPTEN__) && !defined(__wasi__) +#if !defined(__EMSCRIPTEN__) && !defined(__wasi__) && !defined(PGL_MOBILE) /* set up process-exit hook to close the socket */ on_proc_exit(socket_close, 0); @@ -326,6 +338,9 @@ pq_init(ClientSocket *client_sock) MyLatch, NULL); AddWaitEventToSet(FeBeWaitSet, WL_POSTMASTER_DEATH, PGINVALID_SOCKET, NULL, NULL); +#elif defined(PGL_MOBILE) + /* Mobile: Initialize PqRecvBuffer pointer to static buffer initially */ + PqRecvBuffer = &PqRecvBuffer_static[0]; #else /* WASM */ /* because we may fill before starting reading message */ PqRecvBuffer = &PqRecvBuffer_static[0]; @@ -928,6 +943,8 @@ pq_recvbuf(void) else PqRecvLength = PqRecvPointer = 0; } + + #if defined(__EMSCRIPTEN__) || defined(__wasi__) if (queryfp && querylen) { int got = fread( PqRecvBuffer, 1, PQ_RECV_BUFFER_SIZE - PqRecvPointer, queryfp); @@ -937,15 +954,18 @@ pq_recvbuf(void) PDEBUG("# 931: could close fp early here " __FILE__); queryfp = NULL; } + if (got>0) return 0; } return EOF; #endif + /* Ensure that we're in blocking mode */ socket_set_nonblocking(false); +#ifndef PGL_MOBILE /* Can fill buffer from PqRecvLength and upwards */ for (;;) { @@ -956,6 +976,10 @@ pq_recvbuf(void) r = secure_read(MyProcPort, PqRecvBuffer + PqRecvLength, PQ_RECV_BUFFER_SIZE - PqRecvLength); +#ifdef PGL_MOBILE + elog(LOG, "pq_recvbuf: secure_read returned %d, errno=%d", r, errno); +#endif + if (r < 0) { if (errno == EINTR) @@ -972,6 +996,9 @@ pq_recvbuf(void) ereport(COMMERROR, (errcode_for_socket_access(), errmsg("could not receive data from client: %m"))); +#ifdef PGL_MOBILE + elog(LOG, "pq_recvbuf: returning EOF due to read error"); +#endif return EOF; } if (r == 0) @@ -980,12 +1007,16 @@ pq_recvbuf(void) * EOF detected. We used to write a log message here, but it's * better to expect the ultimate caller to do that. */ +#ifdef PGL_MOBILE + elog(LOG, "pq_recvbuf: returning EOF due to r=0"); +#endif return EOF; } /* r contains number of bytes read, so just incr length */ PqRecvLength += r; return 0; } +#endif /* !PGL_MOBILE */ } /* -------------------------------- @@ -1044,8 +1075,12 @@ pq_getbyte_if_available(unsigned char *c) *c = PqRecvBuffer[PqRecvPointer++]; return 1; } -#if defined(__EMSCRIPTEN__) || (__wasi__) -puts("# 1044: pq_getbyte_if_available N/I in " __FILE__ ); abort(); +#ifdef PGL_MOBILE + /* Mobile: If no more data in buffer, return 0 (no data available) */ + elog(LOG, "pq_getbyte_if_available: no data available in mobile CMA buffer"); + return 0; +#elif defined(__EMSCRIPTEN__) || (__wasi__) + puts("# 1044: pq_getbyte_if_available N/I in " __FILE__ ); abort(); #else /* Put the socket into non-blocking mode */ socket_set_nonblocking(true); @@ -1165,6 +1200,22 @@ pq_buffer_remaining_data(void) return (PqRecvLength - PqRecvPointer); } +#ifdef PGL_MOBILE +/* -------------------------------- + * pq_reset_buffer_state - reset receive buffer pointers for mobile error recovery + * + * This is called from mobile_comm_reset() to clear stale buffer state + * that could cause infinite retry loops during error recovery. + * -------------------------------- + */ +void +pq_reset_buffer_state(void) +{ + PqRecvPointer = 0; + PqRecvLength = 0; +} +#endif + /* -------------------------------- * pq_startmsgread - begin reading a message from the client. @@ -1203,7 +1254,38 @@ pq_startmsgread(void) ereport(FATAL, (errcode(ERRCODE_PROTOCOL_VIOLATION), errmsg("terminating connection because protocol synchronization was lost"))); -#if defined(__EMSCRIPTEN__) || defined(__wasi__) + + +#ifdef PGL_MOBILE + /* Mobile: Set up CMA buffer pointers using external buffer address */ + if (pgl_mobile_cma_buffer_addr) { + if (cma_rsize > 0) { + /* Normal case: have input data */ + /* Reset pointer when no remaining data OR when starting a new batch */ + if (!pq_buffer_remaining_data() || PqRecvLength != cma_rsize) { + PqRecvPointer = 0; + } + PqRecvLength = cma_rsize; + PqRecvBuffer = (char*)pgl_mobile_cma_buffer_addr; + + PqSendPointer = 0; + if (!PqSendBuffer_save) + PqSendBuffer_save = PqSendBuffer; + PqSendBuffer = (char*)pgl_mobile_cma_buffer_addr + cma_rsize + 2; + PqSendBufferSize = pgl_mobile_cma_buffer_size - cma_rsize - 2; + + elog(LOG, "pq_startmsgread: mobile CMA setup - rsize=%d, buffer_addr=%p, recv_buf=%p, send_buf=%p, send_size=%d", + cma_rsize, pgl_mobile_cma_buffer_addr, PqRecvBuffer, PqSendBuffer, PqSendBufferSize); + elog(LOG, "pq_startmsgread: buffer state - PqRecvPointer=%d, PqRecvLength=%d, remaining=%zd", + PqRecvPointer, PqRecvLength, pq_buffer_remaining_data()); + } else if (PqRecvLength > 0) { + /* No input but have stale buffer state - reset it to prevent infinite retry */ + PqRecvPointer = 0; + PqRecvLength = 0; + elog(LOG, "pq_startmsgread: reset stale buffer state (rsize=0)"); + } + } +#elif defined(__EMSCRIPTEN__) || defined(__wasi__) if (!pq_buffer_remaining_data()) { if (sockfiles) { PqRecvBuffer = &PqRecvBuffer_static[0]; diff --git a/src/backend/port/posix_sema.c b/src/backend/port/posix_sema.c index f7d5e2b2007f2..0e7e5ed8f3e6a 100644 --- a/src/backend/port/posix_sema.c +++ b/src/backend/port/posix_sema.c @@ -220,8 +220,15 @@ PGReserveSemaphores(int maxSemas) * ShmemAlloc() won't be ready yet. (This ordering is necessary when we * are emulating spinlocks with semaphores.) */ - sharedSemas = (PGSemaphore) - ShmemAllocUnlocked(PGSemaphoreShmemSize(maxSemas)); + sharedSemas = (PGSemaphore) ShmemAllocUnlocked(PGSemaphoreShmemSize(maxSemas)); + elog(DEBUG1, "PGReserveSemaphores: maxSemas=%d type=%s", maxSemas, + #ifdef USE_NAMED_POSIX_SEMAPHORES + "named-posix" + #else + "unnamed-posix" + #endif + ); + #endif numSems = 0; diff --git a/src/backend/port/sysv_shmem.c b/src/backend/port/sysv_shmem.c index c11f963ab6f6d..24b9306f8ad6c 100644 --- a/src/backend/port/sysv_shmem.c +++ b/src/backend/port/sysv_shmem.c @@ -22,9 +22,19 @@ #include #include #include +#ifdef __APPLE__ +#include +#endif +#if defined(PGL_MOBILE) +#define MOBILE_NO_SYSV 1 +#endif +#ifndef MOBILE_NO_SYSV #include +#endif #include +#ifndef MOBILE_NO_SYSV #include +#endif #include #include "miscadmin.h" @@ -117,6 +127,8 @@ static IpcMemoryState PGSharedMemoryAttach(IpcMemoryId shmId, * If we fail with a failure code other than collision-with-existing-segment, * print out an error and abort. Other types of errors are not recoverable. */ +#ifndef MOBILE_NO_SYSV + static void * InternalIpcMemoryCreate(IpcMemoryKey memKey, Size size) { @@ -277,11 +289,15 @@ InternalIpcMemoryCreate(IpcMemoryKey memKey, Size size) return memAddress; } +#endif /* MOBILE_NO_SYSV */ + + /****************************************************************************/ /* IpcMemoryDetach(status, shmaddr) removes a shared memory segment */ /* from process' address space */ /* (called as an on_shmem_exit callback, hence funny argument list) */ /****************************************************************************/ +#ifndef MOBILE_NO_SYSV static void IpcMemoryDetach(int status, Datum shmaddr) { @@ -289,11 +305,19 @@ IpcMemoryDetach(int status, Datum shmaddr) if (shmdt((void *) DatumGetPointer(shmaddr)) < 0) elog(LOG, "shmdt(%p) failed: %m", DatumGetPointer(shmaddr)); } +#else +static void +IpcMemoryDetach(int status, Datum shmaddr) +{ + (void)status; (void)shmaddr; /* no-op on mobile */ +} +#endif /****************************************************************************/ /* IpcMemoryDelete(status, shmId) deletes a shared memory segment */ /* (called as an on_shmem_exit callback, hence funny argument list) */ /****************************************************************************/ +#ifndef MOBILE_NO_SYSV static void IpcMemoryDelete(int status, Datum shmId) { @@ -301,6 +325,13 @@ IpcMemoryDelete(int status, Datum shmId) elog(LOG, "shmctl(%d, %d, 0) failed: %m", DatumGetInt32(shmId), IPC_RMID); } +#else +static void +IpcMemoryDelete(int status, Datum shmId) +{ + (void)status; (void)shmId; /* no-op on mobile */ +} +#endif /* * PGSharedMemoryIsInUse @@ -313,6 +344,7 @@ IpcMemoryDelete(int status, Datum shmId) * DataDir. This is an important consideration since accidental matches of * shmem segment IDs are reasonably common. */ +#ifndef MOBILE_NO_SYSV bool PGSharedMemoryIsInUse(unsigned long id1, unsigned long id2) { @@ -335,6 +367,15 @@ PGSharedMemoryIsInUse(unsigned long id1, unsigned long id2) return true; } +#else +bool +PGSharedMemoryIsInUse(unsigned long id1, unsigned long id2) +{ + (void)id1; (void)id2; + return false; +} +#endif + /* * Test for a segment with id shmId; see comment at IpcMemoryState. * @@ -343,6 +384,7 @@ PGSharedMemoryIsInUse(unsigned long id1, unsigned long id2) * * *addr is set to the segment memory address if we attached to it, else NULL. */ +#ifndef MOBILE_NO_SYSV static IpcMemoryState PGSharedMemoryAttach(IpcMemoryId shmId, void *attachAt, @@ -452,6 +494,14 @@ PGSharedMemoryAttach(IpcMemoryId shmId, */ return shmStat.shm_nattch == 0 ? SHMSTATE_UNATTACHED : SHMSTATE_ATTACHED; } +#else +static IpcMemoryState +PGSharedMemoryAttach(IpcMemoryId shmId, void *attachAt, PGShmemHeader **addr) +{ + (void)shmId; (void)attachAt; (void)addr; + return SHMSTATE_ENOENT; +} +#endif /* * Identify the huge page size to use, and compute the related mmap flags. @@ -664,6 +714,10 @@ CreateAnonymousSegment(Size *size) } *size = allocsize; + + /* Mobile diagnostic: log anon mmap success and address */ + fprintf(stderr, "[pgl_boot] shmem: mapped anon segment size=%zu at=%p\n", allocsize, ptr); + return ptr; } @@ -696,6 +750,7 @@ AnonymousShmemDetach(int status, Datum arg) * is to detect and re-use keys that may have been assigned by a crashed * postmaster or backend. */ +#ifndef MOBILE_NO_SYSV PGShmemHeader * PGSharedMemoryCreate(Size size, PGShmemHeader **shim) @@ -767,6 +822,9 @@ PGSharedMemoryCreate(Size size, errmsg("huge pages not supported with the current \"shared_memory_type\" setting"))); /* Room for a header? */ + + report_unix_error:; + Assert(size > MAXALIGN(sizeof(PGShmemHeader))); if (shared_memory_type == SHMEM_TYPE_MMAP) @@ -891,6 +949,10 @@ PGSharedMemoryCreate(Size size, hdr->freeoffset = MAXALIGN(sizeof(PGShmemHeader)); *shim = hdr; + /* Mobile diagnostic: note whether AnonymousShmem was allocated and sysv shim size */ + elog(DEBUG1, "PGSharedMemoryCreate: sysvsize=%zu anon=%s anon_size=%zu", sysvsize, AnonymousShmem ? "yes" : "no", AnonymousShmemSize); + + /* Save info for possible future use */ UsedShmemSegAddr = memAddress; UsedShmemSegID = (unsigned long) NextShmemSegID; @@ -906,6 +968,36 @@ PGSharedMemoryCreate(Size size, memcpy(AnonymousShmem, hdr, sizeof(PGShmemHeader)); return (PGShmemHeader *) AnonymousShmem; } +#else /* MOBILE_NO_SYSV */ + +PGShmemHeader * +PGSharedMemoryCreate(Size size, PGShmemHeader **shim) +{ + /* Mobile (Android/iOS): use anonymous mmap only; no SysV shmem available */ + Assert(size > MAXALIGN(sizeof(PGShmemHeader))); + AnonymousShmem = CreateAnonymousSegment(&size); + AnonymousShmemSize = size; + + /* Initialize the shared memory header in-place */ + PGShmemHeader *hdr = (PGShmemHeader *) AnonymousShmem; + hdr->magic = PGShmemMagic; + hdr->creatorPID = getpid(); + hdr->totalsize = size; + hdr->freeoffset = MAXALIGN(sizeof(PGShmemHeader)); + hdr->dsm_control = 0; + hdr->index = NULL; +#ifndef WIN32 + hdr->device = 0; + hdr->inode = 0; +#endif + if (shim) + *shim = hdr; + + /* Ensure unmap on exit */ + on_shmem_exit(AnonymousShmemDetach, (Datum) 0); + return hdr; +} +#endif /* MOBILE_NO_SYSV */ #ifdef EXEC_BACKEND @@ -999,6 +1091,7 @@ PGSharedMemoryNoReAttach(void) * to get rid of it. * * UsedShmemSegID and UsedShmemSegAddr are implicit parameters to this + * routine, also AnonymousShmem and AnonymousShmemSize. */ void @@ -1006,6 +1099,7 @@ PGSharedMemoryDetach(void) { if (UsedShmemSegAddr != NULL) { +#ifndef MOBILE_NO_SYSV if ((shmdt(UsedShmemSegAddr) < 0) #if defined(EXEC_BACKEND) && defined(__CYGWIN__) /* Work-around for cygipc exec bug */ @@ -1013,7 +1107,13 @@ PGSharedMemoryDetach(void) #endif ) elog(LOG, "shmdt(%p) failed: %m", UsedShmemSegAddr); +#else + /* No SysV detach on mobile */ +#endif UsedShmemSegAddr = NULL; + + + } if (AnonymousShmem != NULL) diff --git a/src/backend/storage/ipc/dsm.c b/src/backend/storage/ipc/dsm.c index c2e33a7e43305..528287b92d118 100644 --- a/src/backend/storage/ipc/dsm.c +++ b/src/backend/storage/ipc/dsm.c @@ -32,6 +32,7 @@ #include #endif #include +#include #include "common/pg_prng.h" #include "lib/ilist.h" @@ -322,9 +323,29 @@ dsm_cleanup_for_mmap(void) DIR *dir; struct dirent *dent; - /* Scan the directory for something with a name of the correct format. */ + /* Ensure the directory exists; if not, create it and return (nothing to clean). */ dir = AllocateDir(PG_DYNSHMEM_DIR); + if (dir == NULL) + { + if (errno == ENOENT) + { + /* First startup: create the directory and skip cleanup. */ + (void) MakePGDirectory(PG_DYNSHMEM_DIR); + return; + } + /* On other errors, just return to avoid aborting bootstrap. */ +#if defined(__APPLE__) && (TARGET_OS_IOS || TARGET_IPHONE_SIMULATOR) + { + int saved_errno = errno; + elog(DEBUG2, "could not open '%s' for cleanup (errno=%d)", PG_DYNSHMEM_DIR, saved_errno); + } +#else + elog(DEBUG2, "could not open '%s' for cleanup (errno=%d)", PG_DYNSHMEM_DIR, errno); +#endif + return; + } + /* Scan the directory for something with a name of the correct format. */ while ((dent = ReadDir(dir, PG_DYNSHMEM_DIR)) != NULL) { if (strncmp(dent->d_name, PG_DYNSHMEM_MMAP_FILE_PREFIX, diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c index 2100150f01cd4..f0855f27d7a20 100644 --- a/src/backend/storage/ipc/ipci.c +++ b/src/backend/storage/ipc/ipci.c @@ -209,40 +209,60 @@ CreateSharedMemoryAndSemaphores(void) size = CalculateShmemSize(&numSemas); elog(DEBUG3, "invoking IpcMemoryCreate(size=%zu)", size); + /* Extra diagnostics on mobile: log computed sizes and GUCs */ + { + const char *hp = GetConfigOption("huge_pages", false, false); + const char *hps = GetConfigOption("huge_pages_status", false, false); + const char *smt = GetConfigOption("shared_memory_type", false, false); + elog(DEBUG1, "shmem pre-create: size=%zu semaphores=%d huge_pages=%s huge_pages_status=%s shared_memory_type=%s", + size, numSemas, hp ? hp : "?", hps ? hps : "?", smt ? smt : "?"); + } + + /* * Create the shmem segment */ seghdr = PGSharedMemoryCreate(size, &shim); - /* - * Make sure that huge pages are never reported as "unknown" while the - * server is running. - */ - Assert(strcmp("unknown", - GetConfigOption("huge_pages_status", false, false)) != 0); + elog(DEBUG1, "PGSharedMemoryCreate returned header=%p, shim=%p", seghdr, shim); + elog(DEBUG1, "huge_pages_status after create=%s", GetConfigOption("huge_pages_status", false, false)); + elog(DEBUG1, "calling InitShmemAccess"); + InitShmemAccess(seghdr); + elog(DEBUG1, "InitShmemAccess ok"); + #ifdef HAVE_SPINLOCKS + elog(DEBUG1, "spinlocks: native (HAVE_SPINLOCKS)"); + #else + elog(DEBUG1, "spinlocks: emulated (no HAVE_SPINLOCKS)"); + #endif - InitShmemAccess(seghdr); - /* - * Create semaphores - */ + /* Make sure that huge pages are never reported as "unknown" while the server is running. */ + Assert(strcmp("unknown", GetConfigOption("huge_pages_status", false, false)) != 0); + + /* Create semaphores */ + elog(DEBUG1, "calling PGReserveSemaphores(%d)", numSemas); PGReserveSemaphores(numSemas); + elog(DEBUG1, "returned from PGReserveSemaphores"); - /* - * If spinlocks are disabled, initialize emulation layer (which depends on - * semaphores, so the order is important here). - */ -#ifndef HAVE_SPINLOCKS + /* If spinlocks are disabled, initialize emulation layer (depends on semaphores) */ + #ifndef HAVE_SPINLOCKS + elog(DEBUG1, "calling SpinlockSemaInit"); SpinlockSemaInit(); -#endif + elog(DEBUG1, "SpinlockSemaInit ok"); + #endif + + + elog(DEBUG1, "calling InitShmemAllocation"); + InitShmemAllocation(); + elog(DEBUG1, "InitShmemAllocation ok"); + + elog(DEBUG1, "calling CreateOrAttachShmemStructs"); + CreateOrAttachShmemStructs(); + elog(DEBUG1, "CreateOrAttachShmemStructs ok"); + + - /* - * Set up shared memory allocation mechanism - */ - InitShmemAllocation(); - /* Initialize subsystems */ - CreateOrAttachShmemStructs(); #ifdef EXEC_BACKEND diff --git a/src/backend/storage/ipc/shmem.c b/src/backend/storage/ipc/shmem.c index 6d5f08398641c..a61cb171094d4 100644 --- a/src/backend/storage/ipc/shmem.c +++ b/src/backend/storage/ipc/shmem.c @@ -123,9 +123,12 @@ InitShmemAllocation(void) * Initialize the spinlock used by ShmemAlloc. We must use * ShmemAllocUnlocked, since obviously ShmemAlloc can't be called yet. */ + elog(DEBUG1, "InitShmemAllocation: pre-alloc totalsize=%zu freeoffset=%zu", shmhdr->totalsize, shmhdr->freeoffset); ShmemLock = (slock_t *) ShmemAllocUnlocked(sizeof(slock_t)); + elog(DEBUG1, "InitShmemAllocation: post-alloc freeoffset=%zu ShmemLock=%p", shmhdr->freeoffset, (void*) ShmemLock); SpinLockInit(ShmemLock); + elog(DEBUG1, "InitShmemAllocation: SpinLockInit done"); /* * Allocations after this point should go through ShmemAlloc, which @@ -249,8 +252,10 @@ ShmemAllocUnlocked(Size size) Assert(ShmemSegHdr != NULL); newStart = ShmemSegHdr->freeoffset; + elog(DEBUG1, "ShmemAllocUnlocked: req=%zu start=%zu totalsize=%zu", size, newStart, ShmemSegHdr->totalsize); newFree = newStart + size; + elog(DEBUG1, "ShmemAllocUnlocked: newFree=%zu", newFree); if (newFree > ShmemSegHdr->totalsize) ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 72481df9bd16a..c126970992f05 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -107,7 +107,7 @@ int client_connection_check_interval = 0; /* flags for non-system relation kinds to restrict use */ int restrict_nonsystem_relation_kind; -#if (defined(__EMSCRIPTEN__) || defined(__wasi__)) +#if (defined(__EMSCRIPTEN__) || defined(__wasi__) || defined(PGL_MOBILE)) #if !defined(PGL_MAIN) volatile int cma_rsize = 0; volatile bool sockfiles = false; @@ -120,6 +120,7 @@ int SOCKET_DATA = 0; + /* ---------------- * private typedefs etc * ---------------- @@ -384,7 +385,13 @@ SocketBackend(StringInfo inBuf) */ HOLD_CANCEL_INTERRUPTS(); pq_startmsgread(); +#ifdef PGL_MOBILE + elog(LOG, "SocketBackend: after pq_startmsgread()"); +#endif qtype = pq_getbyte(); +#ifdef PGL_MOBILE + elog(LOG, "SocketBackend: pq_getbyte() returned %d ('%c')", qtype, qtype > 0 && qtype < 127 ? qtype : '?'); +#endif if (qtype == EOF) /* frontend disconnected */ { @@ -487,8 +494,19 @@ SocketBackend(StringInfo inBuf) * after the type code; we can read the message contents independently of * the type. */ +#ifdef PGL_MOBILE + elog(LOG, "SocketBackend: calling pq_getmessage() with maxmsglen=%d", maxmsglen); +#endif if (pq_getmessage(inBuf, maxmsglen)) + { +#ifdef PGL_MOBILE + elog(LOG, "SocketBackend: pq_getmessage() failed, returning EOF"); +#endif return EOF; /* suitable message already logged */ + } +#ifdef PGL_MOBILE + elog(LOG, "SocketBackend: pq_getmessage() succeeded, inBuf->len=%d", inBuf->len); +#endif RESUME_CANCEL_INTERRUPTS(); return qtype; diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 2699e31c1e02c..71ff9bb9e53bf 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -321,7 +321,7 @@ static SPIPlanPtr plan_getviewrule = NULL; static const char *const query_getviewrule = "SELECT * FROM pg_catalog.pg_rewrite WHERE ev_class = $1 AND rulename = $2"; /* GUC parameters */ -#if !defined(__EMSCRIPTEN__) && !defined(__wasi__) +#if !defined(__EMSCRIPTEN__) && !defined(__wasi__) && !defined(PGL_MOBILE) bool quote_all_identifiers = false; #endif diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c index dc94e69b2ef95..7467b7809115c 100644 --- a/src/backend/utils/init/postinit.c +++ b/src/backend/utils/init/postinit.c @@ -798,7 +798,9 @@ puts("# 766:"__FILE__); */ CreateAuxProcessResourceOwner(); + fprintf(stderr, "# before StartupXLOG:%s\n", __FILE__); StartupXLOG(); + fprintf(stderr, "# after StartupXLOG:%s\n", __FILE__); /* Release (and warn about) any buffer pins leaked in StartupXLOG */ ReleaseAuxProcessResources(true); /* Reset CurrentResourceOwner to nothing for the moment */ @@ -810,6 +812,8 @@ puts("# 766:"__FILE__); */ before_shmem_exit(pgstat_before_server_shutdown, 0); before_shmem_exit(ShutdownXLOG, 0); + fprintf(stderr, "# after before_shmem_exit\n"); + } /* @@ -844,6 +848,7 @@ puts("# 766:"__FILE__); * AbortTransaction call to clean up. */ before_shmem_exit(ShutdownPostgres, 0); + fprintf(stderr, "# after before_shmem_exit ShutdownPostgres\n"); /* The autovacuum launcher is done here */ if (AmAutoVacuumLauncherProcess()) @@ -934,6 +939,8 @@ if (!strcmp( username , WASM_USERNAME )) { else { /* normal multiuser case */ + fprintf(stderr, "# normal multiuser case\n"); + Assert(MyProcPort != NULL); PerformAuthentication(MyProcPort); InitializeSessionUserId(username, useroid, false); @@ -1207,6 +1214,7 @@ if (!strcmp( username , WASM_USERNAME )) { /* set up ACL framework (so CheckMyDatabase can check permissions) */ initialize_acl(); + fprintf(stderr, "# after initialize_acl\n"); /* * Re-read the pg_database row for our database, check permissions and set @@ -1218,6 +1226,7 @@ if (!strcmp( username , WASM_USERNAME )) { CheckMyDatabase(dbname, am_superuser, (flags & INIT_PG_OVERRIDE_ALLOW_CONNS) != 0); + /* * Now process any command-line switches and any additional GUC variable * settings passed in the startup packet. We couldn't do this before @@ -1226,13 +1235,15 @@ if (!strcmp( username , WASM_USERNAME )) { if (MyProcPort != NULL) process_startup_options(MyProcPort, am_superuser); + fprintf(stderr, "# after process_startup_options\n"); + /* Process pg_db_role_setting options */ process_settings(MyDatabaseId, GetSessionUserId()); - + fprintf(stderr, "# after process_settings\n"); /* Apply PostAuthDelay as soon as we've read all options */ if (PostAuthDelay > 0) pg_usleep(PostAuthDelay * 1000000L); - + fprintf(stderr, "# after postauthdelay\n"); /* * Initialize various default states that can't be set up until we've * selected the active user and gotten the right GUC settings. @@ -1240,13 +1251,15 @@ if (!strcmp( username , WASM_USERNAME )) { /* set default namespace search path */ InitializeSearchPath(); - + fprintf(stderr, "# after initialize_search_path\n"); /* initialize client encoding */ InitializeClientEncoding(); + fprintf(stderr, "# after initialize_client_encoding\n"); /* Initialize this backend's session state. */ InitializeSession(); + fprintf(stderr, "# after initialize_session\n"); /* * If this is an interactive session, load any libraries that should be * preloaded at backend start. Since those are determined by GUCs, this @@ -1257,13 +1270,16 @@ if (!strcmp( username , WASM_USERNAME )) { if ((flags & INIT_PG_LOAD_SESSION_LIBS) != 0) process_session_preload_libraries(); + fprintf(stderr, "# after process_session_preload_libraries\n"); /* report this backend in the PgBackendStatus array */ if (!bootstrap) pgstat_bestart(); + fprintf(stderr, "# after pgstat_bestart\n"); /* close the transaction we started above */ if (!bootstrap) CommitTransactionCommand(); + fprintf(stderr, "# after commit_transaction_command, end of InitPostgres\n"); } /* ========================================================================*/ diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 97c44df762fd6..c73ddcc37dfe5 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -1790,6 +1790,7 @@ SelectConfigFiles(const char *userDoption, const char *progname) bool fname_is_malloced; struct stat stat_buf; struct config_string *data_directory_rec; + fprintf(stderr, "[pgl_boot] SCF enter D=%s PGDATA(env)=%s\n", userDoption ? userDoption : "", getenv("PGDATA") ? getenv("PGDATA") : ""); /* configdir is -D option, or $PGDATA if no -D */ if (userDoption) @@ -1799,6 +1800,7 @@ SelectConfigFiles(const char *userDoption, const char *progname) if (configdir && stat(configdir, &stat_buf) != 0) { + fprintf(stderr, "[pgl_boot] SCF stat(%s) failed errno=%d\n", configdir, errno); write_stderr("%s: could not access directory \"%s\": %m\n", progname, configdir); @@ -1806,6 +1808,10 @@ SelectConfigFiles(const char *userDoption, const char *progname) write_stderr("Run initdb or pg_basebackup to initialize a PostgreSQL data directory.\n"); return false; } + else if (configdir) + { + fprintf(stderr, "[pgl_boot] SCF stat(%s) ok\n", configdir); + } /* * Find the configuration file: if config_file was specified on the @@ -1833,28 +1839,33 @@ SelectConfigFiles(const char *userDoption, const char *progname) progname); return false; } + fprintf(stderr, "[pgl_boot] SCF fname=%s (malloced=%d)\n", fname ? fname : "", fname_is_malloced ? 1 : 0); /* * Set the ConfigFileName GUC variable to its final value, ensuring that * it can't be overridden later. */ + fprintf(stderr, "[pgl_boot] SCF SetConfigOption(config_file=...)\n"); SetConfigOption("config_file", fname, PGC_POSTMASTER, PGC_S_OVERRIDE); + fprintf(stderr, "[pgl_boot] SCF SetConfigOption done\n"); if (fname_is_malloced) free(fname); else guc_free(fname); - /* * Now read the config file for the first time. */ + fprintf(stderr, "[pgl_boot] SCF stat(ConfigFileName=%s) enter\n", ConfigFileName ? ConfigFileName : ""); if (stat(ConfigFileName, &stat_buf) != 0) { + fprintf(stderr, "[pgl_boot] SCF stat(ConfigFileName) failed errno=%d\n", errno); write_stderr("%s: could not access the server configuration file \"%s\": %m\n", progname, ConfigFileName); free(configdir); return false; } + fprintf(stderr, "[pgl_boot] SCF stat(ConfigFileName) ok\n"); /* * Read the configuration file for the first time. This time only the @@ -1870,8 +1881,11 @@ SelectConfigFiles(const char *userDoption, const char *progname) * Note: SetDataDir will copy and absolute-ize its argument, so we don't * have to. */ + fprintf(stderr, "[pgl_boot] SCF before find_option(data_directory)\n"); data_directory_rec = (struct config_string *) find_option("data_directory", false, false, PANIC); + fprintf(stderr, "[pgl_boot] SCF find_option ok, current=%s\n", + *data_directory_rec->variable ? *data_directory_rec->variable : ""); if (*data_directory_rec->variable) SetDataDir(*data_directory_rec->variable); else if (configdir) @@ -1885,6 +1899,7 @@ SelectConfigFiles(const char *userDoption, const char *progname) progname, ConfigFileName); return false; } + fprintf(stderr, "[pgl_boot] SCF SetDataDir done DataDir=%s\n", DataDir ? DataDir : ""); /* * Reflect the final DataDir value back into the data_directory GUC var. @@ -1894,7 +1909,10 @@ SelectConfigFiles(const char *userDoption, const char *progname) * chdir to DataDir, EXEC_BACKEND can read the config file without knowing * DataDir in advance.) */ + fprintf(stderr, "[pgl_boot] SCF SetConfigOption(data_directory=DataDir)\n"); SetConfigOption("data_directory", DataDir, PGC_POSTMASTER, PGC_S_OVERRIDE); + fprintf(stderr, "[pgl_boot] SCF SetConfigOption(data_directory) done\n"); + /* * Now read the config file a second time, allowing any settings in the @@ -1911,11 +1929,14 @@ SelectConfigFiles(const char *userDoption, const char *progname) * when InitializeGUCOptions runs, so the bootstrap default value cannot * be the real desired default. */ + fprintf(stderr, "[pgl_boot] SCF before pg_timezone_abbrev_initialize\n"); pg_timezone_abbrev_initialize(); + fprintf(stderr, "[pgl_boot] SCF after pg_timezone_abbrev_initialize\n"); /* * Figure out where pg_hba.conf is, and make sure the path is absolute. */ + fprintf(stderr, "[pgl_boot] SCF before resolving hba_file\n"); if (HbaFileName) { fname = make_absolute_path(HbaFileName); @@ -1937,7 +1958,9 @@ SelectConfigFiles(const char *userDoption, const char *progname) progname, ConfigFileName); return false; } + fprintf(stderr, "[pgl_boot] SCF hba_file=%s\n", fname ? fname : ""); SetConfigOption("hba_file", fname, PGC_POSTMASTER, PGC_S_OVERRIDE); + fprintf(stderr, "[pgl_boot] SCF hba_file SetConfigOption done\n"); if (fname_is_malloced) free(fname); @@ -1947,6 +1970,7 @@ SelectConfigFiles(const char *userDoption, const char *progname) /* * Likewise for pg_ident.conf. */ + fprintf(stderr, "[pgl_boot] SCF before resolving ident_file\n"); if (IdentFileName) { fname = make_absolute_path(IdentFileName); @@ -1968,7 +1992,9 @@ SelectConfigFiles(const char *userDoption, const char *progname) progname, ConfigFileName); return false; } + fprintf(stderr, "[pgl_boot] SCF ident_file=%s\n", fname ? fname : ""); SetConfigOption("ident_file", fname, PGC_POSTMASTER, PGC_S_OVERRIDE); + fprintf(stderr, "[pgl_boot] SCF ident_file SetConfigOption done\n"); if (fname_is_malloced) free(fname); diff --git a/src/backend/utils/misc/timeout.c b/src/backend/utils/misc/timeout.c index 980a7de8ebf8d..e5fe9bd3f468d 100644 --- a/src/backend/utils/misc/timeout.c +++ b/src/backend/utils/misc/timeout.c @@ -116,7 +116,7 @@ static void insert_timeout(TimeoutId id, int index) { int i; -#if defined(__EMSCRIPTEN__) || defined(__wasi__) +#if defined(__EMSCRIPTEN__) || defined(__wasi__) || defined(PGL_MOBILE) if (!insert_timeout_warned) //(index<0) { insert_timeout_warned = true; @@ -220,7 +220,7 @@ enable_timeout(TimeoutId id, TimestampTz now, TimestampTz fin_time, static void schedule_alarm(TimestampTz now) { -#if defined(__wasi__) +#if defined(__wasi__) || defined(PGL_MOBILE) puts("# 224: schedule_alarm(TimestampTz now)"); (void)signal_due_at; #else diff --git a/src/backend/utils/misc/tzparser.c b/src/backend/utils/misc/tzparser.c index 21fd866d6d639..d75ee05619623 100644 --- a/src/backend/utils/misc/tzparser.c +++ b/src/backend/utils/misc/tzparser.c @@ -31,6 +31,9 @@ #include "utils/memutils.h" #include "utils/tzparser.h" +#include +#include + #define WHITESPACE " \t\n\r" @@ -319,6 +322,7 @@ ParseTzFile(const char *filename, int depth, get_share_path(my_exec_path, share_path); snprintf(file_path, sizeof(file_path), "%s/timezonesets/%s", share_path, filename); + fprintf(stderr, "[pgl_boot] tzparser: trying file %s\n", file_path); tzFile = AllocateFile(file_path, "r"); if (!tzFile) { @@ -329,15 +333,21 @@ ParseTzFile(const char *filename, int depth, * place we notice a problem during postmaster startup. */ int save_errno = errno; + fprintf(stderr, "[pgl_boot] tzparser: open failed errno=%d (%s)\n", save_errno, strerror(save_errno)); DIR *tzdir; snprintf(file_path, sizeof(file_path), "%s/timezonesets", share_path); + fprintf(stderr, "[pgl_boot] tzparser: trying dir %s\n", file_path); tzdir = AllocateDir(file_path); - if (tzdir == NULL) + if (tzdir == NULL) { + int dir_errno = errno; + fprintf(stderr, "[pgl_boot] tzparser: open dir failed errno=%d (%s)\n", dir_errno, strerror(dir_errno)); + { GUC_check_errmsg("could not open directory \"%s\": %m", file_path); + } GUC_check_errhint("This may indicate an incomplete PostgreSQL installation, or that the file \"%s\" has been moved away from its proper location.", my_exec_path); return -1; diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c index 9aa9b48a79325..bab4387459617 100644 --- a/src/bin/initdb/initdb.c +++ b/src/bin/initdb/initdb.c @@ -63,6 +63,9 @@ #ifdef HAVE_SHM_OPEN #include "sys/mman.h" #endif +#ifdef PGL_MOBILE +#undef HAVE_SHM_OPEN +#endif #include "access/xlog_internal.h" #include "catalog/pg_authid_d.h" @@ -82,6 +85,14 @@ #include "mb/pg_wchar.h" #include "miscadmin.h" +#ifdef PGL_MOBILE +#include "../../mobile-build/wasm_common_mobile.h" +#endif + +#if defined(__APPLE__) && (TARGET_OS_IOS || TARGET_IPHONE_SIMULATOR) +/* For iOS builds where system() is unavailable, return failure */ +#define system(cmd) (-1) +#endif /* Ideally this would be in a .h file, but it hardly seems worth the trouble */ extern const char *select_default_timezone(const char *share_path); @@ -815,7 +826,7 @@ cleanup_directories_atexit(void) static char * get_id(void) { -#if !defined(__EMSCRIPTEN__) && !defined(__wasi__) +#if !defined(__EMSCRIPTEN__) && !defined(__wasi__) && !defined(PGL_MOBILE) const char *username; #ifndef WIN32 @@ -831,9 +842,10 @@ get_id(void) return pg_strdup(username); #else + /* WASM and Mobile path - uses environment variable */ setenv("PGUSER", WASM_USERNAME, 0); return pg_strdup(getenv("PGUSER")); -#endif /* wasm */ +#endif /* wasm and mobile */ } static char * @@ -1028,19 +1040,20 @@ static void write_version_file(const char *extrapath) { FILE *version_file; - char *path; + char path[MAXPGPATH]; if (extrapath == NULL) - path = psprintf("%s/PG_VERSION", pg_data); + snprintf(path, sizeof(path), "%s/PG_VERSION", pg_data); else - path = psprintf("%s/%s/PG_VERSION", pg_data, extrapath); + snprintf(path, sizeof(path), "%s/%s/PG_VERSION", pg_data, extrapath); + fprintf(stderr, "[initdb] write_version_file path=%s\n", path); if ((version_file = fopen(path, PG_BINARY_W)) == NULL) pg_fatal("could not open file \"%s\" for writing: %m", path); if (fprintf(version_file, "%s\n", PG_MAJORVERSION) < 0 || fclose(version_file)) pg_fatal("could not write file \"%s\": %m", path); - free(path); + fprintf(stderr, "[initdb] write_version_file done\n"); } /* @@ -1051,15 +1064,16 @@ static void set_null_conf(void) { FILE *conf_file; - char *path; + char path[MAXPGPATH]; - path = psprintf("%s/postgresql.conf", pg_data); + snprintf(path, sizeof(path), "%s/postgresql.conf", pg_data); + fprintf(stderr, "[initdb] set_null_conf path=%s\n", path); conf_file = fopen(path, PG_BINARY_W); if (conf_file == NULL) pg_fatal("could not open file \"%s\" for writing: %m", path); if (fclose(conf_file)) pg_fatal("could not write file \"%s\": %m", path); - free(path); + fprintf(stderr, "[initdb] set_null_conf done\n"); } /* @@ -1082,7 +1096,7 @@ choose_dsm_implementation(void) #if defined(__wasi__) || defined(__EMSCRIPTEN__) return "posix"; #endif -#if defined(HAVE_SHM_OPEN) && !defined(__sun__) +#if defined(HAVE_SHM_OPEN) && !defined(__sun__) && !defined(PGL_MOBILE) int ntries = 10; pg_prng_state prng_state; @@ -1124,6 +1138,34 @@ choose_dsm_implementation(void) static void test_config_settings(void) { +#if defined(__APPLE__) && (TARGET_OS_IOS || TARGET_IPHONE_SIMULATOR) + /* + * iOS doesn't allow system() calls, so skip config testing and use + * hardcoded values that match what mobile bootstrap uses. + */ + printf(_("selecting dynamic shared memory implementation ... ")); + fflush(stdout); + dynamic_shared_memory_type = "sysv"; // Safe fallback for mobile + printf("%s\n", dynamic_shared_memory_type); + + printf(_("selecting default \"max_connections\" ... ")); + fflush(stdout); + n_connections = 1; // Match pg_main.c mobile bootstrap + printf("%d\n", n_connections); + + printf(_("selecting default \"shared_buffers\" ... ")); + fflush(stdout); + n_buffers = 16; // Match pg_main.c mobile bootstrap (16 * 8kB = 128kB) + printf("%dkB\n", n_buffers * (BLCKSZ / 1024)); + + printf(_("selecting default time zone ... ")); + fflush(stdout); + default_timezone = select_default_timezone(share_path); + printf("%s\n", default_timezone ? default_timezone : "GMT"); + + return; // Skip the rest of the function +#endif + /* * This macro defines the minimum shared_buffers we want for a given * max_connections value. The arrays show the settings to try. @@ -1679,10 +1721,13 @@ get_su_pwd(void) * for now. */ FILE *pwf = fopen(pwfilename, "r"); + fprintf(stderr, "[initdb] trying pwfile=%s\n", pwfilename); - if (!pwf) + if (!pwf) { + fprintf(stderr, "[initdb] fopen failed for pwfile=%s errno=%d\n", pwfilename, errno); pg_fatal("could not open file \"%s\" for reading: %m", pwfilename); + } pwd1 = pg_get_line(pwf, NULL); if (!pwd1) { @@ -2947,10 +2992,10 @@ create_data_directory(void) void create_xlog_or_symlink(void) { - char *subdirloc; + char subdirloc[MAXPGPATH]; /* form name of the place for the subdirectory or symlink */ - subdirloc = psprintf("%s/pg_wal", pg_data); + snprintf(subdirloc, sizeof(subdirloc), "%s/pg_wal", pg_data); if (xlog_dir) { @@ -3022,8 +3067,6 @@ create_xlog_or_symlink(void) pg_fatal("could not create directory \"%s\": %m", subdirloc); } - - free(subdirloc); } @@ -3058,8 +3101,10 @@ initialize_data_directory(void) umask(pg_mode_mask); create_data_directory(); + fprintf(stderr, "[initdb] create_data_directory done\n"); create_xlog_or_symlink(); + fprintf(stderr, "[initdb] create_xlog_or_symlink done\n"); /* Create required subdirectories (other than pg_wal) */ printf(_("creating subdirectories ... ")); @@ -3067,9 +3112,9 @@ initialize_data_directory(void) for (i = 0; i < lengthof(subdirs); i++) { - char *path; + char path[MAXPGPATH]; - path = psprintf("%s/%s", pg_data, subdirs[i]); + snprintf(path, sizeof(path), "%s/%s", pg_data, subdirs[i]); /* * The parent directory already exists, so we only need mkdir() not @@ -3077,29 +3122,46 @@ initialize_data_directory(void) */ if (mkdir(path, pg_dir_create_mode) < 0) pg_fatal("could not create directory \"%s\": %m", path); - - free(path); } check_ok(); + fprintf(stderr, "[initdb] subdirectories created\n"); /* Top level PG_VERSION is checked by bootstrapper, so make it first */ + fprintf(stderr, "[initdb] before write_version_file(NULL)\n"); write_version_file(NULL); + fprintf(stderr, "[initdb] after write_version_file(NULL)\n"); /* Select suitable configuration settings */ + fprintf(stderr, "[initdb] before set_null_conf\n"); set_null_conf(); + fprintf(stderr, "[initdb] after set_null_conf\n"); + fprintf(stderr, "[initdb] before test_config_settings\n"); + fprintf(stderr, "[initdb] About to call test_config_settings()\n"); test_config_settings(); + fprintf(stderr, "[initdb] after test_config_settings\n"); + fprintf(stderr, "[initdb] test_config_settings() completed\n"); /* Now create all the text config files */ + fprintf(stderr, "[initdb] before setup_config\n"); + fprintf(stderr, "[initdb] About to call setup_config()\n"); setup_config(); + fprintf(stderr, "[initdb] after setup_config\n"); + fprintf(stderr, "[initdb] setup_config() completed\n"); /* Bootstrap template1 */ + fprintf(stderr, "[initdb] before bootstrap_template1\n"); + fprintf(stderr, "[initdb] About to call bootstrap_template1()\n"); bootstrap_template1(); + fprintf(stderr, "[initdb] after bootstrap_template1\n"); + fprintf(stderr, "[initdb] bootstrap_template1() completed successfully\n"); /* * Make the per-database PG_VERSION for template1 only after init'ing it */ + fprintf(stderr, "[initdb] before write_version_file(base/1)\n"); write_version_file("base/1"); + fprintf(stderr, "[initdb] after write_version_file(base/1)\n"); /* * Create the stuff we don't need to use bootstrap mode for, using a @@ -3111,16 +3173,21 @@ initialize_data_directory(void) initPQExpBuffer(&cmd); printfPQExpBuffer(&cmd, "\"%s\" %s %s template1 >%s", backend_exec, backend_options, extra_options, DEVNULL); - + fprintf(stderr, "[initdb] PG_CMD_OPEN: %s\n", cmd.data); PG_CMD_OPEN(cmd.data); + fprintf(stderr, "[initdb] PG_CMD_OPEN done\n"); setup_auth(cmdfd); + fprintf(stderr, "[initdb] setup_auth done\n"); setup_run_file(cmdfd, system_constraints_file); + fprintf(stderr, "[initdb] system_constraints_file done\n"); setup_run_file(cmdfd, system_functions_file); + fprintf(stderr, "[initdb] system_functions_file done\n"); setup_depend(cmdfd); + fprintf(stderr, "[initdb] setup_depend done\n"); /* * Note that no objects created after setup_depend() will be "pinned". @@ -3128,29 +3195,40 @@ initialize_data_directory(void) */ setup_run_file(cmdfd, system_views_file); + fprintf(stderr, "[initdb] system_views_file done\n"); setup_description(cmdfd); + fprintf(stderr, "[initdb] setup_description done\n"); setup_collation(cmdfd); + fprintf(stderr, "[initdb] setup_collation done\n"); setup_run_file(cmdfd, dictionary_file); + fprintf(stderr, "[initdb] dictionary_file done\n"); setup_privileges(cmdfd); + fprintf(stderr, "[initdb] setup_privileges done\n"); setup_schema(cmdfd); + fprintf(stderr, "[initdb] setup_schema done\n"); load_plpgsql(cmdfd); + fprintf(stderr, "[initdb] load_plpgsql done\n"); vacuum_db(cmdfd); + fprintf(stderr, "[initdb] vacuum_db done\n"); make_template0(cmdfd); + fprintf(stderr, "[initdb] make_template0 done\n"); make_postgres(cmdfd); + fprintf(stderr, "[initdb] make_postgres done\n"); PG_CMD_CLOSE(); termPQExpBuffer(&cmd); check_ok(); + fprintf(stderr, "[initdb] initialize_data_directory complete\n"); } /* pglite entry point */ @@ -3165,6 +3243,7 @@ void strconcat(char*p, const char *head, const char *tail); int pgl_initdb_main() { + fprintf(stderr, "[pgl_initdb_main] ENTRY: function called\n"); char *pwfile = NULL; char *pgdata = NULL; @@ -3175,6 +3254,8 @@ pgl_initdb_main() { strconcat(tmpstr, "--pwfile=", PREFIX); pgdata = strcat_alloc("--pgdata=", PGDATA); + fprintf(stderr, "[pgl_initdb_main] pwfile=%s pgdata=%s\n", pwfile, pgdata); + char *argv[] = { strcat_alloc(PREFIX,"/bin/initdb"), // "--no-clean", @@ -3191,6 +3272,8 @@ pgl_initdb_main() { int argc = sizeof(argv) / sizeof(char*) - 1; + fprintf(stderr, "[pgl_initdb_main] argc=%d progname=%s\n", argc, argv[0]); + #else int @@ -3508,8 +3591,11 @@ PDEBUG("# 3472:"__FILE__ "#TODO: atexit(cleanup_directories_atexit)"); get_restricted_token(); setup_pgdata(); + fprintf(stderr, "[initdb] after setup_pgdata: PGDATA=%s\n", pg_data); setup_bin_paths(argv[0]); + fprintf(stderr, "[initdb] after setup_bin_paths: backend_exec=%s\n", backend_exec); effective_user = get_id(); + fprintf(stderr, "[initdb] effective_user=%s username(opt)=%s\n", effective_user, username?username:"(null)"); if (!username) username = effective_user; @@ -3536,22 +3622,30 @@ PDEBUG("# 3472:"__FILE__ "#TODO: atexit(cleanup_directories_atexit)"); else printf(_("Data page checksums are disabled.\n")); - if (pwprompt || pwfilename) + if (pwprompt || pwfilename) { + fprintf(stderr, "[initdb] get_su_pwd enter: pwprompt=%d pwfilename=%s\n", (int)pwprompt, pwfilename?pwfilename:"(null)"); get_su_pwd(); + fprintf(stderr, "[initdb] get_su_pwd exit\n"); + } printf("\n"); puts("# 3527:" __FILE__); + fprintf(stderr, "[initdb] initializing data directory at %s\n", pg_data); initialize_data_directory(); + fprintf(stderr, "[initdb] initialize_data_directory done\n"); if (do_sync) { fputs(_("syncing data to disk ... "), stdout); fflush(stdout); + fprintf(stderr, "[initdb] syncing data\n"); sync_pgdata(pg_data, PG_VERSION_NUM, sync_method); check_ok(); } - else + else { printf(_("\nSync to disk skipped.\nThe data directory might become corrupt if the operating system crashes.\n")); + fprintf(stderr, "[initdb] do_sync=false\n"); + } if (authwarning) { @@ -3597,5 +3691,6 @@ puts("# 3527:" __FILE__); } success = true; + fprintf(stderr, "[pgl_initdb_main] SUCCESS: function completing, returning 0\n"); return 0; } diff --git a/src/bin/pg_basebackup/pg_createsubscriber.c b/src/bin/pg_basebackup/pg_createsubscriber.c index 677c0cd0843bf..69c865e60e752 100644 --- a/src/bin/pg_basebackup/pg_createsubscriber.c +++ b/src/bin/pg_basebackup/pg_createsubscriber.c @@ -29,6 +29,11 @@ #include "fe_utils/string_utils.h" #include "getopt_long.h" +#if defined(__APPLE__) && (TARGET_OS_IOS || TARGET_IPHONE_SIMULATOR) +/* For iOS builds where system() is unavailable, return failure */ +#define system(cmd) (-1) +#endif + #define DEFAULT_SUB_PORT "50432" /* Command-line options */ diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c index ab34be3fa779e..09a3b6805195e 100644 --- a/src/bin/pg_ctl/pg_ctl.c +++ b/src/bin/pg_ctl/pg_ctl.c @@ -27,6 +27,11 @@ #include "common/logging.h" #include "common/string.h" #include "getopt_long.h" + +#if defined(__APPLE__) && (TARGET_OS_IOS || TARGET_IPHONE_SIMULATOR) +/* For iOS builds where system() is unavailable, return failure */ +#define system(cmd) (-1) +#endif #include "utils/pidfile.h" #ifdef WIN32 /* on Unix, we don't need libpq */ diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c index 27bdf2baf02b4..c871c3faab255 100644 --- a/src/bin/pg_dump/pg_dumpall.c +++ b/src/bin/pg_dump/pg_dumpall.c @@ -34,6 +34,11 @@ static bool quote_all_identifiers; #include "fe_utils/string_utils.h" #include "filter.h" #include "getopt_long.h" + +#if defined(__APPLE__) && (TARGET_OS_IOS || TARGET_IPHONE_SIMULATOR) +/* For iOS builds where system() is unavailable, return failure */ +#define system(cmd) (-1) +#endif #include "pg_backup.h" /* version string we expect back from pg_dump */ diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c index 53eb49abdeaf6..9e2a241c71d82 100644 --- a/src/bin/pg_rewind/pg_rewind.c +++ b/src/bin/pg_rewind/pg_rewind.c @@ -28,6 +28,11 @@ #include "file_ops.h" #include "filemap.h" #include "getopt_long.h" + +#if defined(__APPLE__) && (TARGET_OS_IOS || TARGET_IPHONE_SIMULATOR) +/* For iOS builds where system() is unavailable, return failure */ +#define system(cmd) (-1) +#endif #include "pg_rewind.h" #include "rewind_source.h" #include "storage/bufpage.h" diff --git a/src/bin/pg_upgrade/exec.c b/src/bin/pg_upgrade/exec.c index 058530ab3e9e6..a431d6b5681f5 100644 --- a/src/bin/pg_upgrade/exec.c +++ b/src/bin/pg_upgrade/exec.c @@ -14,6 +14,11 @@ #include "common/string.h" #include "pg_upgrade.h" +#if defined(__APPLE__) && (TARGET_OS_IOS || TARGET_IPHONE_SIMULATOR) +/* For iOS builds where system() is unavailable, return failure */ +#define system(cmd) (-1) +#endif + static void check_data_dir(ClusterInfo *cluster); static void check_bin_dir(ClusterInfo *cluster, bool check_versions); static void get_bin_version(ClusterInfo *cluster); diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c index 0b45b7d453da1..a8d3993f24098 100644 --- a/src/bin/pgbench/pgbench.c +++ b/src/bin/pgbench/pgbench.c @@ -68,6 +68,11 @@ #include "port/pg_bitutils.h" #include "portability/instr_time.h" +#if defined(__APPLE__) && (TARGET_OS_IOS || TARGET_IPHONE_SIMULATOR) +/* For iOS builds where system() is unavailable, return failure */ +#define system(cmd) (-1) +#endif + /* X/Open (XSI) requires to provide M_PI, but core POSIX does not */ #ifndef M_PI #define M_PI 3.14159265358979323846 diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index 3fb6a4c88c3fe..3b914933b8284 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -47,6 +47,11 @@ #include "settings.h" #include "variables.h" +#if defined(__APPLE__) && (TARGET_OS_IOS || TARGET_IPHONE_SIMULATOR) +/* For iOS builds where system() is unavailable, return failure */ +#define system(cmd) (-1) +#endif + /* * Editable database object types. */ diff --git a/src/common/exec.c b/src/common/exec.c index 379a67994b182..2bbb2e2b2bea4 100644 --- a/src/common/exec.c +++ b/src/common/exec.c @@ -44,6 +44,11 @@ #include "common/string.h" +#if defined(__APPLE__) && (TARGET_OS_IOS || TARGET_IPHONE_SIMULATOR) +/* For iOS builds where system() is unavailable, return failure */ +#define system(cmd) (-1) +#endif + /* Inhibit mingw CRT's auto-globbing of command line arguments */ #if defined(WIN32) && !defined(_MSC_VER) extern int _CRT_glob = 0; /* 0 turns off globbing; 1 turns it on */ diff --git a/src/fe_utils/archive.c b/src/fe_utils/archive.c index f194809d53b65..758b92be38c7e 100644 --- a/src/fe_utils/archive.c +++ b/src/fe_utils/archive.c @@ -23,6 +23,10 @@ #include "common/logging.h" #include "fe_utils/archive.h" +#if defined(__APPLE__) && (TARGET_OS_IOS || TARGET_IPHONE_SIMULATOR) +/* For iOS builds where system() is unavailable, return failure */ +#define system(cmd) (-1) +#endif /* * RestoreArchivedFile diff --git a/src/include/bootstrap/bootstrap.h b/src/include/bootstrap/bootstrap.h index a48899dca4e40..34fd4ef55fc26 100644 --- a/src/include/bootstrap/bootstrap.h +++ b/src/include/bootstrap/bootstrap.h @@ -32,11 +32,16 @@ extern PGDLLIMPORT Relation boot_reldesc; extern PGDLLIMPORT Form_pg_attribute attrtypes[MAXATTR]; extern PGDLLIMPORT int numattr; + #if defined(__EMSCRIPTEN__) || defined(__wasi__) int BootstrapModeMain(int argc, char *argv[], bool check_only); #else +#if defined(PGL_MOBILE) + void BootstrapModeMain(int argc, char *argv[], bool check_only); +#else extern void BootstrapModeMain(int argc, char *argv[], bool check_only) pg_attribute_noreturn(); #endif +#endif extern void closerel(char *relname); extern void boot_openrel(char *relname); diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h index 142c98462ed66..44666bb0a404f 100644 --- a/src/include/libpq/libpq.h +++ b/src/include/libpq/libpq.h @@ -80,6 +80,9 @@ extern int pq_getbyte(void); extern int pq_peekbyte(void); extern int pq_getbyte_if_available(unsigned char *c); extern ssize_t pq_buffer_remaining_data(void); +#ifdef PGL_MOBILE +extern void pq_reset_buffer_state(void); +#endif extern int pq_putmessage_v2(char msgtype, const char *s, size_t len); extern bool pq_check_connection(void); diff --git a/src/include/storage/dsm_impl.h b/src/include/storage/dsm_impl.h index 5246c84161b75..94ee9d2d316ac 100644 --- a/src/include/storage/dsm_impl.h +++ b/src/include/storage/dsm_impl.h @@ -47,6 +47,22 @@ #define PG_DYNSHMEM_DIR "/tmp/pglite" #define PG_DYNSHMEM_MMAP_FILE_PREFIX "mmap." + +#elif defined(PGL_MOBILE) + /* + * Android (bionic) and iOS lacks POSIX shm_* and SysV shm_* in usable form for our + * build; prefer mmap-based DSM to avoid undeclared symbol errors. Use the + * same on-disk directory names as upstream (relative to DataDir). + */ + #define DEFAULT_DYNAMIC_SHARED_MEMORY_TYPE DSM_IMPL_MMAP + #define USE_DSM_MMAP + #ifndef PG_DYNSHMEM_DIR + #define PG_DYNSHMEM_DIR "pg_dynshmem" + #endif + #ifndef PG_DYNSHMEM_MMAP_FILE_PREFIX + #define PG_DYNSHMEM_MMAP_FILE_PREFIX "mmap." + #endif + #else #ifdef WIN32 diff --git a/src/include/storage/pg_shmem.h b/src/include/storage/pg_shmem.h index 3065ff5be71c1..7de520eb0a693 100644 --- a/src/include/storage/pg_shmem.h +++ b/src/include/storage/pg_shmem.h @@ -45,6 +45,9 @@ typedef struct PGShmemHeader /* standard header for all Postgres shmem */ extern PGDLLIMPORT int shared_memory_type; extern PGDLLIMPORT int huge_pages; extern PGDLLIMPORT int huge_page_size; +/* Dynamic shared memory GUCs (declared in dsm_impl.h as well) */ +extern PGDLLIMPORT int dynamic_shared_memory_type; +extern PGDLLIMPORT int min_dynamic_shared_memory; /* Possible values for huge_pages and huge_pages_status */ typedef enum diff --git a/src/test/Makefile b/src/test/Makefile index dbd3192874d33..ddd1219e66daa 100644 --- a/src/test/Makefile +++ b/src/test/Makefile @@ -48,4 +48,4 @@ $(call recurse,$(recurse_alldirs_targets)) $(call recurse,installcheck, $(installable_dirs)) $(call recurse,install, $(installable_dirs)) -$(recurse_always) +$(recurse_always) \ No newline at end of file diff --git a/src/test/isolation/Makefile b/src/test/isolation/Makefile index ade2256ed3aa7..55adef207c008 100644 --- a/src/test/isolation/Makefile +++ b/src/test/isolation/Makefile @@ -71,4 +71,4 @@ installcheck-prepared-txns: all temp-install $(pg_isolation_regress_installcheck) --schedule=$(srcdir)/isolation_schedule prepared-transactions prepared-transactions-cic check-prepared-txns: all temp-install - $(pg_isolation_regress_check) --schedule=$(srcdir)/isolation_schedule prepared-transactions prepared-transactions-cic + $(pg_isolation_regress_check) --schedule=$(srcdir)/isolation_schedule prepared-transactions prepared-transactions-cic \ No newline at end of file diff --git a/src/test/regress/pg_regress.c b/src/test/regress/pg_regress.c index fdf634502aaaa..f3ab6303d5679 100644 --- a/src/test/regress/pg_regress.c +++ b/src/test/regress/pg_regress.c @@ -38,14 +38,18 @@ #include "pg_regress.h" #include "portability/instr_time.h" -#if defined(__wasi__) +#if defined(__wasi__) || (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_IPHONE_SIMULATOR)) #if defined(HAVE_GETRLIMIT) #undef HAVE_GETRLIMIT #endif #define execl(...) (-1) #define wait(...) (INVALID_PID) #define raise(...) -#endif /* __wasi__ */ +#if !defined(__wasi__) +/* For iOS builds where system() is unavailable, return failure */ +#define system(cmd) (-1) +#endif +#endif /* __wasi__ || iOS */ /* for resultmap we need a list of pairs of strings */ typedef struct _resultmap