CPIO 文件格式全面详解

CPIO 文件格式全面详解

Zi_Cai

2026-09-01 发布20 浏览 · 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 Haight1977 年编写,随 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-Bootbootm 加载的 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 规范)
请前往 登录/注册 即可发表您的看法…