mirror of
https://github.com/token2/snapd.git
synced 2026-03-13 11:15:47 -07:00
systemd: add a method to list mount units created by a snap
This commit is contained in:
@@ -192,6 +192,10 @@ func (s *emulation) RemoveMountUnitFile(mountedDir string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *emulation) ListMountUnits(snapName, origin string) ([]string, error) {
|
||||
return nil, errNotImplemented
|
||||
}
|
||||
|
||||
func (s *emulation) Mask(service string) error {
|
||||
_, err := systemctlCmd("--root", s.rootDir, "mask", service)
|
||||
return err
|
||||
|
||||
@@ -280,6 +280,9 @@ type Systemd interface {
|
||||
AddMountUnitFileWithOptions(unitOptions *MountUnitOptions) (string, error)
|
||||
// RemoveMountUnitFile unmounts/stops/disables/removes a mount unit.
|
||||
RemoveMountUnitFile(baseDir string) error
|
||||
// ListMountUnits gets the list of targets of the mount units created by
|
||||
// the `origin` module for the given snap
|
||||
ListMountUnits(snapName, origin string) ([]string, error)
|
||||
// Mask the given service.
|
||||
Mask(service string) error
|
||||
// Unmask the given service.
|
||||
@@ -1275,6 +1278,101 @@ func (s *systemd) RemoveMountUnitFile(mountedDir string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func workaroundSystemdQuoting(fragmentPath, where string) string {
|
||||
// We know that the directory components of the fragment path do not need
|
||||
// quoting and are therefore reliable. As for the file name, we workaround
|
||||
// the wrong quoting of older systemd version by re-encoding the "Where"
|
||||
// ourselves.
|
||||
dir := filepath.Dir(fragmentPath)
|
||||
baseName := EscapeUnitNamePath(where)
|
||||
unitType := filepath.Ext(fragmentPath)
|
||||
return filepath.Join(dir, baseName+unitType)
|
||||
}
|
||||
|
||||
func extractOriginModule(systemdUnitPath string) (string, error) {
|
||||
f, err := os.Open(systemdUnitPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var originModule string
|
||||
s := bufio.NewScanner(f)
|
||||
prefix := snappyOriginModule + "="
|
||||
for s.Scan() {
|
||||
line := s.Text()
|
||||
if strings.HasPrefix(line, prefix) {
|
||||
originModule = line[len(prefix):]
|
||||
break
|
||||
}
|
||||
}
|
||||
return originModule, nil
|
||||
}
|
||||
|
||||
func (s *systemd) ListMountUnits(snapName, origin string) ([]string, error) {
|
||||
out, err := s.systemctl("show", "--property=Description,Where,FragmentPath", "*.mount")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var mountPoints []string
|
||||
if bytes.TrimSpace(out) == nil {
|
||||
return mountPoints, nil
|
||||
}
|
||||
// Results are separated by a blank line, so we can split them like this:
|
||||
units := bytes.Split(out, []byte("\n\n"))
|
||||
for _, unitOutput := range units {
|
||||
var where, description, fragmentPath string
|
||||
lines := bytes.Split(bytes.Trim(unitOutput, "\n"), []byte("\n"))
|
||||
for _, line := range lines {
|
||||
splitVal := strings.SplitN(string(line), "=", 2)
|
||||
if len(splitVal) != 2 {
|
||||
return nil, fmt.Errorf("cannot parse systemctl output: %q", line)
|
||||
}
|
||||
switch splitVal[0] {
|
||||
case "Description":
|
||||
description = splitVal[1]
|
||||
case "Where":
|
||||
where = splitVal[1]
|
||||
case "FragmentPath":
|
||||
fragmentPath = splitVal[1]
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected property %q", splitVal[0])
|
||||
}
|
||||
}
|
||||
|
||||
ourDescription := fmt.Sprintf("Mount unit for %s", snapName)
|
||||
if !strings.HasPrefix(description, ourDescription) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Under Ubuntu 16.04, systemd improperly quotes the FragmentPath, so
|
||||
// we must do some extra work here to get the correct path. This code
|
||||
// can be removed once we stop supporting old distros
|
||||
fragmentPath = workaroundSystemdQuoting(fragmentPath, where)
|
||||
|
||||
// only return units programmatically created by some snapd backend:
|
||||
// the mount unit used to mount the snap's squashfs is generally
|
||||
// uninteresting
|
||||
originModule, err := extractOriginModule(fragmentPath)
|
||||
if err != nil || originModule == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// If an `origin` was given, we must return only units created by it
|
||||
if origin != "" && originModule != origin {
|
||||
continue
|
||||
}
|
||||
|
||||
if where == "" {
|
||||
return nil, fmt.Errorf(`missing "Where" in mount unit %q`, fragmentPath)
|
||||
}
|
||||
|
||||
mountPoints = append(mountPoints, where)
|
||||
}
|
||||
return mountPoints, nil
|
||||
}
|
||||
|
||||
func (s *systemd) ReloadOrRestart(serviceName string) error {
|
||||
if s.mode == GlobalUserMode {
|
||||
panic("cannot call restart with GlobalUserMode")
|
||||
|
||||
@@ -1405,6 +1405,88 @@ func (s *SystemdTestSuite) TestUnmaskInEmulationMode(c *C) {
|
||||
{"--root", "/path", "unmask", "foo"}})
|
||||
}
|
||||
|
||||
func (s *SystemdTestSuite) TestListMountUnitsEmpty(c *C) {
|
||||
s.outs = [][]byte{
|
||||
[]byte("\n"),
|
||||
}
|
||||
|
||||
sysd := New(SystemMode, nil)
|
||||
units, err := sysd.ListMountUnits("some-snap", "")
|
||||
c.Check(units, HasLen, 0)
|
||||
c.Check(err, IsNil)
|
||||
}
|
||||
|
||||
func (s *SystemdTestSuite) TestListMountUnitsMalformed(c *C) {
|
||||
s.outs = [][]byte{
|
||||
[]byte(`Description=Mount unit for some-snap, revision x1
|
||||
Where=/somewhere/here
|
||||
FragmentPath=/etc/systemd/system/somewhere-here.mount
|
||||
HereIsOneLineWithoutAnEqualSign
|
||||
`),
|
||||
}
|
||||
|
||||
sysd := New(SystemMode, nil)
|
||||
units, err := sysd.ListMountUnits("some-snap", "")
|
||||
c.Check(units, HasLen, 0)
|
||||
c.Check(err, ErrorMatches, "cannot parse systemctl output:.*")
|
||||
}
|
||||
|
||||
func (s *SystemdTestSuite) TestListMountUnitsHappy(c *C) {
|
||||
tmpDir, err := ioutil.TempDir("/tmp", "snapd-systemd-test-list-mounts-*")
|
||||
c.Assert(err, IsNil)
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
var systemctlOutput string
|
||||
createFakeUnit := func(fileName, snapName, where, origin string) error {
|
||||
path := filepath.Join(tmpDir, fileName)
|
||||
if len(systemctlOutput) > 0 {
|
||||
systemctlOutput += "\n\n"
|
||||
}
|
||||
systemctlOutput += fmt.Sprintf(`Description=Mount unit for %s, revision x1
|
||||
Where=%s
|
||||
FragmentPath=%s
|
||||
`, snapName, where, path)
|
||||
contents := fmt.Sprintf(`[Unit]
|
||||
Description=Mount unit for %s, revision x1
|
||||
|
||||
[Mount]
|
||||
What=/does/not/matter
|
||||
Where=%s
|
||||
Type=doesntmatter
|
||||
Options=do,not,matter,either
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
X-SnapdOrigin=%s
|
||||
`, snapName, where, origin)
|
||||
return ioutil.WriteFile(path, []byte(contents), 0644)
|
||||
}
|
||||
|
||||
// Prepare the unit files
|
||||
err = createFakeUnit("somepath-somedir.mount", "some-snap", "/somepath/somedir", "module1")
|
||||
c.Assert(err, IsNil)
|
||||
err = createFakeUnit("somewhere-here.mount", "some-other-snap", "/somewhere/here", "module2")
|
||||
c.Assert(err, IsNil)
|
||||
err = createFakeUnit("somewhere-there.mount", "some-snap", "/somewhere/there", "module3")
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
s.outs = [][]byte{
|
||||
[]byte(systemctlOutput),
|
||||
}
|
||||
sysd := New(SystemMode, nil)
|
||||
|
||||
// First, get all mount units for some-snap, without filter on the origin module
|
||||
units, err := sysd.ListMountUnits("some-snap", "")
|
||||
c.Check(units, DeepEquals, []string{"/somepath/somedir", "/somewhere/there"})
|
||||
c.Check(err, IsNil)
|
||||
|
||||
// Now repeat the same, filtering on the origin module
|
||||
s.i = 0 // this resets the systemctl output iterator back to the beginning
|
||||
units, err = sysd.ListMountUnits("some-snap", "module3")
|
||||
c.Check(units, DeepEquals, []string{"/somewhere/there"})
|
||||
c.Check(err, IsNil)
|
||||
}
|
||||
|
||||
func (s *SystemdTestSuite) TestMountHappy(c *C) {
|
||||
sysd := New(SystemMode, nil)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user