CPIO archive reader and writer in pure OCaml
README.md

cpio #

CPIO archives in the newc (SVR4) format, in pure OCaml.

Linux boots from an initramfs, which it unpacks from one or more newc CPIO archives. The usual recipe is to stage a directory and run cpio or gen_init_cpio on it, which needs those tools on the host, and root for the device nodes. With this library the archive is just a list of OCaml values: regular files, directories, symlinks and device nodes, each with its own mode and mtime.

Reading works too, including the base-plus-overlay concatenations the kernel accepts, and a multi-gigabyte initramfs can be walked in constant memory to find where each file's data sits. Before extracting an archive you did not build, check it against a policy that rejects path traversal, symlinks escaping the tree, device nodes and setuid bits.

Installation #

$ opam repo add samoht https://tangled.org/gazagnaire.org/opam-overlay.git
$ opam install cpio

Usage #

Creating an archive #

let entries = [
  Cpio.directory "root";
  Cpio.directory "root/bin";
  Cpio.regular ~name:"root/bin/init" ~perm:0o755 "#!/bin/sh\nexec /bin/sh";
  Cpio.symlink ~name:"root/bin/sh" ~target:"/bin/busybox" ();
]

let archive = Cpio.to_string entries

Reading an archive #

let () =
  match Cpio.of_string archive with
  | Ok entries ->
      List.iter
        (fun e ->
          Fmt.pr "%s (%d bytes)@." e.Cpio.header.name
            (String.length e.Cpio.data))
        entries
  | Error msg -> Fmt.epr "Error: %s@." msg

Creating an initramfs #

let init_script = "#!/bin/sh\nexec /bin/sh"

let initramfs =
  Cpio.to_string
    [
      Cpio.directory "dev";
      Cpio.device ~char:true ~major:1 ~minor:3 "dev/null";
      Cpio.device ~char:true ~major:1 ~minor:5 "dev/zero";
      Cpio.device ~char:true ~major:5 ~minor:1 "dev/console";
      Cpio.directory "proc";
      Cpio.directory "sys";
      Cpio.regular ~name:"init" ~perm:0o755 init_script;
    ]

Checking an untrusted archive #

Cpio.validate_all runs every entry through a policy and stops at the first violation. Cpio.default_policy rejects absolute paths, .. components, device nodes, setuid bits, and symlinks that leave the tree.

Format #

Each newc entry is a 110-byte header of ASCII hex fields, the pathname (NUL-terminated, padded to 4 bytes), then the data (padded to 4 bytes). An entry named TRAILER!!! marks the end.

  • cpio-rs, a Rust library mostly for writing archives.
  • cpio_reader, a no_std Rust reader.
  • rcore-os/cpio, a freestanding Rust reader.
  • GNU cpio, the reference implementation.

References #

License #

ISC. See LICENSE.md.