Skip to content
FreeBSDDeep Dive Published Updated 6 min readViews unavailable

FreeBSD filemon and make meta mode: Trace Build Inputs Without Mistaking It for an Audit Trail

Use FreeBSD make meta files and filemon to diagnose undeclared build inputs, check support, interpret dependency traces, and avoid treating them as security logs.

An incremental build can be wrong in two opposite ways: it can skip a target whose real input changed, or rebuild it on every run because its declared prerequisites are too broad. FreeBSD’s make meta mode can help investigate the first problem by recording what a recipe actually did. When the filemon(4) device is available, those records can also capture selected file operations from build children.

This is dependency-discovery machinery, not a security audit facility. It is useful for improving build rules and understanding an update build. It is not a complete record of every process or file access on a host.

What meta mode records

FreeBSD make accepts .MAKE.MODE=meta. It creates a .meta file for each target with the commands run and their output; when filemon(4) is available, the file can include system calls that matter to make. On a later invocation, make compares the recipe with the previous one. If the commands match, it can use the recorded input-file modification times to decide whether the target is stale even when the ordinary dependency graph says it is current.

The relationship is important: meta mode supplements declared prerequisites; it does not turn an inaccurate Makefile into a complete build specification. A build must run through the relevant inputs before there is useful history to compare. Generated files, environment-dependent inputs, network downloads, compiler behavior, and tools that do not touch inputs in an observable way still need deliberate modeling and testing.

filemon is the kernel-facing part. A parent configures the device to collect operations from its child processes. The log uses short record letters for operations such as openat, chdir, execve, fork, link, rename, and exit. Only successful calls are recorded. An openat record may lack an absolute path or may be inaccurate, and read/write records can represent one read-write open or separate opens. Treat the trace as evidence for a build rule, not as a source of exact forensic truth.

Check the exact make build before relying on it

Start with the host release and the value that the installed make advertises:

freebsd-version -kru
make -V .MAKE.PATH_FILEMON

On FreeBSD, the system make is the implementation described by the FreeBSD 15.1 manual cited below. .MAKE.PATH_FILEMON is set when that make was built with filemon support; it prints the device-node path. An empty value means that this binary has no such support. A nonempty path is not proof that the device is present, accessible to the build user, or functioning. Check the node and permissions under the policy for that host before starting a build.

Meta files can still capture commands and output without filemon, but the syscall-derived input list is unavailable. Do not turn missing tracing into a mysterious dependency failure: note that the host lacks the feature, then use a supported make build or a separate controlled trace method. make also has a nofilemon mode when an operator deliberately wants metadata without the device.

Use a disposable build to observe the workflow

The following experiment belongs in a new temporary directory on FreeBSD. Use a writable parent outside the default .MAKE.META.IGNORE_PATHS prefixes; /tmp and /var/tmp are ignored by default, so putting the test input there can make the experiment falsely appear not to track it. This example uses $HOME when it is outside those prefixes and gives the recipe an absolute input path. It creates only a tiny local Makefile and two text files. Its target reads input.txt without declaring that file as a prerequisite, which lets you check whether the installed meta/filemon path notices the otherwise-hidden input:

lab=$(mktemp -d "${HOME:?HOME must be set}/make-meta.XXXXXX") || exit 1
lab=$(realpath "$lab") || exit 1
case "$lab" in
  /dev/*|/etc/*|/proc/*|/tmp|/tmp/*|/var/run/*|/var/tmp/*)
    rmdir "$lab"
    printf 'Choose a writable parent outside .MAKE.META.IGNORE_PATHS.\n' >&2
    exit 1
    ;;
esac
cd "$lab" || exit 1
printf 'all: report.txt\n\nreport.txt:\n\t@cat "$${PWD}/input.txt" > report.txt\n' > Makefile
printf 'first value\n' > input.txt
make .MAKE.MODE=meta all
cat report.txt
sleep 1
printf 'second value\n' > input.txt
make .MAKE.MODE=meta all
cat report.txt
find "$lab" -name '*.meta' -print

The second output should contain second value when the target’s input access has been recorded and the timestamp comparison sees the changed input. If it remains first value, first verify that the correct FreeBSD make ran, inspect .MAKE.PATH_FILEMON, check device availability and permissions, and read the target’s .meta record. Do not infer that every real build will be correct because this small fixture passes; it tests one file access and one update path.

Keep the output local and inspect it before sharing. A build’s command line or captured output may contain private paths, internal hostnames, or other sensitive data. Avoid the env meta mode unless environment capture is specifically needed and approved; it can record values that ordinary command/output capture would not expose. Delete the temporary directory when finished if local retention policy permits.

Read the trace with its blind spots in mind

filemon(4) explicitly says it is not intended for security auditing. It does not track many system calls, and binaries using a non-native ABI may not be fully audited. Failed system calls are absent. Relative paths and concurrent file replacement also complicate interpretation. A missing log entry therefore does not prove that a process never attempted an operation.

make has ignore controls because tracking every observed path can make targets appear stale forever. The default .MAKE.META.IGNORE_PATHS includes /dev, /etc, /proc, /tmp, /var/run, and /var/tmp; it also supports pathname filters. Do not remove broad exclusions to silence rebuilds without understanding why those files are volatile. Prefer a narrow rule or a targeted ignore pattern, and verify that excluding the path cannot hide a real build input.

For repeatable output, keep source trees and generated output isolated and record the make version, build command, target, and whether filemon was available. Compare a clean build with an update build after changing one known input. If the trace exposes an undeclared input, add that input to the build graph or otherwise make the generation step explicit; do not rely on a stale .meta file as the only documentation of how the artifact is produced.

Meta mode is most useful when it reveals a discrepancy between a target’s declared dependencies and the files its recipe consumes. It does not replace review of the build rules, source provenance, compiler options, or output. Keep dependency evidence scoped to the build, and never repurpose it as a promise of complete host auditing. The current filemon(4) manual warns that unloading its module may panic the system; do not force-unload it as routine cleanup.

Related:

Sources:

Comments