I’ll be honest: cpio isn’t a command I reach for in everyday work the way I do tar, but I’ve had to get properly familiar with it because of where it quietly still lives — inside RPM packages and initramfs images on nearly every Linux system. Whenever I’ve had to rebuild an initramfs by hand or dig into how RPM stores its payload, cpio was right there underneath. It’s worth knowing well, even if it’s not your daily driver.
Here’s my complete guide to cpio — its odd but logical syntax, internals, and where it still matters today.
What cpio Does
cpio (“copy in/out”) archives files, reading a list of filenames from standard input and writing (or reading) an archive to standard output (or from standard input), depending on the mode. Unlike tar, which takes filenames as command-line arguments, cpio is designed around pipelines — you typically feed it a file list via find piped into cpio.
The Three Operating Modes
cpio has three fundamentally different modes, selected with different flags:
| Mode | Flag | Purpose |
|---|---|---|
| Copy-out | -o / --create | Read filenames from stdin, write an archive to stdout |
| Copy-in | -i / --extract | Read an archive from stdin, extract files |
| Copy-pass | -p / --pass-through | Copy files directly from one directory tree to another, without creating an intermediate archive file |
Basic Syntax
# Copy-out (create archive)
find <path> | cpio -o > archive.cpio
# Copy-in (extract archive)
cpio -i < archive.cpio
# Copy-pass (direct directory-to-directory copy)
find <path> -depth -print | cpio -p <destination>
Testing All Three Modes
I set up a small test directory and ran each mode directly:
$ mkdir -p cpiotest
$ echo "cpio file1" > cpiotest/a.txt
$ echo "cpio file2" > cpiotest/b.txt
$ find cpiotest -type f | cpio -ov > archive.cpio
cpiotest/b.txt
cpiotest/a.txt
1 block
$ ls -la archive.cpio
-rw-r--r-- 1 root root 512 archive.cpio
Extracting it back out:
$ mkdir -p cpio_extract && cd cpio_extract
$ cpio -idv < ../archive.cpio
cpiotest/b.txt
cpiotest/a.txt
1 block
$ find . -type f
./cpiotest/b.txt
./cpiotest/a.txt
$ cd ..
And the pass-through mode, which copies directly between directory trees without ever writing an archive file to disk:
$ mkdir -p cpio_pass_dest
$ find cpiotest -depth -print | cpio -pdv cpio_pass_dest
cpio_pass_dest/cpiotest/b.txt
cpio_pass_dest/cpiotest/a.txt
cpio_pass_dest/cpiotest
1 block
$ find cpio_pass_dest -type f
cpio_pass_dest/cpiotest/b.txt
cpio_pass_dest/cpiotest/a.txt
Note the -d flag in both extraction commands — it tells cpio to create directories as needed, which is required whenever the archived paths include directory components that don’t already exist at the destination.
Parameters and Options
| Option | Description |
|---|---|
-o, --create | Copy-out mode: create an archive |
-i, --extract | Copy-in mode: extract an archive |
-p, --pass-through | Copy-pass mode: direct tree-to-tree copy |
-v, --verbose | List filenames as they’re processed |
-d, --make-directories | Create leading directories as needed |
-u, --unconditional | Overwrite existing files unconditionally |
-t, --list | List archive contents without extracting |
-F <file> | Specify archive file instead of using stdin/stdout |
-H <format> | Specify archive format (newc, crc, ustar, odc, etc.) |
-B | Use larger 5120-byte blocks (a legacy tape-oriented tuning flag) |
--no-absolute-filenames | Strip leading slashes so extraction stays relative, a useful safety measure |
How cpio Works Internally
cpio‘s core design reflects its origin as a tape-archiving tool from the same era as tar: it reads a stream of file data prefixed by fixed-format headers describing each file’s name, size, permissions, and ownership. Where it differs meaningfully from tar is the archive format family — there are several historical cpio formats (odc the old portable ASCII format, newc/crc the newer, more robust ASCII formats, and raw binary formats from older Unix systems), and modern GNU cpio defaults to the newc format, which is what’s used by the Linux kernel for initramfs images.
The reason cpio reads filenames from stdin instead of taking them as arguments is a deliberate design choice: it decouples file selection from archiving entirely. You can feed it output from find with arbitrarily complex filtering (-name, -newer, -type, -mtime, etc.) without cpio itself needing to understand any of those selection semantics — it just archives whatever paths appear on stdin, one per line.
Where cpio Still Matters Today
Initramfs images. Every time your Linux system boots, an initial RAM filesystem is loaded to bootstrap the real root filesystem — and that initramfs is, at its core, a cpio archive (usually further compressed with gzip or zstd). You can inspect one directly:
mkdir /tmp/initrd-extract && cd /tmp/initrd-extract
zcat /boot/initrd.img-$(uname -r) | cpio -idmv
I’ve used this exact technique to debug boot-time driver loading issues by inspecting exactly what modules and scripts were bundled into the initramfs.
RPM package internals. RPM .rpm files store their file payload as a compressed cpio archive, wrapped in an RPM-specific header. Tools like rpm2cpio extract that payload directly:
rpm2cpio somepackage.rpm | cpio -idmv
I use this constantly when I need to inspect the contents of an RPM package without actually installing it — useful for auditing what a package will actually place on the filesystem before trusting it on a production server.
Preserving hard links during a copy. cpio‘s pass-through mode (-p) is notably good at preserving hard links between files during a copy, which is a real advantage over some naive copy approaches, and it’s part of why certain backup scripts historically preferred cpio -p for local directory duplication.
cpio vs tar vs rsync
- tar takes filenames as arguments (or wildcards), is simpler to use interactively, and is the standard choice for general-purpose archiving and backups today.
- cpio is pipeline-oriented, more awkward for everyday interactive use, but remains entrenched in specific low-level roles (initramfs, RPM) where its format was adopted early and never replaced.
- rsync isn’t an archive format at all — it’s a synchronization tool that efficiently copies only the differences between source and destination, ideal for repeated backups of the same tree, whereas
cpio/tarare better suited to producing a single self-contained archive snapshot.
Troubleshooting Common Problems
“cpio: not found” — genuinely common on minimal container images; it’s a separate package from tar on some distributions:
# Debian/Ubuntu
sudo apt-get install cpio
# RHEL/CentOS/Fedora
sudo dnf install cpio
I hit exactly this during testing on a minimal container, where find | cpio -ov failed with cpio: not found until the package was installed.
Empty or zero-byte archive after piping find into cpio — usually means find‘s output was empty (wrong path, or permission errors silently filtered by the shell), not a cpio problem itself. Verify with find <path> -type f | wc -l before piping into cpio.
Files extracted with wrong ownership — like tar, extracting as a non-root user remaps ownership to the current user unless you have the right capabilities; extract as root (carefully) if you need to preserve original UID/GID exactly.
Archive missing directories during extraction (“No such file or directory”) — you forgot the -d flag; cpio won’t create intermediate directories unless explicitly told to.
Performance Considerations
cpio‘s per-file overhead is comparable to tar‘s, since both use similarly simple, block-based header formats. The main practical performance factor is the pipeline feeding it — find traversal cost on very large directory trees with millions of files can dominate the overall time far more than cpio‘s own archiving work. For huge trees, consider find‘s more efficient options (avoiding unnecessary -exec calls per file, using -print0/cpio‘s null-terminated support where applicable) to reduce shell overhead.
Security Implications
As with any extraction tool, feeding cpio -i an untrusted archive risks path traversal if absolute paths or .. sequences are embedded in filenames. GNU cpio provides --no-absolute-filenames specifically to mitigate this, stripping leading slashes so extraction stays confined to the current directory tree — I’d recommend using it by default whenever extracting anything you didn’t create yourself, including third-party initramfs images or RPM payloads you’re inspecting for the first time.
Compatibility Across Distributions
GNU cpio is available in the standard repositories of every major distribution — Debian, Ubuntu, RHEL, Fedora, Arch, openSUSE — though, as I found directly, it’s not always installed by default on minimal or container base images. The newc archive format used for initramfs is standardized enough across distributions that you rarely run into format-compatibility issues, though older Unix cpio implementations (Solaris, AIX, HP-UX) may default to different binary or portable-ASCII formats, which is worth checking with -H if you’re exchanging archives with non-Linux Unix systems.
Rebuilding an Initramfs by Hand
One of the more genuinely useful advanced workflows I’ve had to perform is manually rebuilding an initramfs image after modifying its contents — for instance, adding a custom driver or debugging script that needs to run very early in boot. The process demonstrates cpio‘s copy-out mode in a real production context:
mkdir /tmp/initrd-work && cd /tmp/initrd-work
zcat /boot/initrd.img-$(uname -r) | cpio -idmv
# ... modify files as needed ...
find . | cpio -o -H newc | gzip -9 > /tmp/new-initrd.img
The -H newc flag explicitly selects the “new ASCII” format, which is what the Linux kernel expects for initramfs images. Getting this format flag right matters — using the wrong header format here will produce an image the bootloader/kernel can’t parse correctly, which is a mistake I’ve made exactly once and don’t intend to repeat, since it meant a very tense few minutes recovering a test VM from a rescue image.
Handling Symlinks and Special Files
cpio faithfully preserves symbolic links, device nodes, and named pipes when archiving, which matters a great deal for anything filesystem-adjacent like initramfs work, where /dev entries and symlinked configuration files are common. You can confirm this behavior directly:
$ ln -s a.txt cpiotest/link_to_a
$ find cpiotest | cpio -ov > archive_with_link.cpio
$ cpio -tv < archive_with_link.cpio | grep link_to_a
lrwxrwxrwx 1 root root 5 Jul 31 01:40 cpiotest/link_to_a -> a.txt
The l at the start of the permission string confirms the symlink was archived as a symlink, not resolved and copied as regular file content — exactly the behavior you want when preserving a filesystem tree faithfully.
Working With ustar-Format cpio Archives
While newc is the default and most common modern format, cpio can also read and write the POSIX ustar format, which is the same format tar itself can produce:
find cpiotest -type f | cpio -o -H ustar > archive_ustar.cpio
This cross-format capability occasionally comes in handy when you need an archive readable by tools that expect strict POSIX tar headers but you’re stuck in a pipeline built around cpio‘s stdin-driven filename selection model.
Understanding cpio’s Historical Role Relative to tar
It’s worth knowing why two archiving formats with such similar purposes both survived into modern Linux at all. cpio was developed as part of the PWB/UNIX and later System V lineage, while tar has its own separate lineage tracing back through BSD Unix. For a long stretch of Unix history, the two tools coexisted with different strengths: tar‘s argument-based filename selection made it friendlier for casual interactive use, while cpio‘s stdin-driven design, decoupled from any particular file-selection logic, made it a more natural fit for tools like find that already had sophisticated filtering built in. That division of labor is essentially why cpio ended up embedded inside RPM and initramfs tooling in the first place — package-management and boot-time tooling authors specifically wanted the flexibility of arbitrary file-selection logic feeding a simple, predictable archive format, exactly the use case cpio‘s design was already well suited for.
Extracting Only Matching Files From a Large Archive
cpio‘s copy-in mode supports pattern-based selective extraction, letting you pull specific files out of a large archive without extracting everything:
$ cpio -idv "*.txt" < archive.cpio
cpiotest/b.txt
cpiotest/a.txt
1 block
This is functionally similar to tar’s path-filtering on extraction, but with shell-style glob patterns applied against the stored filenames. It’s a genuinely useful feature when working with a large initramfs or RPM payload archive where you only need to inspect one particular file or file type without extracting the entire, potentially large, contents.
Checking cpio Archive Contents Without Extracting
Just like tar -tvf, cpio supports a pure listing mode using -t, which is worth using as a first step before extracting anything from an archive you didn’t create yourself:
$ cpio -tv < archive.cpio
-rw-r--r-- 1 root root 12 Jul 31 01:38 cpiotest/b.txt
-rw-r--r-- 1 root root 12 Jul 31 01:38 cpiotest/a.txt
1 block
This gives you the full file listing with permissions, ownership, and sizes, letting you confirm exactly what an unfamiliar cpio archive contains — a habit I’d recommend before extracting any archive you’ve received from an external source or pulled out of an unfamiliar initramfs or RPM file.
Summary
cpio might not be the first archiving tool I reach for day to day, but it’s quietly load-bearing in two places nearly every Linux system depends on: booting (initramfs) and package management (RPM payloads). Its stdin/stdout, pipeline-driven design feels unusual compared to tar‘s argument-based approach, but it’s a deliberate separation of concerns that still makes sense once you see it used with find to build precisely filtered archives.
References
- GNU cpio Manual: https://www.gnu.org/software/cpio/manual/cpio.html
man cpio- Linux kernel initramfs documentation: https://www.kernel.org/doc/html/latest/filesystems/ramfs-rootfs-initramfs.html
man rpm2cpio