Screenpipe 的 SQLite 恢复扩展 Vendoring 实践:从 3.51.3 源码移植到嵌入式页面级数据恢复

发布时间:2026/9/13 18:20:50
Screenpipe 的 SQLite 恢复扩展 Vendoring 实践:从 3.51.3 源码移植到嵌入式页面级数据恢复 Screenpipe 的 SQLite 恢复扩展 Vendoring 实践从 3.51.3 源码移植到嵌入式页面级数据恢复【免费下载链接】screenpipeYC (S26) | Open Computer History | Record your screen continuously locally and provide context to your agents (Claude, Codex, Openclaw, Hermes, Runner...)项目地址: https://gitcode.com/GitHub_Trending/sc/screenpipe本篇文章围绕 screenpipe 仓库中 crates/screenpipe-sqlite-recovery/VENDORING.md 展开完整解析该项目如何将 SQLite 官方公共领域恢复扩展recovery extension从version-3.51.3源码标签移植vendor进 Rust 工程并使其与工作区锁定的libsqlite3-sys 0.37.0内嵌 SQLite 版本严格对齐。读完本文你将掌握vendoring 文件的来源与 SHA-256 校验方式、本地集成补丁的动机与内容、build.rs的编译接线、lib.rs中 FFI 封装的核心 API以及版本升级时应当遵循的替换与测试流程。为什么 screenpipe 需要“嵌入式”页面级恢复screenpipe 是一款持续在本地录制屏幕与音频、并为 AI Agent 提供上下文的开源工具其核心数据全部沉淀在本地 SQLite 数据库中主要是高频写入的db.sqlite捕获库。面对SQLITE_IOERR、SQLITE_CORRUPT、SQLITE_FULL、SQLITE_NOTADB等硬错误项目遵循“诊断优先、离线修复”的隔离与恢复契约详见 docs/SQLITE_RECOVERY.md先停止写入、独立连接验证确认物理损坏后才进入离线恢复流程。离线恢复的关键一步是调用 SQLite 官方的页面级恢复 APIPage-level Recovery API。传统做法是 shell 出去调用系统安装的sqlite3命令行工具但 screenpipe 选择了一条更可控的路线——把官方恢复扩展的 C 源码直接 vendoring 进仓库通过cc编译进二进制从 crates/screenpipe-sqlite-recovery/src/lib.rs 的 crate 级文档可以看出设计意图该 crate 包装 SQLite 官方恢复扩展刻意不生成sqlite3子进程因此在 macOS、Windows、Linux 以及打包后的 CLI 中使用的都是同一份被锁定的 SQLite 库。这样做的好处包括不依赖宿主机安装任何 SQLite 工具、跨平台行为一致、便于在恢复过程中强制启用某些编译选项。版本对齐策略恢复扩展必须与内嵌 SQLite 同源vendoring 的首要原则是版本匹配。仓库根目录的 Cargo.toml 明确锁定# libsqlite3-sys 0.37 bundles SQLite 3.51.3, including the WAL-reset fix. libsqlite3-sys { version 0.37.0, features [bundled] }即libsqlite3-sys0.37.0 内嵌bundled的 SQLite 版本为3.51.3并自带 WAL-reset 修复。因此恢复扩展也必须取自 SQLite 官方version-3.51.3这一源码标签下的发布归档tarball保证扩展调用的内部 API 与内嵌库完全同源、行为一致。任何一方单独升级都可能导致 ABI 或语义不匹配这正是 VENDORING 文档反复强调“严格匹配”的原因。同时恢复扩展依赖虚拟表SQLITE_ENABLE_DBPAGE_VTAB该编译选项必须在内嵌 SQLite 编译时开启详见下文构建接线。Vendored 文件清单与 SHA-256 完整性校验VENDORING.md 记录了从官方归档中移植的 3 个文件并给出了含本地补丁后的 SHA-256 摘要用于校验与审计文件SHA-256含本地补丁作用dbdata.c0a57eae22b51507c28f6c647fba00ccacd39b5aaf3393dfe9435fb04810a6e0e提供sqlite_dbdata/sqlite_dbptr虚拟表直接读取数据库 b-tree 页面数据sqlite3recover.c97e87d213f4e87df619d388e24f8ab466f533ad5fc6227669e71045a0763d0cd恢复扩展主体实现sqlite3recover.hc088088d15af055304e931842f7c197024d46ff1b7077912ec42149e9fa4f9bb恢复扩展的 C 头文件这三个文件实际存放在 crates/screenpipe-sqlite-recovery/vendor/sqlite-3.51.3-recovery/ 目录下目录名即标明了来源版本。dbdata.c中的注释也印证了其身份——它实现的是从数据库 b-tree 中直接提取数据的sqlite_dbdata/sqlite_dbptr虚拟表这正是恢复扩展读取损坏库页面数据的底层机制。需要特别注意的是sqlite3recover.c的摘要97e87d...是打过本地补丁之后的版本文档同时给出其未打补丁的上游摘要为bc56c1131dfdc979fd93a7e013856f4c2a98570c722c069600f6c66d2d47743f。两值不同恰好证明本地补丁确实存在升级时应以此为依据核对补丁是否仍然必要。本地集成补丁恢复写入前关闭外键强制vendoring 不是原样照搬sqlite3recover.c携带了一处本地集成补丁VENDORING.md 对此有精确描述补丁位置恢复扩展的文件输出连接file-output connection在恢复写入之前禁用外键强制foreign-key enforcement补丁动机使其与扩展回调输出模式callback-output mode下 SQLite 生成的 SQL 行为保持一致补丁边界完成恢复的候选库仍然必须通过完整性与外键校验后才会被安装——也就是说补丁只是让恢复阶段不被外键约束阻断校验责任仍在后续的候选验证环节。这套行为的正确性由 lib.rs 中的测试覆盖无论父表与子表在页序上谁先谁后、无论外键是否DEFERRABLE INITIALLY DEFERRED恢复都能保留可读的行引用且恢复后pragma_foreign_key_check违规数为 0而另一组测试则证明当父行确实不可用时孤儿行恢复不会静默丢弃这些行而是保留下来交给候选验证器去拒绝见 lib.rs。这正体现了“恢复阶段宽容、验证阶段严格”的分层设计。构建接线build.rs 与 SQLITE_ENABLE_DBPAGE_VTABcrates/screenpipe-sqlite-recovery/build.rs 负责把 vendored C 源码编译进 cratelet sqlite_include env::var_os(DEP_SQLITE3_INCLUDE) .map(PathBuf::from) .expect(libsqlite3-sys must expose its bundled SQLite headers); cc::Build::new() .include(sqlite_include) .include(VENDOR_DIR) .file(format!({VENDOR_DIR}/sqlite3recover.c)) .file(format!({VENDOR_DIR}/dbdata.c)) .define(SQLITE_ENABLE_DBPAGE_VTAB, None) .warnings(false) .compile(screenpipe_sqlite_recovery);几个关键点头文件来源通过DEP_SQLITE3_INCLUDE环境变量由libsqlite3-sys在构建时导出拿到内嵌 SQLite 的 C 头文件目录确保恢复扩展与内嵌库共用同一套 SQLite API 定义编译单元同时编译sqlite3recover.c与dbdata.c输出静态库screenpipe_sqlite_recovery编译选项-DSQLITE_ENABLE_DBPAGE_VTAB由 build.rs 显式定义——VENDORING.md 提到“工作区配置为内嵌 SQLite 编译开启该选项”此处即是恢复扩展侧的具体落实该选项同时也会在内嵌 SQLite 本体编译时启用可通过 embedded_recovery_is_available 在运行时验证增量构建通过cargo:rerun-if-changed声明三个 vendored 文件的变更会触发重建。crate 的依赖关系在 Cargo.toml 中也很清晰运行时依赖libsqlite3-sysworkspace 统一版本构建期依赖cc测试期使用rusqlite 0.39.0bundledfeature与tempfile。Rust FFI 封装recover_database 核心 APIcrates/screenpipe-sqlite-recovery/src/lib.rs 通过手写 FFI 声明了恢复扩展的四个 C 入口sqlite3_recover_init(database, schema, destination)— 初始化恢复句柄sqlite3_recover_run(recover)— 执行页面级恢复sqlite3_recover_errmsg(recover)— 获取恢复错误信息sqlite3_recover_finish(recover)— 结束并释放恢复句柄对外暴露的唯一核心函数为recover_database(source: Path, destination: Path) - Result(), RecoveryError其安全语义值得仔细说明源文件只读打开通过sqlite3_open_v2以SQLITE_OPEN_READONLY | SQLITE_OPEN_URI打开源文件全程不写、不 checkpoint 源库调用方必须传入私有工作路径作为 source绝不能直接传线上数据库路径——主文件可以是离线隔离库的硬链接hard link而 WAL/SHM 必须是私有副本拒绝覆盖已存在的 destination若目标文件已存在则直接报错既保证恢复产出物是全新文件fresh-file identity又防止重试覆盖掉之前恢复留下的证据evidence错误类型化RecoveryError携带operation失败阶段、codeSQLite 错误码与message错误文本Display 输出如run page-level recovery failed with SQLite code 11: ...便于定位恢复失败的具体环节句柄生命周期恢复句柄、数据库句柄都会在出错路径上被正确关闭路径中含 NUL 字节、非 UTF-8Windows 分支等边界情况也有专门校验lib.rs。此外embedded_recovery_is_available 通过sqlite3_compileoption_used(ENABLE_DBPAGE_VTAB)在运行时证明内嵌库具备恢复扩展所需的虚拟表能力并有同名测试在 CI 的每个支持平台上执行。在引擎 CLI 中的落地screenpipe db recover 流程恢复扩展最终服务于screenpipe db recover离线恢复流程。在 crates/screenpipe-engine/src/cli/db.rs 中可以看到完整的调用链检查隔离标记db.sqlite.quarantine.json存在、确认应用已停止获取跨进程恢复锁协调中断的交换将线上主库硬链接到私有working-copy目录、复制 WAL/SHM确保恢复输入只读执行核心恢复调用println!(running SQLite page-level recovery against the working copy); screenpipe_sqlite_recovery::recover_database(work, candidate) .context(embedded page-level recovery failed; quarantined DB/WAL/SHM remain untouched)?;重建从权威表派生的外部内容 FTS5 索引对候选库执行新鲜文件身份、quick_check/ 完整integrity_check/foreign_key_check、写入 canary 等验证详见 docs/SQLITE_RECOVERY.md将原始 DB/WAL/SHM 移入db-recovery-*/source-generation/安装验证通过的候选库并归档隔离标记。整个流程中恢复步骤完全不依赖宿主机是否安装了sqlite3可执行文件或包管理器——这正是本 crate 存在意义的最直接体现。测试矩阵恢复行为在三个平台上的保证lib.rs 内置的测试覆盖了恢复扩展的关键行为契约无需外部 CLI 的恢复创建源库、写入数据、恢复后能在只读方式下读出原值且integrity_check返回ok外键恢复多种拓扑父子表跨表引用、父行 rowid 排在子行之后、自引用等场景恢复后pragma_foreign_key_check违规数为 0且新连接仍默认启用外键强制PRAGMA foreign_keys 1孤儿行保留父行缺失时可读的孤儿子行被保留下来交由验证器拒绝违规数为 1绝不静默丢弃索引页损坏时打捞数据在 200 行数据 索引的场景下人为用0xff覆写索引根页偏移处 512 字节恢复后 200 行数据全部找回、integrity_check通过且原始文件字节分毫不差WAL 提交数据恢复journal_modeWAL且wal_autocheckpoint0时只存在于 WAL 中的已提交记录也能通过只读主文件恢复出来原始 DB/WAL/SHM 三元组保持不变拒绝覆盖既有候选目标已存在时返回包含refusing to overwrite的错误信息。结合 VENDORING.md 的要求——升级后必须在macOS、Windows、Linux三个平台运行这些恢复测试——可以推断 CI 对每个支持平台都执行上述测试确保 vendored 代码跨平台行为一致。版本升级与维护流程当内嵌 SQLite 版本变更即libsqlite3-sys升级导致 bundled SQLite 版本变化时VENDORING.md 给出了明确的维护清单同步替换从与新版 SQLite 完全匹配的官方源码标签发布归档中重新提取dbdata.c、sqlite3recover.c、sqlite3recover.h三个文件替换crates/screenpipe-sqlite-recovery/vendor/sqlite-3.51.3-recovery/目录下的对应文件评估本地补丁保留本地补丁——除非上游在该版本中已经修复了同样的行为即文件输出连接的外键强制问题更新摘要重新计算并更新文档中的 SHA-256 摘要含补丁后的三个文件摘要以及未打补丁的上游摘要以便核对三平台回归在 macOS、Windows、Linux 上运行恢复测试确认恢复行为、外键语义、WAL 恢复与索引页损坏打捞等契约未回归确认编译选项确保新版内嵌 SQLite 编译仍开启SQLITE_ENABLE_DBPAGE_VTABbuild.rs 中的.define(...)与embedded_recovery_is_available运行时检查会持续守护这一前提。这套流程的核心思想是恢复扩展必须始终与内嵌 SQLite 版本同源、校验摘要、保留并复核本地补丁、三平台回归。任何一步缺失都可能让恢复扩展在内嵌库升级后出现 API 不匹配或行为漂移。关键参考路径VENDORING.md — 本文主体文档版本对齐、文件清单、摘要、补丁与升级流程build.rs —cc编译接线与SQLITE_ENABLE_DBPAGE_VTAB定义src/lib.rs — FFI 封装、recover_databaseAPI 与测试矩阵vendor/sqlite-3.51.3-recovery/ — vendored 的dbdata.c、sqlite3recover.c、sqlite3recover.hCargo.toml — 工作区锁定的libsqlite3-sys 0.37.0bundled SQLite 3.51.3docs/SQLITE_RECOVERY.md — 隔离与恢复的整体契约、候选验证与安装流程crates/screenpipe-engine/src/cli/db.rs — 引擎 CLI 中对recover_database的实际调用点【免费下载链接】screenpipeYC (S26) | Open Computer History | Record your screen continuously locally and provide context to your agents (Claude, Codex, Openclaw, Hermes, Runner...)项目地址: https://gitcode.com/GitHub_Trending/sc/screenpipe创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考