mounts: list says an armed automount waits, not mounted, as the look already judges it (issue 437)
mesh/merge-gate pass: builds mounts → shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery delivered
mesh/merge-gate pass: builds mounts → shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery delivered
The autofs line an armed automount leaves in mountinfo made List say "mounted" while nothing was mounted. One judgement, Mounted.IsAutomount, now serves List, the look and the checks before a mount.
This commit is contained in:
@@ -593,7 +593,7 @@ func (a *Applier) judge(p Pair, states map[string]map[string]string, mounted map
|
||||
}
|
||||
mnt, auto := states[p.MountUnit], states[p.AutoUnit]
|
||||
cur, isMounted := mounted[p.Mountpoint]
|
||||
isMounted = isMounted && cur.Type != "autofs"
|
||||
isMounted = isMounted && !cur.IsAutomount()
|
||||
switch {
|
||||
case !there && p.Occasional:
|
||||
return StateAbsent, "not present: " + tried.Error()
|
||||
|
||||
@@ -158,7 +158,7 @@ func kindOf(what, fstype string) string {
|
||||
// there, or nothing when it is, or nothing is mounted there but an automount.
|
||||
func (m *Machine) notOwnSource(kind, what, point string, mounted map[string]Mounted) error {
|
||||
mt, there := mounted[point]
|
||||
if !there || mt.Type == "autofs" {
|
||||
if !there || mt.IsAutomount() {
|
||||
return nil
|
||||
}
|
||||
if kind != "" && m.sourceIs(kind, what, mt) {
|
||||
@@ -172,7 +172,7 @@ func (m *Machine) notOwnSource(kind, what, point string, mounted map[string]Moun
|
||||
func (m *Machine) mountpointFree(p Pair, own map[string]bool, mounted map[string]Mounted) error {
|
||||
ours := own[p.MountUnit] && own[p.AutoUnit]
|
||||
if mt, there := mounted[p.Mountpoint]; there {
|
||||
if mt.Type == "autofs" {
|
||||
if mt.IsAutomount() {
|
||||
if ours {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -135,6 +135,14 @@ type Mounted struct {
|
||||
Options string `json:"options,omitempty"`
|
||||
}
|
||||
|
||||
// IsAutomount says whether this line is only the autofs mount an armed automount leaves at its point.
|
||||
// Nothing is mounted there yet: the share is mounted over it at first use, and that line then wins in
|
||||
// ParseMountinfo. Every judgement of "mounted" asks this one question, so List, the look and the checks
|
||||
// before a mount cannot disagree (novox/hq issue 437).
|
||||
func (mt Mounted) IsAutomount() bool {
|
||||
return mt.Type == "autofs"
|
||||
}
|
||||
|
||||
// ParseMountinfo reads /proc/self/mountinfo: per line the mount point (field 5), and after the " - "
|
||||
// separator the type, the source and the superblock options. The last mount at a point wins, as it
|
||||
// is the one a path resolves to.
|
||||
@@ -400,7 +408,7 @@ func (mt *Mount) say() string {
|
||||
if mt.MountUnit.Result != "" && mt.MountUnit.Result != "success" {
|
||||
state += ", " + mt.MountUnit.Result
|
||||
}
|
||||
case mt.MountedNow != nil:
|
||||
case mt.MountedNow != nil && !mt.MountedNow.IsAutomount():
|
||||
state = "mounted"
|
||||
case mt.Automount != nil && mt.Automount.Active == "active":
|
||||
state = "not mounted, waits for its first use (automount " + mt.Automount.Sub + ")"
|
||||
|
||||
@@ -258,6 +258,35 @@ func TestListReadsTheRootMountsUnit(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// The desktop's recalbox share as it stood on 2026-10-11 (novox/hq issue 437): its automount armed
|
||||
// (active, waiting), its mount unit inactive, and in mountinfo only the autofs line the armed automount
|
||||
// leaves at the point, with the superblock options the desktop showed. Nothing is mounted there, so it
|
||||
// must be said as waiting for its first use, never as mounted. /mnt/backup in TestList keeps the other
|
||||
// half: an nfs4 line over the autofs line is mounted.
|
||||
func TestListSaysAnArmedAutomountIsNotMounted(t *testing.T) {
|
||||
f := newFake(t)
|
||||
f.file("/etc/fstab", "//192.0.2.176/Share /mnt/recalbox cifs username=root,password=s3cret,iocharset=utf8,sec=ntlmssp 0 0\n")
|
||||
f.file("/proc/self/mountinfo", "22 1 259:3 / / rw,relatime shared:1 - ext4 /dev/nvme0n1p3 rw\n"+
|
||||
"40 22 0:50 / /mnt/recalbox rw,relatime shared:30 - autofs systemd-1 rw,fd=116,pgrp=1,timeout=600,minproto=5,maxproto=5,direct,pipe_ino=68285996\n")
|
||||
f.answers["systemctl list-units --type=mount,automount --all --no-legend --plain --no-pager"] = desktopListUnits
|
||||
names := strings.Join(ParseListUnits(desktopListUnits), " ")
|
||||
f.answers["systemctl show -p "+ShowProperties+" --no-pager -- "+names] =
|
||||
"Id=mnt-recalbox.automount\nWhere=/mnt/recalbox\nResult=success\nActiveState=active\nSubState=waiting\n" +
|
||||
"FragmentPath=/etc/systemd/system/mnt-recalbox.automount\nUnitFileState=enabled\n\n" +
|
||||
"Id=mnt-recalbox.mount\nWhere=/mnt/recalbox\nWhat=//192.0.2.176/Share\nType=cifs\n" +
|
||||
"Options=credentials=/var/lib/mounts/smb-recalbox.credentials,iocharset=utf8,_netdev,nofail,uid=1000,gid=1000\n" +
|
||||
"Result=success\nActiveState=inactive\nSubState=dead\nFragmentPath=/etc/systemd/system/mnt-recalbox.mount\n" +
|
||||
"TimeoutUSec=10s\nUnitFileState=enabled-runtime\n"
|
||||
r := f.machine().List(false, "recalbox")
|
||||
if len(r.Said) != 1 {
|
||||
t.Fatalf("said %q, want one line for /mnt/recalbox", r.Said)
|
||||
}
|
||||
want := "/mnt/recalbox (cifs from //192.0.2.176/Share): not mounted, waits for its first use (automount waiting); from /etc/fstab line 1, no nofail"
|
||||
if r.Said[0] != want {
|
||||
t.Errorf("said %q,\nwant %q", r.Said[0], want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListNarrowed(t *testing.T) {
|
||||
m := listFake(t).machine()
|
||||
if r := m.List(true, ""); len(r.Mounts) != 3 {
|
||||
|
||||
Reference in New Issue
Block a user