CPIO 文件格式全面详解

CPIO 文件格式全面详解

Zi_Cai

2026-09-01 发布156 浏览 · 0 点赞 · 0 收藏

CPIO 文件格式全面详解

本文参考 POSIX 规范、GNU cpio / libarchive 官方文档、cpio(5) 手册页
编写而成。文中所有字段布局、magic 值、对齐规则均以公开规范为准,
数据结构以通用 C 语言定义给出。


1. 概述

cpio 是 Unix 上最早期的通用归档程序(archiver)之一,其名称来自 "copy in and out"(复制进/复制出),因为它本质上是一个面向标准输入/标准输出的流式拷贝工具:find | cpio -o 把文件列表打包成归档流,cpio -i 再从归档流中解出文件。

一个 cpio 归档(archive)就是一组 文件条目(entry)的简单串联:

[条目 1][条目 2][条目 3]...[TRAILER!!! 条目]

每个条目由三部分组成:

  1. 文件头(header):固定长度的元数据块,记录文件名长度、大小、
    inode、权限(mode)、属主(uid/gid)、链接数、时间戳、设备号等;
  2. 文件名(name):以 \0(NUL)结尾的字节串;
  3. 文件数据(data):文件的实际内容,可为空(目录、设备节点、
    符号链接、硬链接引用等)。

归档以名为 TRAILER!!! 的特殊条目收尾,之后的字节是补零填充(padding),用于对齐到磁带/磁盘的块边界。

cpio 格式最大的特点是:极简、流式、可管道化。它没有 tar 那样的 512 字节固定块结构,头很小(26~110 字节),不要求随机访问,天然适合磁带顺序写入和 管道 | gzip 链式处理。

2. 历史沿革

2.1 起源:1977,AT&T PWB/UNIX

cpio 由 AT&T 贝尔实验室 Unix 支持组的 Dick Haight 于 1977 年编写,随 PWB/UNIX 1.0(Programmer's Workbench,程序员工作台,一个基于第六版 UNIX 的内部发行版)首次发布。它与 find 配套设计:find 负责枚举文件名,cpio 负责把列出的文件复制到别处或打包进归档。

为什么发明 cpio? 当时的核心诉求是把文件备份到 9 轨磁带上:

  • 磁带只能顺序读写,无法像磁盘那样随机寻址,因此需要一种
    顺序写入、边读边写的流式格式;
  • 磁带介质昂贵,记录头要尽量小,避免浪费;
  • 备份要能保留 Unix 文件系统的特殊文件(设备节点、FIFO、符号链接、
    inode、硬链接),这是早期磁带归档格式做不到的。

cpio 于 1981 年随 System III UNIX 首次对外发布。值得注意的是:cpio 比 tar 更早诞生——tar 直到 1979 年(第七版 UNIX)才出现,只是 cpio 早期仅限 AT&T 内部使用,知名度反而不如 tar。

2.2 分裂:二进制格式与字符格式并行

  • PWB 二进制格式(bin,magic 070707):最初的格式,所有数字用
    2 字节/4 字节原生二进制表示,依赖机器字节序;
  • 旧字符格式(odc,magic 070707):到 1980 年左右(System III
    时期)已出现,数字改用 ASCII 八进制表示,解决了跨机器可移植性问题。

2.3 标准化:POSIX.1-1988

IEEE 1003.1-1988(POSIX.1)把 cpio 的格式正式标准化,即 "portable octet-oriented cpio format",俗称 odc / old character / cpiop。这是历史上唯一进入 POSIX 的 cpio 变体。

2.4 SVR4 改革:newc 与 crc

1989 年,System V Release 4(SVR4) 引入两个新格式:

  • newc("new character",magic 070701):字段改为 8 位十六进制,
    字段宽度从 18 位跃升到 32 位,支持超过 65536 个 inode 的文件系统,
    并拆分 devmajor/devminor;
  • crc(magic 070702):在 newc 基础上增加 32 位校验和字段,
    用于检测介质(磁带)读写错误。

之所以需要改革,是因为旧格式的硬上限(详见第 4、5 节)在磁盘容量快速增长后成为瓶颈:inode 编号超过 262143、单文件超过 2GB 的机器越来越常见。

2.5 2001:被 pax 取代

POSIX.1-2001 中,cpio 与 tar 命令双双被移出标准,取而代之的是能同时读写两者格式的 pax 工具。但 cpio/ustar/pax 三种格式仍由 POSIX.1-2001 明确定义(作为 pax 的交换格式)。今天 pax -w -x cpio 仍可生成 odc 格式归档。

2.6 现代实现

  • GNU cpio:主流 cpio 实现。出于历史兼容,cpio -o 的默认输出
    格式仍是旧二进制 bin(这带来大 inode 编号无法存储的问题,见
    Red Hat bug #952313),官方文档明确建议用 -H newc / -H crc。
    读取时自动探测所有格式,并能自动处理异种字节序的二进制归档。
  • libarchive / bsdcpio:BSD 系的现代重实现,默认输出格式为 odc。
  • Linux 内核 initramfs:内核初始化根文件系统就是一个
    gzip 压缩的 newc 归档——这是 cpio 在当代最重要的应用场景之一。

2.7 演进动因小结

时期 事件 要解决的问题
1977 cpio 诞生(PWB/UNIX) 磁带顺序备份;保留特殊文件与 inode
~1980 odc 出现 二进制格式依赖机器字节序,无法跨平台交换
1988 POSIX.1-1988 采纳 odc 命令与格式标准化
1989 SVR4 推出 newc inode/文件大小超过旧格式 18/33 位上限
1989 SVR4 推出 crc 无任何校验,磁带坏块会静默损坏数据
2001 POSIX 用 pax 取代 cpio/tar 命令 两大格式之争;统一交换格式
至今 GNU cpio / libarchive / 内核 initramfs 仍在固件、系统启动场景广泛使用

3. 通用结构(所有格式的公共部分)

3.1 条目模型

+----------------+---------------------------+--------------------+
|  header        |  name (NUL 结尾)          |  data(可为空)     |
|  固定长度       |  长度由 namesize 字段给出  |  长度由 filesize 字段 |
+----------------+---------------------------+--------------------+
  • 文件名与文件数据之间、文件数据之后,按格式不同可能有对齐填充(见 3.3);
  • 目录(directory)条目的 data 为空;设备节点、FIFO、socket 的 data 也为空;
  • 符号链接的 data 存放链接目标路径字符串;
  • 硬链接引用条目(newc/crc 下)data 为空,靠 (dev, ino) 关联数据载体。

3.2 mode 字段:文件类型位 + 权限位

mode 是 16 位/32 位字段,高 4 位(掩码 0xF000)表示文件类型,低 12 位(掩码 0x0FFF)是 Unix 权限位。类型位的标准定义如下:

/* 文件类型位(与 <sys/stat.h> 的 S_IF* 同值,此处以十六进制书写) */
#define CPIO_IFMT   0xF000   /* 文件类型掩码 */
#define CPIO_IFREG  0x8000   /* 普通文件 */
#define CPIO_IFDIR  0x4000   /* 目录 */
#define CPIO_IFLNK  0xA000   /* 符号链接 */
#define CPIO_IFCHR  0x2000   /* 字符设备 */
#define CPIO_IFBLK  0x6000   /* 块设备 */
#define CPIO_IFIFO  0x1000   /* 命名管道(FIFO) */
#define CPIO_IFSOCK 0xC000   /* 套接字 */
#define CPIO_PERM   0x0FFF   /* 权限位掩码 */

/* 文件类型 = mode & CPIO_IFMT */
类型位 含义 数据内容
0x8000 普通文件 文件内容
0x4000 目录 空
0xA000 符号链接 链接目标路径
0x2000 字符设备节点 空(设备号在 rdev 字段)
0x6000 块设备节点 空(设备号在 rdev 字段)
0x1000 FIFO 命名管道 空
0xC000 套接字 空

这是 cpio 相对 tar 的核心优势之一:tar 的传统格式几乎无法表达设备节点、socket 等特殊文件,而 cpio 从一开始就为备份整机(含 /dev)设计,完整保留设备号。

3.3 对齐(padding)规则

格式 对齐基数 说明
newc / crc 4 字节 文件名(含 NUL)后、文件数据后各对齐到 4 字节边界
bin(旧二进制) 2 字节 文件名后、文件数据后各对齐到 2 字节边界
odc(旧 ASCII) 无对齐 文件名后紧跟数据,不填充

通用的实现方式:

/* 跳过填充:使偏移 (offset + pad) 成为 align 的整数倍 */
static void skip_padding(FILE *fp, long offset, int align)
{
    long pad = (align - (offset % align)) % align;
    while (pad-- > 0)
        fgetc(fp);
}

newc 采用 4 字节对齐是为了让头/数据在主流 32 位机器上自然对齐,加快磁带/磁盘 I/O;odc 不填充则是因为纯文本格式本就逐字节解析,省掉填充能减小归档体积。

3.4 结束标记:TRAILER

归档最后一个条目名为 TRAILER!!!,filesize 为 0。读到它即视为归档结束;其后剩余字节是补零,用于把归档长度填充到块边界(GNU cpio 默认 512 字节块,可通过 --block-size / -B 调整)。很多实现(如 libarchive)还会校验末尾是否为全零。

#define CPIO_TRAILER "TRAILER!!!"

3.5 符号链接

符号链接条目的 data 字段直接存放目标路径(字符串),不存链接自身的 inode 目标。读取时把 data 按字节解码为字符串即为链接目标;写入时把目标路径原样填入 data 即可。

注意:解包符号链接时若不加以控制,可能被用于路径逃逸(详见第 11 节)。

3.6 硬链接

cpio 用 (devmajor, devminor, ino) 三元组标识一组硬链接:同一组的多个条目共享 inode/设备号与 nlink(链接计数),但只有其中一个条目携带文件数据,其余条目 filesize 为 0。

写入时把同一组的非载体条目改写为无数据条目(filesize = 0);读取时按 (dev, ino) 分组,把无数据条目关联到携带数据的载体条目,即可还原硬链接关系,避免重复存储同一份文件内容。

3.7 数字编码的三种形态

形态 使用格式 特点
二进制整数 bin 体积最小,但依赖机器字节序
八进制 ASCII odc 可移植,但定长字段位数少(18/33 位)
十六进制 ASCII newc / crc 可移植且字段宽度大(32 位)

字符格式(odc/newc/crc)可统一用 strtol / sscanf 按相应进制解析;二进制格式(bin)读取时先按大端读 magic,若不匹配则整体交换字节序,从而兼容大端/小端机器产出的归档。


4. 旧二进制格式:bin

4.1 历史与动机

bin 是 cpio 的原始格式,随 1977 年 PWB/UNIX 诞生。它把元数据全部编码为原生 2 字节/4 字节整数,体积最小、解析最快,非常适合磁带顺序备份。其 magic 为八进制 070707(十六进制 0x71C7,即 070707 的二进制展开)。

4.2 数据结构(26 字节头)

偏移 长度 字段 说明
0 2 magic 0x71C7,按机器字节序存储
2 2 dev 设备号(16 位)
4 2 ino inode 编号(16 位)
6 2 mode 文件类型 + 权限
8 2 uid 属主(16 位)
10 2 gid 属组(16 位)
12 2 nlink 链接数(16 位)
14 2 rdev 特殊文件的设备号
16 4 mtime 修改时间(32 位,秒)
20 2 namesize 文件名长度(含 NUL)
22 4 filesize 文件数据长度(32 位)

共 26 字节,之后是文件名,文件名与数据均按 2 字节对齐。

通用的 C 结构体定义(紧凑布局,与磁盘格式完全一致,共 26 字节):

#pragma pack(push, 1)   /* 禁止编译器插入对齐填充 */
struct cpio_bin_header {
    uint16_t c_magic;    /* 070707 */
    uint16_t c_dev;
    uint16_t c_ino;
    uint16_t c_mode;
    uint16_t c_uid;
    uint16_t c_gid;
    uint16_t c_nlink;
    uint16_t c_rdev;
    uint32_t c_mtime;    /* 4 字节 */
    uint16_t c_namesize;
    uint32_t c_filesize; /* 4 字节 */
};                       /* sizeof == 26 */
#pragma pack(pop)

为保证跨机器一致,推荐统一按大端写出:

struct cpio_bin_header hdr = {0};

hdr.c_magic    = htons(070707);
hdr.c_dev      = htons((uint16_t)(dev & 0xFFFF));
hdr.c_ino      = htons((uint16_t)(ino & 0xFFFF));
hdr.c_mode     = htons((uint16_t)(mode & 0xFFFF));
hdr.c_uid      = htons((uint16_t)(uid & 0xFFFF));
hdr.c_gid      = htons((uint16_t)(gid & 0xFFFF));
hdr.c_nlink    = htons((uint16_t)(nlink & 0xFFFF));
hdr.c_rdev     = htons((uint16_t)(rdev & 0xFFFF));
hdr.c_mtime    = htonl((uint32_t)(mtime & 0xFFFFFFFF));
hdr.c_namesize = htons((uint16_t)(strlen(name) + 1));
hdr.c_filesize = htonl((uint32_t)size);

fwrite(&hdr, 1, sizeof(hdr), fp);
fwrite(name, 1, strlen(name) + 1, fp);
/* 随后按 2 字节对齐(见 3.3),再写文件数据 */

读取时自动识别字节序——先按本机字节序读 magic,不匹配则整体交换:

uint16_t magic;
fread(&magic, 2, 1, fp);

int byte_swap = 0;
if (magic != 070707) {
    magic = (uint16_t)((magic >> 8) | (magic << 8));  /* 交换字节序 */
    byte_swap = 1;
}
if (magic != 070707)
    error("not a bin cpio archive");
/* 后续 16 位字段按 byte_swap 决定是否交换 */

4.3 问题与局限性

  1. 字节序依赖:小端机器(如 VAX、x86)写出的归档与大端机器
    (如 68000、SPARC)不兼容。虽然读取端可以通过 magic 试探补偿,
    但跨平台交换仍不可靠;
  2. 字段过窄:inode/uid/gid/dev/nlink 都是 16 位,最大 65535;
    大磁盘文件系统的 inode 编号很容易超过;
  3. 文件大小上限:32 位 filesize,按有符号解释最大约 2GB
    (GNU cpio 标注 bin 单文件上限 2147483647 字节);
  4. 时间戳 32 位:2038 年问题(与所有 32 位 time_t 相同)。

这些限制促使 1980 年代出现 ASCII 字符格式(odc),1990 年代又被 newc 全面取代。如今 bin 只用于读取历史归档,GNU cpio 默认仍输出 bin(出于向后兼容),但官方明确建议改用 newc/crc。


5. 旧 ASCII 格式:odc(POSIX cpiop)

5.1 历史与动机

odc(old character,也称 portable old ASCII)在 System III 时期(约 1980 年)出现,是 cpio 对可移植性问题的第一次回应:把数字全部改为 ASCII 八进制文本,彻底摆脱机器字节序。它是 IEEE 1003.1-1988(POSIX.1)唯一标准化的 cpio 格式(即 cpiop),也是 GNU cpio 的 -H odc、pax 的 -x cpio 所生成的格式。

5.2 数据结构(76 字节头)

偏移 长度 字段 说明
0 6 magic 070707
6 6 dev 设备号(八进制,18 位)
12 6 ino inode 编号(18 位)
18 6 mode 文件类型 + 权限
24 6 uid 属主(18 位)
30 6 gid 属组(18 位)
36 6 nlink 链接数(18 位)
42 6 rdev 特殊文件设备号(18 位)
48 11 mtime 修改时间(八进制,33 位)
59 6 namesize 文件名长度(含 NUL,18 位)
65 11 filesize 文件数据长度(八进制,33 位)

共 76 字节,文件名后无对齐填充,数据紧跟文件名。

各 6 位字段不足时高位截断(dev/rdev 只保留低 18 位),mtime/filesize 为 11 位八进制(33 位)。通用的构造方式:

char hdr[76];

snprintf(hdr, sizeof(hdr),
         "070707"
         "%06o%06o%06o%06o%06o%06o%06o"  /* dev ino mode uid gid nlink rdev */
         "%011lo%06o%011lo",             /* mtime namesize filesize */
         (unsigned)(dev   & 0x3FFFF),
         (unsigned)(ino   & 0x3FFFF),
         (unsigned)(mode  & 0x3FFFF),
         (unsigned)(uid   & 0x3FFFF),
         (unsigned)(gid   & 0x3FFFF),
         (unsigned)(nlink & 0x3FFFF),
         (unsigned)(rdev  & 0x3FFFF),
         (unsigned long)(mtime & 0x1FFFFFFFFL),
         (unsigned)(strlen(name) + 1),
         (unsigned long)(size & 0x1FFFFFFFFL));

fwrite(hdr, 1, sizeof(hdr), fp);
fwrite(name, 1, strlen(name) + 1, fp);
fwrite(data, 1, size, fp);   /* odc 无对齐,数据紧跟文件名 */

解析端按同样偏移读取定长字段,用 strtol(..., 8) 或 sscanf(..., "%6o", ...) 逐字段还原。

5.3 问题与局限性(为什么被 newc 取代)

  1. 18 位字段上限:6 位八进制 = 18 位,dev/ino/uid/gid/nlink/rdev
    最大 262143。90 年代磁盘容量暴增后,inode 编号轻松突破;
  2. 33 位文件大小上限:11 位八进制 = 33 位,单文件最大约
    8GB(8589934591 字节),当年足够,如今偏小;
  3. 解析成本:每个字段都要做文本→整数的转换,且长度不定
    (要 trim 前导零);
  4. 无校验:任何位翻转都会静默产生错误文件(与 bin 相同)。

odc 的优势是完全可移植、纯文本可读、兼容所有 POSIX 实现,因此在需要最大互操作性的场合(如 POSIX pax、跨平台分发)至今仍被使用,GNU cpio 手册将其列为推荐的互操作格式之一。


6. 新 ASCII 格式:newc(SVR4,magic 070701)

6.1 历史与动机

newc(new character,SVR4 便携格式)随 System V Release 4 (1989 年)引入,是 odc 的全面升级,专门解决旧格式的三大硬伤:inode 上限 65535/262143、单文件上限 2/8GB、设备号无法拆分。

改进要点:

  1. 字段全部改为 8 位十六进制(32 位整数),inode 上限从 26 万
    提升到 40 亿(2^32 - 1),足以覆盖现代文件系统;
  2. dev 拆分为 devmajor / devminor,rdev 同理,彻底解决设备号
    编码问题;
  3. 4 字节对齐,让头与数据在 32 位机器上自然对齐,加快读写;
  4. namesize 显式化:文件名长度单独存储,且规定含结尾 NUL,
    解析更简单、无歧义;
  5. 数据字段按大端十六进制书写,与平台无关。

6.2 数据结构(110 字节头)

偏移 长度 字段 说明
0 6 magic 070701(newc)/ 070702(crc)
6 8 ino inode 编号(十六进制,32 位)
14 8 mode 文件类型 + 权限
22 8 uid 属主
30 8 gid 属组
38 8 nlink 链接数
46 8 mtime 修改时间(秒)
54 8 filesize 文件数据长度
62 8 devmajor 设备号主部
70 8 devminor 设备号次部
78 8 rdevmajor 特殊文件设备号主部
86 8 rdevminor 特殊文件设备号次部
94 8 namesize 文件名长度(含结尾 NUL)
102 8 check 校验和(crc 格式用,newc 为 0)

共 110 字节。文件名(含 NUL)后与文件数据后均按 4 字节对齐。

通用的构造方式(逐字段拼接 8 位十六进制字符串,不足补零):

char hdr[110];

snprintf(hdr, sizeof(hdr),
         "070701"                            /* 或 "070702"(crc) */
         "%08x%08x%08x%08x%08x"             /* ino mode uid gid nlink */
         "%08x%08x"                         /* mtime filesize */
         "%08x%08x%08x%08x"                 /* devmaj devmin rdevmaj rdevmin */
         "%08x%08x",                        /* namesize check */
         ino, mode, uid, gid, nlink,
         (unsigned)(mtime & 0xFFFFFFFF),
         (unsigned)(size & 0xFFFFFFFF),
         devmajor, devminor, rdevmajor, rdevminor,
         (unsigned)(strlen(name) + 1),
         checksum);                         /* newc 填 0,crc 填校验和 */

fwrite(hdr, 1, sizeof(hdr), fp);
fwrite(name, 1, strlen(name) + 1, fp);
skip_padding(fp, 110 + strlen(name) + 1, 4);  /* 4 字节对齐(见 3.3) */

fwrite(data, 1, size, fp);
skip_padding(fp, size, 4);

解析端按同样偏移读取定长字段,用 sscanf(..., "%8x", &v) 逐字段还原。

6.3 限制

  • 单文件最大 4GB(filesize 为 32 位,4294967295 字节);
  • 无任何完整性校验(这是 crc 格式存在的理由);
  • 文件名字段 namesize 为 32 位,实际够用。

6.4 为什么 newc 是当代事实标准

Linux 内核的 initramfs(初始根文件系统)规定使用 newc 格式:内核解压 cpio 归档后直接作为 tmpfs 挂载,而内核的 gen_init_cpio 工具、dracut / mkinitcpio / busybox cpio 均以 newc 为默认。由于固件、嵌入式系统的 rootfs/ramdisk 普遍走 initramfs 链路,newc 已成为嵌入式与固件场景的事实标准格式。


7. 带校验的 newc:crc(magic 070702)

7.1 历史与动机

crc 格式与 newc 同时由 SVR4 引入,唯一区别是把 110 字节头中最后的 check 字段(偏移 102,8 位十六进制)从 0 改为文件数据的校验和,用于检测磁带坏块、网络传输错误等导致的静默数据损坏。GNU cpio 称之为 Sum32。

注意:这个"校验和"不是真正的 CRC(循环冗余校验),而是把文件数据的每个字节直接相加后取 32 位低位的和。它实现极简(一个累加循环),能捕获大多数随机位翻转与整块丢失,但对抗结构性错误的能力弱于 CRC32;名称沿用了"crc"的叫法,纯属历史命名。

7.2 校验算法

写入端对文件数据逐字节求和(32 位溢出自然取模 2^32),写入 check 字段:

uint32_t checksum = 0;
for (size_t i = 0; i < size; i++)
    checksum += data[i];
/* crc 格式:check = checksum;newc 格式:check = 0 */

读取端重新求和比对,不一致即判定归档损坏:

uint32_t sum = 0;
for (size_t i = 0; i < size; i++)
    sum += data[i];

if (sum != hdr_checksum)
    error("crc mismatch: %s", name);

/* 注意:GNU cpio 对符号链接与硬链接条目不做求和校验 */

与 GNU cpio 保持一致的细节:符号链接与硬链接条目跳过校验(它们的 data 是路径字符串而非文件内容,GNU 实现不做求和)。

7.3 何时选 crc vs newc

场景 推荐 理由
内核 initramfs newc 内核要求/约定,校验无意义(有 gzip 完整性兜底)
磁带/慢速介质备份 crc 能发现介质错误
网络传输(ftp/管道) crc 能发现传输损坏
最小体积、最大兼容 newc 体积更小、实现更广

8. HP-UX 变体:hpbin 与 hpodc

8.1 由来

HP-UX 的 cpio 在存储字符/块设备节点时与众不同:它把设备号(major/minor)放在 filesize 字段中,而不是 rdev 字段,且数据为空。GNU cpio 因此单独提供两个格式名:

  • hpbin:HP-UX 旧二进制格式(设备文件存储方式不同);
  • hpodc:HP-UX 旧 ASCII 格式(设备文件存储方式不同)。

GNU cpio 与 libarchive 等主流实现均以这两个格式名识别并读写。

8.2 读取逻辑

hpodc 读取时,若条目是字符/块设备,从 filesize 字段拆出设备号(高 16 位为 major、低 16 位为 minor),且不读取数据:

if ((mode & 0xF000) == 0x2000 || (mode & 0xF000) == 0x6000) {
    /* HP 格式把设备号编码在 filesize 字段中 */
    uint16_t dev_major = (uint16_t)((filesize >> 16) & 0xFFFF);
    uint16_t dev_minor = (uint16_t)( filesize        & 0xFFFF);
    data = NULL;                        /* 无文件数据 */
}

hpbin 的处理方式相同(把设备号编码进 32 位 filesize 字段)。写入端在 HP 格式下反向操作:把设备号编码进 filesize,rdev 字段置零。

这类变体提醒我们:"cpio" 是一个格式家族而非单一格式,实现者必须逐格式处理这些 vendor 差异。


9. cpio 与 tar 的对比

cpio 与 tar 是 Unix 历史上两大归档格式,命运交织:cpio(1977)比 tar(1979)更早诞生,但 tar 因随 7th Edition 一同发布而更广为人知。POSIX 曾长期为"标准里该留谁"争论,最终 2001 年两者都被 pax 取代。核心差异:

维度 cpio tar
诞生 1977(PWB/UNIX,Dick Haight) 1979(7th Edition Unix)
头大小 26~110 字节 固定 512 字节/块
块结构 无块概念,条目紧凑串联 512 字节块,末尾两块全零
流式/管道 天生为管道设计(find | cpio -o) 可流式,但块结构更重
特殊文件 完整支持设备节点、FIFO、socket 传统 tar 无法表达设备节点
inode/链接数 保留 inode、nlink、硬链接组 不保存 inode
设备号 dev/rdev 字段显式存储 无标准字段
字节序问题 仅 bin 格式有(已废弃) 无(数字为 ASCII)
单文件上限 bin 2GB / odc 8GB / newc 4GB v7 8GB / ustar 8GB(扩展可更大)
文件名长度 newc 支持 32 位长度 v7 仅 100 字符(ustar 前缀+255)
长文件名/大文件扩展 无标准扩展(换格式解决) ustar 前缀字段、pax 扩展头
POSIX 命运 命令在 2001 年移除,格式留作 pax 交换 同左
现状 内核 initramfs、固件、嵌入式 软件分发、系统备份最普遍

cpio 的优势:头小省磁带空间;天然管道化;完整保留设备文件与 inode,适合整机/系统盘备份(/dev、/proc 下的节点);无需预知总大小,可无限追加。

tar 的优势:512 字节块可独立定位、损坏容忍度更高;后来通过 ustar/pax 获得长文件名、大文件(8GB+)、扩展属性(xattr/ACL)等能力,生态与工具链远大于 cpio;POSIX 的 pax 也以 ustar 为默认。

实用经验:

  • 打包普通软件、源码分发 → 首选 tar/ustar/pax;
  • 打包 initramfs、设备树、固件 rootfs → 用 newc(内核约定);
  • 需要跨平台最小依赖 → odc(POSIX 兼容性最好)。

10. 现代应用场景

10.1 Linux 内核 initramfs(最核心)

Linux 内核把初始根文件系统做成 gzip 压缩的 newc cpio 归档:gen_init_cpio 脚本从文件列表生成 newc 归档,usr/ 目录下的 initramfs 在编译期被打进内核镜像;启动时内核解压后直接挂为 tmpfs。dracut、mkinitcpio、busybox initramfs 工具全部围绕 newc 工作。任何需要"引导用最小文件系统"的固件/嵌入式场景都离不开 cpio。

10.2 固件与嵌入式

  • Android boot.img:内核 + 压缩的 ramdisk(cpio 归档);
  • U-Boot:bootm 加载的 ramdisk 常为 newc/odc cpio;
  • 路由器/交换机固件:常见 squashfs + cpio 组合或纯 cpio rootfs;
  • UEFI 平台:部分厂商用 cpio 打包诊断镜像与驱动目录。

10.3 系统工具链

  • GNU cpio:备份脚本(find | cpio -o -H crc \| gzip)仍在广泛使用;
  • libarchive / bsdcpio:Python tarfile 之外的另一互操作选择;
  • 内核/initramfs 调试:cpio -itv < initramfs.gz 直接列出内容;
  • POSIX pax:pax -w -x cpio 生成 odc 互操作归档。

10.4 谁在生产中还在"写"cpio?

工具/场景 默认或常用格式
Linux gen_init_cpio newc
dracut / mkinitcpio / busybox newc
GNU cpio -o(默认) bin(建议 -H crc)
bsdcpio -o(默认) odc
pax -x cpio odc

11. 安全注意点

cpio 格式本身极简,但解包时存在与 tar 类似的经典风险,实现库必须处理:

  1. 路径穿越(Path Traversal):恶意归档的条目名可含 ../ 或绝对路径(/etc/passwd),若直接拼接到目标目录,会覆盖归档外文件。安全实现应拒绝绝对路径与含 .. 段的条目名,或按策略剥离后解包;
  2. 符号链接逃逸:若先解出指向目录外的符号链接,后续条目再通过该链接写入,可绕过路径检查。安全实现要么拒绝链接目标含 ..,要么在每次写入前重新校验真实路径;
  3. 设备节点与权限:归档可含设备节点和 setuid 文件,解包时若以特权身份执行,等于给攻击者"制造设备/提升权限"的能力。生产工具默认应跳过设备节点或以非特权身份解包;
  4. zip-bomb 式资源耗尽:恶意 filesize 巨大或条目数量极多,读取端要防止一次性分配超大缓冲,采用流式/分块读取并限制缓冲上限;
  5. 头部畸形输入:namesize 为 0、filesize 与对齐填充不符、TRAILER!!! 缺失等,健壮实现必须显式校验,非法即停止解析并报错;
  6. 校验和:crc 格式的求和校验能发现传输损坏,但不能防篡改(求和极易被构造为相同值),完整性校验仍需配套 gzip 或签名。

12. 结语

从 1977 年贝尔实验室的磁带备份工具,到 2026 年仍是 Linux 启动链路与固件生态的基石,cpio 用五十年初步显示了一个道理:极简的流式格式拥有最强的生命力。它被 POSIX 移出命令标准,却因内核 initramfs 的约定而"曲线复活";它被 tar 在通用领域超越,却在设备文件、inode 保留、管道化这些 tar 的短板上无可替代。cpio文件格式的演进史,就是"可移植性"与"容量上限"这两个归档格式永恒主题的一部浓缩史。


13. 参考资料

  • GNU cpio 手册(info cpio / man cpio):格式选项与上限说明
  • cpio(5) 手册页:cpio 归档格式规范(字段布局权威参考)
  • IEEE Std 1003.1-1988 / 1003.1-2001(POSIX):odc 与 pax 交换格式定义
  • libarchive Wiki:FormatCpio(格式历史与变体综述)
  • libarchive cpio.5(FreeBSD/Ubuntu man 仓库版:PWB 历史考证)
  • Linux 内核文档:Documentation/driver-api/early-userspace/(initramfs 规范)
请前往 登录/注册 即可发表您的看法…