github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/interfaces/mount/spec_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2017 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package mount_test 21 22 import ( 23 "strings" 24 25 . "gopkg.in/check.v1" 26 27 "github.com/snapcore/snapd/interfaces" 28 "github.com/snapcore/snapd/interfaces/ifacetest" 29 "github.com/snapcore/snapd/interfaces/mount" 30 "github.com/snapcore/snapd/logger" 31 "github.com/snapcore/snapd/osutil" 32 "github.com/snapcore/snapd/snap" 33 "github.com/snapcore/snapd/snap/snaptest" 34 ) 35 36 type specSuite struct { 37 iface *ifacetest.TestInterface 38 spec *mount.Specification 39 plugInfo *snap.PlugInfo 40 plug *interfaces.ConnectedPlug 41 slotInfo *snap.SlotInfo 42 slot *interfaces.ConnectedSlot 43 } 44 45 var _ = Suite(&specSuite{ 46 iface: &ifacetest.TestInterface{ 47 InterfaceName: "test", 48 MountConnectedPlugCallback: func(spec *mount.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { 49 return spec.AddMountEntry(osutil.MountEntry{Dir: "dir-a", Name: "connected-plug"}) 50 }, 51 MountConnectedSlotCallback: func(spec *mount.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { 52 return spec.AddMountEntry(osutil.MountEntry{Dir: "dir-b", Name: "connected-slot"}) 53 }, 54 MountPermanentPlugCallback: func(spec *mount.Specification, plug *snap.PlugInfo) error { 55 return spec.AddMountEntry(osutil.MountEntry{Dir: "dir-c", Name: "permanent-plug"}) 56 }, 57 MountPermanentSlotCallback: func(spec *mount.Specification, slot *snap.SlotInfo) error { 58 return spec.AddMountEntry(osutil.MountEntry{Dir: "dir-d", Name: "permanent-slot"}) 59 }, 60 }, 61 plugInfo: &snap.PlugInfo{ 62 Snap: &snap.Info{SuggestedName: "snap"}, 63 Name: "name", 64 Interface: "test", 65 }, 66 slotInfo: &snap.SlotInfo{ 67 Snap: &snap.Info{SuggestedName: "snap"}, 68 Name: "name", 69 Interface: "test", 70 }, 71 }) 72 73 func (s *specSuite) SetUpTest(c *C) { 74 s.spec = &mount.Specification{} 75 s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil, nil) 76 s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil, nil) 77 } 78 79 // AddMountEntry and AddUserMountEntry are not not broken 80 func (s *specSuite) TestSmoke(c *C) { 81 ent0 := osutil.MountEntry{Dir: "dir-a", Name: "fs1"} 82 ent1 := osutil.MountEntry{Dir: "dir-b", Name: "fs2"} 83 ent2 := osutil.MountEntry{Dir: "dir-c", Name: "fs3"} 84 85 uent0 := osutil.MountEntry{Dir: "per-user-a", Name: "fs1"} 86 uent1 := osutil.MountEntry{Dir: "per-user-b", Name: "fs2"} 87 88 c.Assert(s.spec.AddMountEntry(ent0), IsNil) 89 c.Assert(s.spec.AddMountEntry(ent1), IsNil) 90 c.Assert(s.spec.AddMountEntry(ent2), IsNil) 91 92 c.Assert(s.spec.AddUserMountEntry(uent0), IsNil) 93 c.Assert(s.spec.AddUserMountEntry(uent1), IsNil) 94 95 c.Assert(s.spec.MountEntries(), DeepEquals, []osutil.MountEntry{ent0, ent1, ent2}) 96 c.Assert(s.spec.UserMountEntries(), DeepEquals, []osutil.MountEntry{uent0, uent1}) 97 } 98 99 // Added entries can clash and are automatically renamed by MountEntries 100 func (s *specSuite) TestMountEntriesDeclash(c *C) { 101 buf, restore := logger.MockLogger() 102 defer restore() 103 104 c.Assert(s.spec.AddMountEntry(osutil.MountEntry{Dir: "foo", Name: "fs1"}), IsNil) 105 c.Assert(s.spec.AddMountEntry(osutil.MountEntry{Dir: "foo", Name: "fs2"}), IsNil) 106 c.Assert(s.spec.MountEntries(), DeepEquals, []osutil.MountEntry{ 107 {Dir: "foo", Name: "fs1"}, 108 {Dir: "foo-2", Name: "fs2"}, 109 }) 110 111 c.Assert(s.spec.AddUserMountEntry(osutil.MountEntry{Dir: "bar", Name: "fs1"}), IsNil) 112 c.Assert(s.spec.AddUserMountEntry(osutil.MountEntry{Dir: "bar", Name: "fs2"}), IsNil) 113 c.Assert(s.spec.UserMountEntries(), DeepEquals, []osutil.MountEntry{ 114 {Dir: "bar", Name: "fs1"}, 115 {Dir: "bar-2", Name: "fs2"}, 116 }) 117 118 // extract the relevant part of the log 119 loggedMsgs := strings.Split(buf.String(), "\n") 120 msg := strings.SplitAfter(strings.TrimSpace(loggedMsgs[0]), ": ")[1] 121 c.Assert(msg, Equals, `renaming mount entry for directory "foo" to "foo-2" to avoid a clash`) 122 msg = strings.SplitAfter(strings.TrimSpace(loggedMsgs[1]), ": ")[1] 123 c.Assert(msg, Equals, `renaming mount entry for directory "bar" to "bar-2" to avoid a clash`) 124 } 125 126 // The mount.Specification can be used through the interfaces.Specification interface 127 func (s *specSuite) TestSpecificationIface(c *C) { 128 var r interfaces.Specification = s.spec 129 c.Assert(r.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) 130 c.Assert(r.AddConnectedSlot(s.iface, s.plug, s.slot), IsNil) 131 c.Assert(r.AddPermanentPlug(s.iface, s.plugInfo), IsNil) 132 c.Assert(r.AddPermanentSlot(s.iface, s.slotInfo), IsNil) 133 c.Assert(s.spec.MountEntries(), DeepEquals, []osutil.MountEntry{ 134 {Dir: "dir-a", Name: "connected-plug"}, 135 {Dir: "dir-b", Name: "connected-slot"}, 136 {Dir: "dir-c", Name: "permanent-plug"}, 137 {Dir: "dir-d", Name: "permanent-slot"}}) 138 } 139 140 const snapWithLayout = ` 141 name: vanguard 142 version: 0 143 layout: 144 /usr: 145 bind: $SNAP/usr 146 /lib/mytmp: 147 type: tmpfs 148 mode: 1777 149 /lib/mylink: 150 symlink: $SNAP/link/target 151 /etc/foo.conf: 152 bind-file: $SNAP/foo.conf 153 ` 154 155 func (s *specSuite) TestMountEntryFromLayout(c *C) { 156 snapInfo := snaptest.MockInfo(c, snapWithLayout, &snap.SideInfo{Revision: snap.R(42)}) 157 s.spec.AddLayout(snapInfo) 158 c.Assert(s.spec.MountEntries(), DeepEquals, []osutil.MountEntry{ 159 // Layout result is sorted by mount path. 160 {Dir: "/etc/foo.conf", Name: "/snap/vanguard/42/foo.conf", Options: []string{"bind", "rw", "x-snapd.kind=file", "x-snapd.origin=layout"}}, 161 {Dir: "/lib/mylink", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=/snap/vanguard/42/link/target", "x-snapd.origin=layout"}}, 162 {Dir: "/lib/mytmp", Name: "tmpfs", Type: "tmpfs", Options: []string{"x-snapd.mode=01777", "x-snapd.origin=layout"}}, 163 {Dir: "/usr", Name: "/snap/vanguard/42/usr", Options: []string{"rbind", "rw", "x-snapd.origin=layout"}}, 164 }) 165 } 166 167 func (s *specSuite) TestParallelInstanceMountEntryFromLayout(c *C) { 168 snapInfo := snaptest.MockInfo(c, snapWithLayout, &snap.SideInfo{Revision: snap.R(42)}) 169 snapInfo.InstanceKey = "instance" 170 s.spec.AddLayout(snapInfo) 171 s.spec.AddOvername(snapInfo) 172 c.Assert(s.spec.MountEntries(), DeepEquals, []osutil.MountEntry{ 173 // Parallel instance mappings come first 174 {Dir: "/snap/vanguard", Name: "/snap/vanguard_instance", Options: []string{"rbind", "x-snapd.origin=overname"}}, 175 {Dir: "/var/snap/vanguard", Name: "/var/snap/vanguard_instance", Options: []string{"rbind", "x-snapd.origin=overname"}}, 176 // Layout result is sorted by mount path. 177 {Dir: "/etc/foo.conf", Name: "/snap/vanguard/42/foo.conf", Options: []string{"bind", "rw", "x-snapd.kind=file", "x-snapd.origin=layout"}}, 178 {Dir: "/lib/mylink", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=/snap/vanguard/42/link/target", "x-snapd.origin=layout"}}, 179 {Dir: "/lib/mytmp", Name: "tmpfs", Type: "tmpfs", Options: []string{"x-snapd.mode=01777", "x-snapd.origin=layout"}}, 180 {Dir: "/usr", Name: "/snap/vanguard/42/usr", Options: []string{"rbind", "rw", "x-snapd.origin=layout"}}, 181 }) 182 } 183 184 func (s *specSuite) TestSpecificationUberclash(c *C) { 185 // When everything clashes for access to /usr/foo, what happens? 186 const uberclashYaml = `name: uberclash 187 version: 0 188 layout: 189 /usr/foo: 190 type: tmpfs 191 ` 192 snapInfo := snaptest.MockInfo(c, uberclashYaml, &snap.SideInfo{Revision: snap.R(42)}) 193 entry := osutil.MountEntry{Dir: "/usr/foo", Type: "tmpfs", Name: "tmpfs"} 194 s.spec.AddMountEntry(entry) 195 s.spec.AddUserMountEntry(entry) 196 s.spec.AddLayout(snapInfo) 197 c.Assert(s.spec.MountEntries(), DeepEquals, []osutil.MountEntry{ 198 {Dir: "/usr/foo", Type: "tmpfs", Name: "tmpfs", Options: []string{"x-snapd.origin=layout"}}, 199 // This is the non-layout entry, it was renamed to "foo-2" 200 {Dir: "/usr/foo-2", Type: "tmpfs", Name: "tmpfs"}, 201 }) 202 c.Assert(s.spec.UserMountEntries(), DeepEquals, []osutil.MountEntry{ 203 // This is the user entry, it was _not_ renamed and it would clash with 204 // /foo but there is no way to request things like that for now. 205 {Dir: "/usr/foo", Type: "tmpfs", Name: "tmpfs"}, 206 }) 207 } 208 209 func (s *specSuite) TestParallelInstanceMountEntriesNoInstanceKey(c *C) { 210 snapInfo := &snap.Info{SideInfo: snap.SideInfo{RealName: "foo", Revision: snap.R(42)}} 211 s.spec.AddOvername(snapInfo) 212 c.Assert(s.spec.MountEntries(), HasLen, 0) 213 c.Assert(s.spec.UserMountEntries(), HasLen, 0) 214 } 215 216 func (s *specSuite) TestParallelInstanceMountEntriesReal(c *C) { 217 snapInfo := &snap.Info{SideInfo: snap.SideInfo{RealName: "foo", Revision: snap.R(42)}, InstanceKey: "instance"} 218 s.spec.AddOvername(snapInfo) 219 c.Assert(s.spec.MountEntries(), DeepEquals, []osutil.MountEntry{ 220 // /snap/foo_instance -> /snap/foo 221 {Name: "/snap/foo_instance", Dir: "/snap/foo", Options: []string{"rbind", "x-snapd.origin=overname"}}, 222 // /var/snap/foo_instance -> /var/snap/foo 223 {Name: "/var/snap/foo_instance", Dir: "/var/snap/foo", Options: []string{"rbind", "x-snapd.origin=overname"}}, 224 }) 225 c.Assert(s.spec.UserMountEntries(), HasLen, 0) 226 }