gates / gates (push) Successful in 31s
On a box whose guest has one data volume the two built-in fill targets, "/" (the controller's overlay on the Docker data volume) and /mnt/sys_drive, are one filesystem. The target list was de-duplicated by path only, so one fill would send two warnings under two names. fillTargets now keeps one target per filesystem; the first stays (app area, system area, drives). The identity is system.FilesystemKey, not st_dev alone: inside the container "/" is an overlay with its own device number, so an overlay path takes the device of a Docker-kept file on the data-root (/etc/hostname, /etc/hosts, /etc/resolv.conf). Measured read-only on demo-hp 9201/9202 and the N100 on 2026-11-10. Tests: TestR1371_* (3), TestFilesystemKey_* (2). Red seen three times. No version, no image, no floor: it waits for a release session. Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
60 lines
3.1 KiB
Go
60 lines
3.1 KiB
Go
package system
|
|
|
|
// Which filesystem does a path lie on? — the identity the fill watch de-duplicates by (R-1371).
|
|
//
|
|
// WHY st_dev ALONE IS NOT THE ANSWER. Inside the controller's container "/" is an OVERLAY. An overlay has its own
|
|
// device number and its own filesystem id, and both differ from the filesystem it lies on — while statfs reports that
|
|
// filesystem's size and free space. Measured 2026-11-10 on demo-hp (guests 9201, 9202) and the N100 (felhom.eu
|
|
// documentation/audits/space-list-2026-11-10/evidence/extra/fs-identity.txt):
|
|
//
|
|
// / type=overlayfs dev=139 blocks=18016108 free=10674453
|
|
// /mnt/sys_drive type=ext2/ext3 dev=64519 blocks=18016108 free=10674452
|
|
// /etc/hostname type=ext2/ext3 dev=64519 (the same on /etc/hosts and /etc/resolv.conf)
|
|
//
|
|
// One filesystem, two device numbers. The size-and-free numbers cannot be the identity either: the two `free` values
|
|
// above are from ONE read loop and already differ by one block — a filesystem that is filling is being written.
|
|
//
|
|
// THE RULE. A path that is not on an overlay is identified by its device number. A path on an overlay is identified by
|
|
// the device number of a file Docker itself keeps on its data-root and bind-mounts into every container
|
|
// (dockerDataRootAnchors): the container's overlay lies on that same data-root. If no anchor can be read, there is NO
|
|
// key — and a caller must then treat the path as a filesystem of its own (one warning too many, never one too few).
|
|
//
|
|
// LIMITS, stated: this holds for a container that Docker runs with Docker's own /etc files (the product's only
|
|
// shape). A container whose /etc/hostname is bind-mounted from another filesystem by hand would get the wrong key.
|
|
|
|
// dockerDataRootAnchors are the files Docker writes under <data-root>/containers/<id>/ and bind-mounts into the
|
|
// container. Any one of them names the data-root's real device.
|
|
var dockerDataRootAnchors = []string{"/etc/hostname", "/etc/hosts", "/etc/resolv.conf"}
|
|
|
|
// FilesystemKey returns a number that is equal for two paths exactly when they lie on the same filesystem, as far as
|
|
// this process can tell, and ok=false when it cannot tell.
|
|
func FilesystemKey(path string) (key uint64, ok bool) {
|
|
key, ok = filesystemKeyWith(path, statDev, isOverlayFS, dockerDataRootAnchors)
|
|
debugf("[DEBUG] [system] FilesystemKey: path=%q key=%d ok=%v", path, key, ok)
|
|
return key, ok
|
|
}
|
|
|
|
// filesystemKeyWith is the rule with its three inputs injected (the tests' seam): dev reads a path's device number,
|
|
// overlay says whether a path lies on an overlay; both report ok=false for a path they cannot read.
|
|
func filesystemKeyWith(path string, dev func(string) (uint64, bool), overlay func(string) (isOverlay, ok bool), anchors []string) (uint64, bool) {
|
|
if path == "" {
|
|
return 0, false
|
|
}
|
|
isOv, ok := overlay(path)
|
|
if !ok {
|
|
return 0, false
|
|
}
|
|
if !isOv {
|
|
return dev(path)
|
|
}
|
|
for _, a := range anchors {
|
|
if aOv, ok := overlay(a); !ok || aOv {
|
|
continue // unreadable, or itself on the overlay (not bind-mounted): it names nothing
|
|
}
|
|
if d, ok := dev(a); ok {
|
|
return d, true
|
|
}
|
|
}
|
|
return 0, false
|
|
}
|