☰
Firefox书签备份恢复:dejsonlz4编译与jsonlz4解压指南
2026/10/26 10:34:42 网站建设 项目流程

简介:dejsonlz4 是一份面向需要处理 Firefox 书签备份文件的开发者与进阶用户的开源小工具包,重点解决 .jsonlz4 非标准压缩格式难以直接读取、解压的问题。压缩包共 7 个文件,以 3 个 C 源文件、2 个 Markdown 文档、1 个头文件和 1 份许可证为主,涵盖核心解压实现、LZ4 相关代码、使用说明与授权信息,整体约 22KB,体量轻便,便于查阅、编译和二次集成。内容围绕解压逻辑展开,读者可据此理解书签备份的存储结构,获得可直接参考的 C 实现、说明文档及许可条款,适合数据恢复、格式解析或工具改造时对照使用;对希望直接查看 C 语言解压流程的读者也较友好,并可作为学习 LZ4 块格式与命令行工具组织方式的参考样本,同时保留清晰的源码组织。目前已有 470 人学习下载,具备一定参考热度。

1. 从一份打不开的书签备份说起:dejsonlz4 要解决的是什么

Firefox 会定期把书签写进 profile 目录下的bookmarks-YYYY-MM-DD.jsonlz4,重装系统、换机器、或者配置目录被误删之后,很多人第一反应是双击打开,结果得到一个二进制乱码文件——它不是坏了,是被 LZ4 压过一层,外面还套了个 12 字节的自定义头。dejsonlz4 就是干这件事的开源小工具:把.jsonlz4解压回标准 JSON,让你能直接读、能拿脚本做迁移比对,也能把上千条书签的标题和地址抽成一张表。它没有 GUI、不装服务、不起后台进程,编译出来就是一个可执行文件。但环境不对、命令给错、头信息没对齐,照样会卡在一堆看不懂的报错上。下面这套流程,是我在几台机器上反复装过之后留下来的一份能照着跑的记录。

2. 环境先列清:dejsonlz4 的依赖清单与工具链取舍

装这类小工具最容易犯的错,是先apt install一堆东西,最后发现根本用不上。dejsonlz4 的上游把它做成了单文件 C 源码,自带精简的 LZ4 块解码实现,不链接系统里的 liblz4,也不依赖 zlib、openssl 这些常见库。换句话说,只要机器上有一个能编 C99 的编译器,剩下的都是路径和参数问题。所以在动手之前,我更愿意先把「有什么、缺什么、缺的怎么补」列成一张表,而不是边编边报错边猜。

2.1 依赖清单:一张表看完

依赖项要求用途怎么确认
C 编译器支持 C99 的 gcc / clang编译单文件源码cc --version
标准库头文件随编译器一起装stdio / stdlib / string编译不报No such file
make任意版本跑 Makefile(可选)make --version
shell 环境bash / zsh / PowerShell跑批量脚本echo $SHELL
磁盘几十 MB 足够源码 + 产物 + 样本df -h .

把这几条挨个敲一遍,缺哪个补哪个:

# 三条命令确认工具链齐不齐,缺哪个装哪个 cc --version | head -n 1 # 看编译器是不是 gcc/clang,版本不用太新 make --version | head -n 1 # 没有 make 也能手敲 gcc,只是批量编译麻烦 uname -srm # 确认平台和架构,后面选 mingw 还是 clang 靠它

第一条输出的如果是cc (GCC) ...或者Apple clang version ...,说明编译器在位;如果提示command not found,Debian 系装build-essential,RHEL 系装gcc make,macOS 跑xcode-select --install把命令行工具拉下来。第二条只是确认 make 在不在,这套流程里 make 属于「有更好、没有也能过」的角色,真正关键的是第三条——平台和架构决定了后面编出来的产物能不能在目标机器上跑,尤其是你要把二进制拷到另一台机器去解压备份的时候。

2.2 三个平台的工具链怎么选

Linux 最省事,发行版自带的 gcc 就够,不用追新版本。我一般直接用cc,让系统去决定用 gcc 还是 clang,这样换机器不用改命令。macOS 要注意两件事:一是必须先把 Xcode Command Line Tools 装上,否则cc只是个指向不存在路径的壳子;二是 Apple Silicon 上编出来的默认是 arm64,如果你要把二进制拷到 Intel 机器上跑,得显式指定架构。Windows 是坑最多的一个,MSVC 不是不能编,但源码里用了一些 POSIX 风格的写法,用 MSVC 容易在类型和头文件上卡住,常见做法是走 MSYS2 里的 mingw-w64,或者干脆开 WSL 按 Linux 那套来。

三个平台的准备动作放在一起对比更清楚:

# Linux:装完 build-essential 基本就结束了 sudo apt install -y build-essential # macOS:只装命令行工具,不装完整 Xcode xcode-select --install # Windows(MSYS2 环境内):装 mingw-w64 工具链 pacman -S --needed mingw-w64-x86_64-gcc make

Linux 那条命令除了 gcc,还会带上 make 和 libc 头文件,一步到位。macOS 的xcode-select --install会弹一个图形确认框,装完cc才能用,这一步经常被跳过,然后编译时提示invalid active developer path,看着像源码问题,其实是工具链根本没装完。Windows 那条是在 MSYS2 的终端里执行的,装完记得确认当前 shell 是MINGW64而不是MSYS,两者编出来的产物链接的运行时不一样,混用会出现「本机能跑、拷走就缺 dll」的情况。

2.3 源码放哪、目录怎么摆

我不建议把源码和产物直接丢在~/Downloads里,也不建议放进 Firefox 的 profile 目录。前者的下场是过两周自己都找不到编出来的是哪个版本,后者更麻烦——profile 目录会被浏览器同步、清理、甚至被其他工具扫描,放个可执行文件进去属于给自己找事。我一般会固定一个工作目录,把源码、产物、样本分三层放:

# 三层目录:源码、产物、待解压样本各自独立,清理时不会误删 mkdir -p ~/work/dejsonlz4/{src,bin,samples} cd ~/work/dejsonlz4/src # 从项目主页取源码包解开,或 git clone 到当前目录 ls -l dejsonlz4.c sha256sum dejsonlz4.c | tee ../src.sha256

前两条命令建目录并进入源码层,第三行确认核心源文件确实在。最后一行是我个人的习惯:把源文件的哈希记下来存到上一级目录。上游这个项目的发布节奏比较随性,没有语义化版本号可跟,稳妥做法就是记 commit hash 或者源码指纹,这样在第二台机器上编出来的东西和第一台是不是同一份,一眼就能对出来,不用靠回忆。samples目录专门放从各处收集来的.jsonlz4样本,包括正常的、截断的、以及扩展名对但内容不对的,后面排查问题时会反复用到。

3. 编译 dejsonlz4:从一行 gcc 到可复现的 Makefile

环境齐了之后,编译本身只有一行命令的事,但「能编过」和「编出来的东西可信」是两回事。我见过太多人随手gcc dejsonlz4.c得到一个a.out,然后过两天完全想不起来这个文件是什么、用什么参数编的、能不能直接发给同事。所以这一章我会分三步走:先给出最小可用的编译命令并解释每个参数,再把它固化成一个能重复执行的 Makefile,最后处理 Windows 和 macOS 上那些不一样的细节。

3.1 最小编译命令与产物自检

# -O2 开优化,-Wall -Wextra 打开常用告警,-std=c99 明确语言标准 cc -O2 -Wall -Wextra -std=c99 -o ../bin/dejsonlz4 dejsonlz4.c # 产物自检三连:类型、依赖、能不能跑 file ../bin/dejsonlz4 ldd ../bin/dejsonlz4 2>/dev/null || otool -L ../bin/dejsonlz4 ../bin/dejsonlz4

第一条命令里,-O2是常规优化级别,解压这种以内存拷贝和循环为主的程序,开 O2 之后处理大文件会明显快一些,但不要上-O3,对小工具来说收益很小,反而可能让调试信息更难对。-Wall -Wextra建议保留,第一次编译时如果冒出大量告警,说明源码和你手上的编译器版本存在适配问题,这时候先看清告警内容再决定要不要忽略。-std=c99是硬要求,源码里存在循环内声明变量这类写法,用默认的 gnu89 标准会直接报错。

后面三条是自检。file确认产物架构和你预期一致,别在 arm 机器上编出 x86 的二进制还不自知。ldd在 Linux 上看动态链接了哪些库,正常情况应该只有 libc 一项,如果冒出一堆你没预期的库,说明编译时被环境变量带偏了;macOS 没有 ldd,用otool -L代替。最后一条不带参数直接跑,正常会打印一段用法说明并以非零状态退出,这既是确认二进制能执行,也是提前把参数格式看一眼——不同时间取到的源码,用法提示可能略有差别,以你自己编出来这一版打印的为准。

3.2 写成 Makefile,让第二台机器也能复现

CC ?= cc CFLAGS ?= -O2 -Wall -Wextra -std=c99 SRC := dejsonlz4.c BIN := ../bin/dejsonlz4 PREFIX ?= /usr/local all: $(BIN) $(BIN): $(SRC) @mkdir -p $(dir $(BIN)) $(CC) $(CFLAGS) -o $@ $< install: $(BIN) install -m 0755 $(BIN) $(PREFIX)/bin/dejsonlz4 clean: rm -f $(BIN) .PHONY: all install clean

这段 Makefile 的关键在几个?=和自动变量上。CC ?= cc和CFLAGS ?=用的是条件赋值,意思是「如果外部没传,才用这里的默认值」,所以换编译器时只要make CC=clang,加调试信息时make CFLAGS="-O0 -g -std=c99",不用改文件本身。$(dir $(BIN))取出产物的目录部分,配合mkdir -p保证bin目录一定存在,避免在干净目录里第一次 make 就失败。$@是目标名,$<是第一个依赖,也就是源文件,这样写成规则比把文件名硬编码进去更耐改。

install目标里用install -m 0755而不是cp,是因为它一步到位设好权限,省得后面再chmod。PREFIX默认为/usr/local,普通用户执行时可能没权限,这时候要么加sudo,要么改成make install PREFIX=$HOME/.local,后者我更推荐——小工具放用户目录下,升级和卸载都不会污染系统路径。clean只删产物不删源码,别写成rm -rf加通配符,这类脚本在别人机器上跑出过事故。

3.3 Windows 与 macOS 的差异处理

Windows 下最大的坑是「本机能跑、拷给别人就报缺 dll」。mingw 编出来的默认是动态链接运行时库,解决方式是在编译时加-static把运行时静态链进去:

# Windows(MSYS2/MINGW64):静态链接,避免拷走之后缺 libgcc/winpthread x86_64-w64-mingw32-gcc -O2 -std=c99 -static -o dejsonlz4.exe dejsonlz4.c

-static会让产物体积涨到几百 KB,换来的是单文件可分发,值。如果你只在自己机器上用,不加也行,但要注意 MSYS2 的bin目录必须在 PATH 里,否则会提示找不到libwinpthread-1.dll。

macOS 这边要留意架构。默认编出来的是当前机器架构,要在 Intel 和 Apple Silicon 之间通用,可以编成双架构:

# macOS:同时包含 arm64 和 x86_64,便于跨机器分发 clang -O2 -std=c99 -arch arm64 -arch x86_64 -o ../bin/dejsonlz4 dejsonlz4.c file ../bin/dejsonlz4 # 输出里应同时出现两种架构

-arch可以传多次,编译器会把两份目标代码合并成一个胖二进制。代价是体积翻倍,好处是拷给谁都直接能跑。如果只在当前机器用,去掉这两个参数反而更快。

4. 解压与配置:先看懂 12 字节的头,再谈命令怎么给

很多人第一次用 dejsonlz4 失败,不是工具的问题,是文件根本没被识别成 jsonlz4。这一步的核心是搞明白.jsonlz4这个容器的结构:它不是标准 LZ4 frame,而是一段自定义头加一段裸 LZ4 块。头搞清楚了,后面的参数、批量脚本、校验方法都是顺理成章的事。

4.1 容器结构:三个字段决定成败

偏移(字节)长度内容说明
08mozLz40\0魔数,对不上就不是这个格式
84原始大小小端序 uint32,解压后应有的字节数
12剩余LZ4 块数据裸块,不是 LZ4 frame

拿真实的备份文件看一眼头,比任何文档都直观:

# 只看前 16 个字节,够判断魔数和长度字段了 od -A d -t x1 -N 16 samples/bookmarks-2024-05-01.jsonlz4

输出里前 8 个字节应该能对上6d 6f 7a 4c 7a 34 30 00,也就是mozLz40加一个结束符。第 8 到 11 字节是原始大小,小端序,读的时候要倒着拼。如果这 8 个字节对不上,后面的步骤全部不用试了,要么文件不是这种格式,要么下载或拷贝过程中被改过。这个长度字段非常有用——它是解压后应该得到的准确字节数,等于给自己留了一个校验基准,解出来的文件大小和它对不上,就说明中间出了岔子。

4.2 单文件解压:参数就两个,顺序别搞反

# 用法:dejsonlz4 <输入文件> [输出文件] ../bin/dejsonlz4 samples/bookmarks-2024-05-01.jsonlz4 samples/bookmarks.json # 不给输出文件时写到标准输出,适合管道场景 ../bin/dejsonlz4 samples/bookmarks-2024-05-01.jsonlz4 | head -c 200

第一个参数是输入,第二个是输出,就这么简单。不给第二个参数时结果走标准输出,这种模式在管道里很顺手,比如直接接jq做统计,但要注意别在 Windows 的控制台里直接看——终端编码和换行处理会让 JSON 看起来像乱码,误以为解压失败。所以我在 Windows 上永远显式指定输出文件,多敲几个字符,省掉一轮误判。

还有一个-m开关,是给另一类扩展名.mozlz4的会话恢复文件准备的。判断依据其实不在扩展名,而在文件头:开头是mozLz40\0就走默认路径,如果默认模式提示格式不符,再拿-m试一次。以你自己编出来那一版打印的用法说明为准,别照搬网上抄来抄去的参数表,不同时间取的源码在选项上可能有细微差别。

4.3 批量解压:一个目录几十份备份怎么处理

Firefox 的备份是按日期滚动的,真要恢复的时候,往往是一整个目录几十个文件摆在面前,手动一个个敲不现实:

#!/usr/bin/env bash # 批量解压目录下所有 .jsonlz4,输出到指定目录,已解过的跳过 set -euo pipefail DECOMP="${DECOMP:-$HOME/work/dejsonlz4/bin/dejsonlz4}" SRC_DIR="${1:?用法: batch.sh <备份目录> [输出目录]}" OUT_DIR="${2:-./decoded}" mkdir -p "$OUT_DIR" shopt -s nullglob for f in "$SRC_DIR"/*.jsonlz4; do base="$(basename "$f" .jsonlz4)" out="$OUT_DIR/$base.json" # 已有非空结果就跳过,避免重复跑覆盖掉人工修正过的文件 if [[ -s "$out" ]]; then echo "skip $base.json" continue fi if "$DECOMP" "$f" "$out"; then echo "ok $base.json" else echo "fail $f" >&2 # 失败继续跑下一个,不中断整批 fi done

set -euo pipefail是三件套:遇错退出、未定义变量报错、管道中任一环节失败就算失败。shopt -s nullglob解决的是「目录里一个匹配都没有时,通配符原样传进循环」这个经典问题,不加的话循环体里会拿到一个字面量*.jsonlz4,然后报一个莫名其妙的文件不存在。${1:?...}是参数必填的写法,不传就直接打印用法退出,比跑到一半才发现路径错了要好。

跳过已存在文件那段是有意为之:这批备份里往往有几份是重点,你可能已经手工修正过输出,重跑一次全冲掉就白干了。失败不中断整批同样重要——几十个文件里有一两个截断的损坏备份太常见,为了一个坏文件把后面好的全停掉,纯属跟自己过不去。

4.4 输出校验:JSON 合法只是第一关

# 第一关:结构合法 python3 -m json.tool decoded/bookmarks-2024-05-01.json > /dev/null && echo "JSON OK" # 第二关:内容合理,统计书签节点数量 jq '[.. | objects | select(.type == "text/x-moz-place")] | length' \ decoded/bookmarks-2024-05-01.json

python3 -m json.tool是最轻的结构校验,能把尾部截断、括号不闭合这类问题抓出来,但它不保证内容完整——一个只剩前一百条书签的文件同样能通过。所以第二关用jq的递归下降..遍历所有层级,筛出类型为text/x-moz-place的节点并计数,和你在浏览器里记得的书签量级对一对,数量差一个数量级就说明这份备份不完整,得换更早的日期再试。

5. 避坑与排查:六个真实翻车现场

这套流程我在不同机器上装过好几轮,下面这几条是重复出现频率最高的,按「现象、原因、解决」三段记下来,遇到时可以直接对号入座,不用从头推。

5.1 编译阶段

现象:报'for' loop initial declarations are only allowed in C99 mode,指向一个看起来很普通的 for 循环。原因:编译器默认按 gnu89 处理,不允许在 for 的初始化表达式里声明变量。解决:编译参数里补-std=c99或-std=gnu99,别去改源码,改源码会让下次换机器时对不上原始版本。

现象:提示找不到ssize_t或者一堆 POSIX 类型未定义。原因:在 Windows 上用了 MSVC 或者不完整的工具链,这些类型不在标准 C 里。解决:换 mingw-w64,或者干脆开 WSL 按 Linux 那套走,比在 MSVC 上逐个补 typedef 省时间。

现象:链接时报找不到 lz4 相关符号或提示-llz4无法解析。原因:照着旧教程在命令里加了-llz4,但这一版源码自带解码实现,不需要外部库。解决:把-llz4去掉,ldd确认产物只依赖 libc 就对了。

5.2 运行与输出阶段

现象:解压直接报格式不符,或者解出来是空文件。原因:输入文件根本不是这个容器格式,比如把导出的.json或者.bak当成了.jsonlz4。解决:用od看前 8 字节,对不上mozLz40就换文件,别在参数上继续折腾。

现象:解出来的 JSON 尾部被截断,解析器报Expecting ',' delimiter。原因:备份写入过程中进程被终止或磁盘写满,文件本身就不完整,头部记录的长度和实际数据对不上。解决:比对stat出来的输出大小和头部第 8 到 11 字节记录的值,不一致就换同目录下更早日期的备份,坏文件没有修复价值。

现象:输出文件权限拒绝,或者写进去的内容是空的。原因:输出路径指到了 Firefox 正在使用的 profile 目录,或者指向了一个只读挂载点。解决:输出统一放到工作目录下的decoded/,和浏览器目录彻底分开,这条我吃过一次亏之后就一直这么干。

6. 装完怎么验证:拿一份真备份把整条链路走一遍

编译通过不等于能用,我习惯在交付给自己或同事之前,把下面这套验证完整跑一遍,五分钟左右,能挡掉绝大多数后续返工。

先确认二进制本身的行为符合预期:不带任何参数直接执行,应该打印用法说明并以非零码退出。这一步看着无聊,但它同时验证了「文件可执行」「动态库齐全」「参数解析正常」三件事,比file和ldd加起来还直接。接着拿一份真实的bookmarks-*.jsonlz4跑完整解压,把输出落到工作目录而不是 profile 里,然后用三条命令交叉验证:

# 1) 二进制行为:应打印用法并非零退出 ../bin/dejsonlz4; echo "exit=$?" # 2) 完整解压一份真实备份 ../bin/dejsonlz4 samples/bookmarks-2024-05-01.jsonlz4 decoded/check.json # 3) 大小对齐 + JSON 合法 + 书签计数 stat -c %s decoded/check.json 2>/dev/null || stat -f %z decoded/check.json python3 -m json.tool decoded/check.json > /dev/null && echo "JSON OK" jq '[.. | objects | select(.type == "text/x-moz-place")] | length' decoded/check.json

第一条的echo "exit=$?"是重点,很多人只看输出不看退出码,脚本里一旦接上这个工具,退出码才是判断依据。第三条的stat写了两个分支,-c是 GNU 的写法,macOS 上要用-f %z,两个都写上就不用记哪个平台用哪个。把这里得到的字节数和od看到的头部长度字段对一下,两个数字一致,才算是真正把「压缩头、数据块、解码器」这三段串通了。

最后一步是内容层面的抽检:从解出来的 JSON 里随机抽十条,确认标题和地址不是空的,层级结构和你在浏览器里看到的目录对得上。这一步没法完全自动化,但抽十条也就一分钟,比事后发现整份备份缺了一层目录要划算得多。从那以后,我每次在新机器上装完这套东西,都强制把这三条验证跑一遍再收工——工具本身简单,翻车的从来都是环境和路径。希望这套流程能帮你少走一遍弯路,源码和几个平台的预编译产物我都整理在一起了,取回去按第 3 章编一遍就能直接用。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询