FreeBSD mtree: Create and Verify Filesystem Hierarchy Manifests
Use FreeBSD mtree to define filesystem trees, compare paths and metadata, select stable digest fields, and review drift without mutating files.
FreeBSD’s mtree utility describes and compares directory hierarchies. A specification can record which paths should exist and selected properties such as object type, owner, group, permissions, size, modification time, symbolic-link target, and file digest. The same program can generate a specification from a directory tree and later report differences against it. Used carefully, mtree is a practical way to make filesystem expectations reviewable and repeatable.
An mtree comparison answers a bounded question: do the observed paths and selected attributes match this specification? It does not explain who changed a file, restore a previous version, infer intended configuration, or prove that a specification is authoritative. Those limits matter. If a baseline is generated from a tree after it has drifted, comparing against that baseline faithfully reproduces the drift. Keep the reference artifact outside the tree being measured and review its provenance.
Decide what the specification should describe
Start with one directory whose contents have a stable purpose. A service-owned static configuration tree, a test fixture, or a generated filesystem image is easier to model than all of /. A whole-system specification quickly encounters sockets, pid files, logs, package state, device nodes, runtime data, and mounted filesystems that legitimately change. A broad scan can create noise while still omitting important behavior.
Choose keywords to match the acceptance question. type distinguishes regular files, directories, links, and special objects. uid and gid can capture numeric ownership. mode records permissions. size can detect unexpected truncation or growth. sha256digest captures file content using a digest. time is often intentionally unstable and should be included only if the timestamp itself is a requirement. A metadata-only spec can tell that a file still exists with the expected mode while missing a content change; a digest-only spec can miss a wrong owner or symlink target.
Avoid relying on symbolic account names as the only stable identity when a tree moves across systems with different user databases. Numeric IDs can be more deterministic for a reproducible image, while names may be more readable for a host-specific manifest. Decide which representation is meaningful to the project and retain the user/group database assumptions with the spec.
The following creates a baseline for an intentionally stable test tree. It writes the specification outside the measured directory so that the output file does not appear as an unexpected entry:
root=/srv/app-config
spec=/var/tmp/app-config.mtree
mtree -c -p "$root" -k type,uid,gid,mode,size,sha256digest > "$spec"
sed -n '1,30p' "$spec"
The -c option generates a specification, -p selects its root, and -k chooses the properties to include. Review the generated file before treating it as a contract. Confirm that paths are relative to the selected root, that expected generated or volatile objects are handled deliberately, and that the file is readable and retained outside the directory being compared.
A specification generated from the live filesystem is a candidate baseline, not approval by itself. Compare it with a known-good image, a change review, or a documented build output. Keep prior versions under source or artifact control with a human-readable reason for changes. Otherwise a routine regeneration can convert an unexplained difference into the new accepted state.
Compare without changing the hierarchy
The default action with -f compares a filesystem hierarchy against the supplied spec. Keep the same root path and keyword set when checking. For the example baseline:
mtree -f /var/tmp/app-config.mtree -p /srv/app-config
echo $?
Read both the diagnostics and exit status. An empty output is useful only when the command actually ran against the intended root and returned success. A nonzero result should trigger classification, not automatic repair. A missing path, extra path, type change, owner change, permission change, digest mismatch, and unreadable directory require different responses.
mtree compares only the attributes present in the specification. If size was not recorded, a size change may not be reported. If a digest was not recorded, the content may differ even while metadata matches. Conversely, timestamps can vary during packaging or deployment without a meaningful content change. Explicitly document which attributes are authoritative and which were omitted to avoid unstable comparisons.
The tool also operates within the permissions and namespace visible to the process. An unprivileged invocation may not be able to inspect every directory or attribute. A mounted filesystem, symlink, or permission boundary can change what path traversal sees. Record the command, root, user, mount state, and exit status with the result so that a later comparison is reproducible.
Understand path scope and exclusions
The -p path option defines the hierarchy root. It is safer to begin with a narrow subtree than with /, especially where mounted filesystems, pseudo-filesystems, or volatile data exist beneath it. The -x option can prevent descent below mount points; an exclude file can omit known paths. Exclusions should be reviewed like the rest of the spec because they reduce what the check can observe.
Symlinks deserve deliberate treatment. By default, mtree checks the link object and its recorded target as applicable rather than recursively treating every link as a new tree root. The -L option follows all symbolic links, which can expand the scan beyond the chosen namespace or revisit external trees. Do not enable it as a generic “more complete” mode. Decide whether the intended object is the link itself or the referenced target, and use an explicit scoped root.
Special objects also need policy. Sockets and device nodes in a live service tree may be recreated dynamically, so a static spec can report legitimate differences. Device numbers may be machine-specific. A test fixture may need exact object identity, while a deployed host may care only that a required directory exists and has specific ownership. Keep different classes of requirements in separate specs rather than weakening one global spec until every mismatch is ignored.
mtree has options to modify a filesystem or a specification. The update and install modes can change ownership, mode, flags, timestamps, or spec entries depending on the flags and available fields. They are intentionally outside a read-only verification workflow. Do not add -U or -u to a comparison command because output looked inconvenient. First determine the exact discrepancy, decide the desired state, preserve a backup, and use a separately reviewed corrective process.
Use a stable digest policy
When content matters, include a digest keyword supported by the installed mtree version. FreeBSD’s mtree supports SHA-256 and other digest fields; sha256digest is a documented synonym for sha256. A consistent digest helps distinguish two files with identical size and timestamps but different content. It does not identify the change’s author or intent.
Generating digests across a very large hierarchy can be I/O intensive. Benchmark on the actual storage, schedule scans away from peak workload, and consider whether every file needs content hashing. Metadata-only checks are cheaper but answer fewer questions. For packaged releases or immutable artifacts, digest coverage is often appropriate. For a dynamic spool or cache, a stable tree contract may instead define only directory existence and selected ownership.
If a tree contains files that legitimately change at deployment, create a spec for the stable subset or maintain explicit exclusions. Do not simply omit all size and digest checks to make the report clean. That removes the ability to detect important file replacement. A useful spec documents why each excluded path or volatile attribute is excluded and who owns that decision.
Integrate into a deployment or image workflow
An effective workflow has a generation stage and a verification stage. Generate a candidate spec from a controlled build, run the same command in CI against the staged tree, review the diff, and promote the spec alongside the artifact. At deployment, verify the extracted tree before switching traffic or starting the service. Keep the spec version associated with the deployed release so that operators compare a host with the correct intended state.
For image construction, mtree can act as a manifest format alongside tools such as makefs. The manifest describes expected paths and attributes; makefs has its own semantics for what it creates from a manifest or directory tree. Test the exact toolchain and image format rather than assuming that an mtree comparison proves a bootable or semantically correct filesystem image.
When automation consumes mtree output, prefer an explicit format and a pinned FreeBSD release. Human-readable diagnostics are useful for operators but should not be scraped with fragile regular expressions. The -C output is designed for easier parsing and emits one line per path, but consumers still need to handle quoting, escaped names, and missing entries correctly. Test filenames with spaces and non-ASCII bytes if the tree can contain them.
Triage drift and document acceptance
On a mismatch, preserve the spec and output before any cleanup. Re-run a focused comparison for the affected path if the overall output is large, then inspect the object with stat, ls -ld, readlink, and a digest utility. Compare package manifests, build records, and deployment logs to find the source of the difference. Do not regenerate the baseline first; doing so destroys the evidence needed to decide whether the change is expected.
Classify each discrepancy as expected change, unintended change, environmental mismatch, or scanner limitation. Expected changes should be reflected in a reviewed spec update. Unintended changes need repair through the package manager or deployment system that owns the file, not arbitrary chmod or chown commands. Environmental mismatches can indicate that the same spec is being applied to an unsupported release or architecture.
An acceptance record should include the spec’s checksum or revision, generation source, mtree command line, FreeBSD version, hierarchy root, mount state, user identity, exit status, and any approved exclusions. The spec should have a named owner and a controlled update path. This turns mtree from a one-time command into a reproducible comparison with enough context to interpret its result.
Related:
- FreeBSD’s VFS Layer: How Multiple Filesystems Share One Interface
- UFS dump and restore on FreeBSD: Incrementals, Snapshots, and Recovery
Sources: