Merge pull request 'mounts: list reads the root mount's unit (issue 374)' (#194) from fix/374-mounts-list-reads-the-root-mounts-unit into main
This commit was merged in pull request #194.
This commit is contained in:
@@ -304,7 +304,10 @@ func (m *Machine) List(onlyNetwork bool, match string) MountsReport {
|
||||
r.Notes = append(r.Notes, fmt.Sprintf("the service manager's units were not read: %v", err))
|
||||
}
|
||||
if names := ParseListUnits(listed); len(names) > 0 {
|
||||
args := append([]string{"show", "-p", ShowProperties, "--no-pager"}, names...)
|
||||
// The names go after "--": the root mount's unit is -.mount, and systemctl reads an argument
|
||||
// that starts with a dash as an option and refuses the whole call, so every mount lost its
|
||||
// unit state (novox/hq issue 374).
|
||||
args := append([]string{"show", "-p", ShowProperties, "--no-pager", "--"}, names...)
|
||||
shown, err := m.Run(ctx, "systemctl", args...)
|
||||
if err != nil && shown == "" {
|
||||
r.Notes = append(r.Notes, fmt.Sprintf("the units' properties were not read: %v", err))
|
||||
|
||||
@@ -40,6 +40,11 @@ func (f *fake) machine() *Machine {
|
||||
Root: f.root,
|
||||
Run: func(_ context.Context, name string, args ...string) (string, error) {
|
||||
line := strings.TrimSpace(name + " " + strings.Join(args, " "))
|
||||
if name == "systemctl" {
|
||||
if err := systemctlRefuses(args); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
if out, ok := f.answers[line]; ok {
|
||||
return out, nil
|
||||
}
|
||||
@@ -49,6 +54,26 @@ func (f *fake) machine() *Machine {
|
||||
}
|
||||
}
|
||||
|
||||
// systemctlRefuses is what systemctl itself does with an argument that starts with a dash before
|
||||
// "--": it reads it as an option, so the root mount's unit -.mount is refused. The message is the
|
||||
// one systemctl gave on the desktop (novox/hq issue 374); a fake that took -.mount as a unit name
|
||||
// agreed with the mistake.
|
||||
func systemctlRefuses(args []string) error {
|
||||
for i := 0; i < len(args); i++ {
|
||||
a := args[i]
|
||||
switch {
|
||||
case a == "--":
|
||||
return nil
|
||||
case a == "-p":
|
||||
i++ // its value is the property list
|
||||
case strings.HasPrefix(a, "--"):
|
||||
case strings.HasPrefix(a, "-") && len(a) > 1:
|
||||
return fmt.Errorf("exit status 1: systemctl: invalid option -- '%c'\nHint: to specify units starting with a dash, use \"--\":\n systemctl [OPTIONS…] COMMAND -- %s …", a[1], a)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestRedactOptions(t *testing.T) {
|
||||
cases := []struct{ in, want string }{
|
||||
{"", ""},
|
||||
@@ -156,7 +181,7 @@ func listFake(t *testing.T) *fake {
|
||||
f.file("/proc/self/mountinfo", sampleMountinfo)
|
||||
f.answers["systemctl list-units --type=mount,automount --all --no-legend --plain --no-pager"] = sampleListUnits
|
||||
names := strings.Join(ParseListUnits(sampleListUnits), " ")
|
||||
f.answers["systemctl show -p "+ShowProperties+" --no-pager "+names] = sampleShow
|
||||
f.answers["systemctl show -p "+ShowProperties+" --no-pager -- "+names] = sampleShow
|
||||
return f
|
||||
}
|
||||
|
||||
@@ -190,6 +215,49 @@ func TestList(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// The desktop's own mount units, as its service manager listed them on 2026-10-11 (novox/hq issue
|
||||
// 374): the root mount's unit is -.mount, and systemctl reads a name that starts with a dash as an
|
||||
// option unless it comes after "--". Every mount then lost its unit state, not only the root's.
|
||||
const desktopListUnits = `-.mount loaded active mounted Root Mount
|
||||
boot.mount loaded active mounted /boot
|
||||
home.mount loaded active mounted /home
|
||||
mnt-hdd_2tb.mount loaded active mounted /mnt/hdd_2tb
|
||||
mnt-recalbox.automount loaded active waiting recalbox, mounted by the mesh at first use
|
||||
mnt-recalbox.mount loaded inactive dead recalbox (//192.0.2.176/Share), mounted by the mesh
|
||||
storage-media.automount loaded active waiting media, mounted by the mesh at first use
|
||||
storage-media.mount loaded inactive dead media (192.0.2.10:/storage/media), mounted by the mesh
|
||||
`
|
||||
|
||||
func TestListReadsTheRootMountsUnit(t *testing.T) {
|
||||
f := newFake(t)
|
||||
f.file("/etc/fstab", "UUID=aaaa / ext4 rw,relatime 0 1\n")
|
||||
f.file("/proc/self/mountinfo", "22 1 259:3 / / rw,relatime shared:1 - ext4 /dev/nvme0n1p3 rw\n")
|
||||
f.answers["systemctl list-units --type=mount,automount --all --no-legend --plain --no-pager"] = desktopListUnits
|
||||
names := strings.Join(ParseListUnits(desktopListUnits), " ")
|
||||
if !strings.HasPrefix(names, "-.mount ") {
|
||||
t.Fatalf("the root mount's unit was not listed: %q", names)
|
||||
}
|
||||
f.answers["systemctl show -p "+ShowProperties+" --no-pager -- "+names] =
|
||||
"Id=-.mount\nWhere=/\nWhat=/dev/nvme0n1p3\nType=ext4\nOptions=rw\nResult=success\nActiveState=active\nSubState=mounted\n" +
|
||||
"FragmentPath=/run/systemd/generator/-.mount\nSourcePath=/etc/fstab\nUnitFileState=generated\n\n" +
|
||||
"Id=storage-media.automount\nWhere=/storage/media\nResult=success\nActiveState=active\nSubState=waiting\n" +
|
||||
"FragmentPath=/etc/systemd/system/storage-media.automount\nUnitFileState=enabled\n"
|
||||
r := f.machine().List(false, "")
|
||||
if len(r.Notes) != 0 {
|
||||
t.Fatalf("the units' properties must be read, the root mount's included: %q", r.Notes)
|
||||
}
|
||||
got := map[string]Mount{}
|
||||
for _, mt := range r.Mounts {
|
||||
got[mt.Mountpoint] = mt
|
||||
}
|
||||
if root := got["/"]; root.MountUnit == nil || root.MountUnit.Unit != "-.mount" {
|
||||
t.Errorf("/ must carry its unit -.mount: %+v", root.MountUnit)
|
||||
}
|
||||
if media := got["/storage/media"]; media.Automount == nil {
|
||||
t.Errorf("/storage/media must carry its automount: %+v", media)
|
||||
}
|
||||
}
|
||||
|
||||
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