github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/cmd/snap-update-ns/main_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 main_test 21 22 import ( 23 "bytes" 24 "fmt" 25 "io/ioutil" 26 "os" 27 "path/filepath" 28 "testing" 29 30 . "gopkg.in/check.v1" 31 32 update "github.com/snapcore/snapd/cmd/snap-update-ns" 33 "github.com/snapcore/snapd/dirs" 34 "github.com/snapcore/snapd/features" 35 "github.com/snapcore/snapd/logger" 36 "github.com/snapcore/snapd/osutil" 37 "github.com/snapcore/snapd/testutil" 38 ) 39 40 func Test(t *testing.T) { TestingT(t) } 41 42 type mainSuite struct { 43 testutil.BaseTest 44 as *update.Assumptions 45 log *bytes.Buffer 46 } 47 48 var _ = Suite(&mainSuite{}) 49 50 func (s *mainSuite) SetUpTest(c *C) { 51 s.BaseTest.SetUpTest(c) 52 s.as = &update.Assumptions{} 53 buf, restore := logger.MockLogger() 54 s.BaseTest.AddCleanup(restore) 55 s.log = buf 56 } 57 58 func (s *mainSuite) TestExecuteMountProfileUpdate(c *C) { 59 dirs.SetRootDir(c.MkDir()) 60 defer dirs.SetRootDir("/") 61 62 restore := update.MockChangePerform(func(chg *update.Change, as *update.Assumptions) ([]*update.Change, error) { 63 return nil, nil 64 }) 65 defer restore() 66 67 snapName := "foo" 68 desiredProfileContent := `/var/lib/snapd/hostfs/usr/share/fonts /usr/share/fonts none bind,ro 0 0 69 /var/lib/snapd/hostfs/usr/local/share/fonts /usr/local/share/fonts none bind,ro 0 0` 70 71 desiredProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapMountPolicyDir, snapName) 72 err := os.MkdirAll(filepath.Dir(desiredProfilePath), 0755) 73 c.Assert(err, IsNil) 74 err = ioutil.WriteFile(desiredProfilePath, []byte(desiredProfileContent), 0644) 75 c.Assert(err, IsNil) 76 77 currentProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapRunNsDir, snapName) 78 err = os.MkdirAll(filepath.Dir(currentProfilePath), 0755) 79 c.Assert(err, IsNil) 80 err = ioutil.WriteFile(currentProfilePath, nil, 0644) 81 c.Assert(err, IsNil) 82 83 upCtx := update.NewSystemProfileUpdateContext(snapName, false) 84 err = update.ExecuteMountProfileUpdate(upCtx) 85 c.Assert(err, IsNil) 86 87 c.Check(currentProfilePath, testutil.FileEquals, `/var/lib/snapd/hostfs/usr/local/share/fonts /usr/local/share/fonts none bind,ro 0 0 88 /var/lib/snapd/hostfs/usr/share/fonts /usr/share/fonts none bind,ro 0 0 89 `) 90 } 91 92 func (s *mainSuite) TestAddingSyntheticChanges(c *C) { 93 dirs.SetRootDir(c.MkDir()) 94 defer dirs.SetRootDir("/") 95 96 // The snap `mysnap` wishes to export it's usr/share/mysnap directory and 97 // make it appear as if it was in /usr/share/mysnap directly. 98 const snapName = "mysnap" 99 const currentProfileContent = "" 100 const desiredProfileContent = "/snap/mysnap/42/usr/share/mysnap /usr/share/mysnap none bind,ro 0 0" 101 102 currentProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapRunNsDir, snapName) 103 desiredProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapMountPolicyDir, snapName) 104 105 c.Assert(os.MkdirAll(filepath.Dir(currentProfilePath), 0755), IsNil) 106 c.Assert(os.MkdirAll(filepath.Dir(desiredProfilePath), 0755), IsNil) 107 c.Assert(ioutil.WriteFile(currentProfilePath, []byte(currentProfileContent), 0644), IsNil) 108 c.Assert(ioutil.WriteFile(desiredProfilePath, []byte(desiredProfileContent), 0644), IsNil) 109 110 // In order to make that work, /usr/share had to be converted to a writable 111 // mimic. Some actions were performed under the hood and now we see a 112 // subset of them as synthetic changes here. 113 // 114 // Note that if you compare this to the code that plans a writable mimic 115 // you will see that there are additional changes that are _not_ 116 // represented here. The changes have only one goal: tell 117 // snap-update-ns how the mimic can be undone in case it is no longer 118 // needed. 119 restore := update.MockChangePerform(func(chg *update.Change, as *update.Assumptions) ([]*update.Change, error) { 120 // The change that we were asked to perform is to create a bind mount 121 // from within the snap to /usr/share/mysnap. 122 c.Assert(chg, DeepEquals, &update.Change{ 123 Action: update.Mount, Entry: osutil.MountEntry{ 124 Name: "/snap/mysnap/42/usr/share/mysnap", 125 Dir: "/usr/share/mysnap", Type: "none", 126 Options: []string{"bind", "ro"}}}) 127 synthetic := []*update.Change{ 128 // The original directory (which was a part of the core snap and is 129 // read only) was hidden with a tmpfs. 130 {Action: update.Mount, Entry: osutil.MountEntry{ 131 Dir: "/usr/share", Name: "tmpfs", Type: "tmpfs", 132 Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/usr/share/mysnap"}}}, 133 // For the sake of brevity we will only represent a few of the 134 // entries typically there. Normally this list can get quite long. 135 // Also note that the entry is a little fake. In reality it was 136 // constructed using a temporary bind mount that contained the 137 // original mount entries of /usr/share but this fact was lost. 138 // Again, the only point of this entry is to correctly perform an 139 // undo operation when /usr/share/mysnap is no longer needed. 140 {Action: update.Mount, Entry: osutil.MountEntry{ 141 Dir: "/usr/share/adduser", Name: "/usr/share/adduser", 142 Options: []string{"bind", "ro", "x-snapd.synthetic", "x-snapd.needed-by=/usr/share/mysnap"}}}, 143 {Action: update.Mount, Entry: osutil.MountEntry{ 144 Dir: "/usr/share/awk", Name: "/usr/share/awk", 145 Options: []string{"bind", "ro", "x-snapd.synthetic", "x-snapd.needed-by=/usr/share/mysnap"}}}, 146 } 147 return synthetic, nil 148 }) 149 defer restore() 150 151 upCtx := update.NewSystemProfileUpdateContext(snapName, false) 152 c.Assert(update.ExecuteMountProfileUpdate(upCtx), IsNil) 153 154 c.Check(currentProfilePath, testutil.FileEquals, 155 `tmpfs /usr/share tmpfs x-snapd.synthetic,x-snapd.needed-by=/usr/share/mysnap 0 0 156 /usr/share/adduser /usr/share/adduser none bind,ro,x-snapd.synthetic,x-snapd.needed-by=/usr/share/mysnap 0 0 157 /usr/share/awk /usr/share/awk none bind,ro,x-snapd.synthetic,x-snapd.needed-by=/usr/share/mysnap 0 0 158 /snap/mysnap/42/usr/share/mysnap /usr/share/mysnap none bind,ro 0 0 159 `) 160 } 161 162 func (s *mainSuite) TestRemovingSyntheticChanges(c *C) { 163 dirs.SetRootDir(c.MkDir()) 164 defer dirs.SetRootDir("/") 165 166 c.Assert(os.MkdirAll(dirs.FeaturesDir, 0755), IsNil) 167 c.Assert(ioutil.WriteFile(features.RobustMountNamespaceUpdates.ControlFile(), []byte(nil), 0644), IsNil) 168 169 // The snap `mysnap` no longer wishes to export it's usr/share/mysnap 170 // directory. All the synthetic changes that were associated with that mount 171 // entry can be discarded. 172 const snapName = "mysnap" 173 const currentProfileContent = `tmpfs /usr/share tmpfs x-snapd.synthetic,x-snapd.needed-by=/usr/share/mysnap 0 0 174 /usr/share/adduser /usr/share/adduser none bind,ro,x-snapd.synthetic,x-snapd.needed-by=/usr/share/mysnap 0 0 175 /usr/share/awk /usr/share/awk none bind,ro,x-snapd.synthetic,x-snapd.needed-by=/usr/share/mysnap 0 0 176 /snap/mysnap/42/usr/share/mysnap /usr/share/mysnap none bind,ro 0 0 177 ` 178 const desiredProfileContent = "" 179 180 currentProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapRunNsDir, snapName) 181 desiredProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapMountPolicyDir, snapName) 182 183 c.Assert(os.MkdirAll(filepath.Dir(currentProfilePath), 0755), IsNil) 184 c.Assert(os.MkdirAll(filepath.Dir(desiredProfilePath), 0755), IsNil) 185 c.Assert(ioutil.WriteFile(currentProfilePath, []byte(currentProfileContent), 0644), IsNil) 186 c.Assert(ioutil.WriteFile(desiredProfilePath, []byte(desiredProfileContent), 0644), IsNil) 187 188 n := -1 189 restore := update.MockChangePerform(func(chg *update.Change, as *update.Assumptions) ([]*update.Change, error) { 190 n++ 191 switch n { 192 case 0: 193 c.Assert(chg, DeepEquals, &update.Change{ 194 Action: update.Unmount, 195 Entry: osutil.MountEntry{ 196 Name: "/snap/mysnap/42/usr/share/mysnap", 197 Dir: "/usr/share/mysnap", Type: "none", 198 Options: []string{"bind", "ro"}, 199 }, 200 }) 201 case 1: 202 c.Assert(chg, DeepEquals, &update.Change{ 203 Action: update.Unmount, 204 Entry: osutil.MountEntry{ 205 Name: "tmpfs", Dir: "/usr/share", Type: "tmpfs", 206 Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/usr/share/mysnap", "x-snapd.detach"}, 207 }, 208 }) 209 default: 210 panic(fmt.Sprintf("unexpected call n=%d, chg: %v", n, *chg)) 211 } 212 return nil, nil 213 }) 214 defer restore() 215 216 upCtx := update.NewSystemProfileUpdateContext(snapName, false) 217 c.Assert(update.ExecuteMountProfileUpdate(upCtx), IsNil) 218 219 c.Check(currentProfilePath, testutil.FileEquals, "") 220 } 221 222 func (s *mainSuite) TestApplyingLayoutChanges(c *C) { 223 dirs.SetRootDir(c.MkDir()) 224 defer dirs.SetRootDir("/") 225 226 const snapName = "mysnap" 227 const currentProfileContent = "" 228 const desiredProfileContent = "/snap/mysnap/42/usr/share/mysnap /usr/share/mysnap none bind,ro,x-snapd.origin=layout 0 0" 229 230 currentProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapRunNsDir, snapName) 231 desiredProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapMountPolicyDir, snapName) 232 233 c.Assert(os.MkdirAll(filepath.Dir(currentProfilePath), 0755), IsNil) 234 c.Assert(os.MkdirAll(filepath.Dir(desiredProfilePath), 0755), IsNil) 235 c.Assert(ioutil.WriteFile(currentProfilePath, []byte(currentProfileContent), 0644), IsNil) 236 c.Assert(ioutil.WriteFile(desiredProfilePath, []byte(desiredProfileContent), 0644), IsNil) 237 238 n := -1 239 restore := update.MockChangePerform(func(chg *update.Change, as *update.Assumptions) ([]*update.Change, error) { 240 n++ 241 switch n { 242 case 0: 243 c.Assert(chg, DeepEquals, &update.Change{ 244 Action: update.Mount, 245 Entry: osutil.MountEntry{ 246 Name: "/snap/mysnap/42/usr/share/mysnap", 247 Dir: "/usr/share/mysnap", Type: "none", 248 Options: []string{"bind", "ro", "x-snapd.origin=layout"}, 249 }, 250 }) 251 return nil, fmt.Errorf("testing") 252 default: 253 panic(fmt.Sprintf("unexpected call n=%d, chg: %v", n, *chg)) 254 } 255 }) 256 defer restore() 257 258 // The error was not ignored, we bailed out. 259 upCtx := update.NewSystemProfileUpdateContext(snapName, false) 260 c.Assert(update.ExecuteMountProfileUpdate(upCtx), ErrorMatches, "testing") 261 262 c.Check(currentProfilePath, testutil.FileEquals, "") 263 } 264 265 func (s *mainSuite) TestApplyingParallelInstanceChanges(c *C) { 266 dirs.SetRootDir(c.MkDir()) 267 defer dirs.SetRootDir("/") 268 269 const snapName = "mysnap" 270 const currentProfileContent = "" 271 const desiredProfileContent = "/snap/mysnap_foo /snap/mysnap none rbind,x-snapd.origin=overname 0 0" 272 273 currentProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapRunNsDir, snapName) 274 desiredProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapMountPolicyDir, snapName) 275 276 c.Assert(os.MkdirAll(filepath.Dir(currentProfilePath), 0755), IsNil) 277 c.Assert(os.MkdirAll(filepath.Dir(desiredProfilePath), 0755), IsNil) 278 c.Assert(ioutil.WriteFile(currentProfilePath, []byte(currentProfileContent), 0644), IsNil) 279 c.Assert(ioutil.WriteFile(desiredProfilePath, []byte(desiredProfileContent), 0644), IsNil) 280 281 n := -1 282 restore := update.MockChangePerform(func(chg *update.Change, as *update.Assumptions) ([]*update.Change, error) { 283 n++ 284 switch n { 285 case 0: 286 c.Assert(chg, DeepEquals, &update.Change{ 287 Action: update.Mount, 288 Entry: osutil.MountEntry{ 289 Name: "/snap/mysnap_foo", 290 Dir: "/snap/mysnap", Type: "none", 291 Options: []string{"rbind", "x-snapd.origin=overname"}, 292 }, 293 }) 294 return nil, fmt.Errorf("testing") 295 default: 296 panic(fmt.Sprintf("unexpected call n=%d, chg: %v", n, *chg)) 297 } 298 }) 299 defer restore() 300 301 // The error was not ignored, we bailed out. 302 upCtx := update.NewSystemProfileUpdateContext(snapName, false) 303 c.Assert(update.ExecuteMountProfileUpdate(upCtx), ErrorMatches, "testing") 304 305 c.Check(currentProfilePath, testutil.FileEquals, "") 306 } 307 308 func (s *mainSuite) TestApplyIgnoredMissingMount(c *C) { 309 dirs.SetRootDir(c.MkDir()) 310 defer dirs.SetRootDir("/") 311 312 const snapName = "mysnap" 313 const currentProfileContent = "" 314 const desiredProfileContent = "/source /target none bind,x-snapd.ignore-missing 0 0" 315 316 currentProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapRunNsDir, snapName) 317 desiredProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapMountPolicyDir, snapName) 318 319 c.Assert(os.MkdirAll(filepath.Dir(currentProfilePath), 0755), IsNil) 320 c.Assert(os.MkdirAll(filepath.Dir(desiredProfilePath), 0755), IsNil) 321 c.Assert(ioutil.WriteFile(currentProfilePath, []byte(currentProfileContent), 0644), IsNil) 322 c.Assert(ioutil.WriteFile(desiredProfilePath, []byte(desiredProfileContent), 0644), IsNil) 323 324 n := -1 325 restore := update.MockChangePerform(func(chg *update.Change, as *update.Assumptions) ([]*update.Change, error) { 326 n++ 327 switch n { 328 case 0: 329 c.Assert(chg, DeepEquals, &update.Change{ 330 Action: update.Mount, 331 Entry: osutil.MountEntry{ 332 Name: "/source", 333 Dir: "/target", 334 Type: "none", 335 Options: []string{"bind", "x-snapd.ignore-missing"}, 336 }, 337 }) 338 return nil, update.ErrIgnoredMissingMount 339 default: 340 panic(fmt.Sprintf("unexpected call n=%d, chg: %v", n, *chg)) 341 } 342 }) 343 defer restore() 344 345 // The error was ignored, and no mount was recorded in the profile 346 upCtx := update.NewSystemProfileUpdateContext(snapName, false) 347 c.Assert(update.ExecuteMountProfileUpdate(upCtx), IsNil) 348 c.Check(s.log.String(), Equals, "") 349 c.Check(currentProfilePath, testutil.FileEquals, "") 350 } 351 352 func (s *mainSuite) TestApplyUserFstab(c *C) { 353 dirs.SetRootDir(c.MkDir()) 354 defer dirs.SetRootDir("/") 355 356 var changes []update.Change 357 restore := update.MockChangePerform(func(chg *update.Change, as *update.Assumptions) ([]*update.Change, error) { 358 changes = append(changes, *chg) 359 return nil, nil 360 }) 361 defer restore() 362 363 snapName := "foo" 364 desiredProfileContent := `$XDG_RUNTIME_DIR/doc/by-app/snap.foo $XDG_RUNTIME_DIR/doc none bind,rw 0 0` 365 366 desiredProfilePath := fmt.Sprintf("%s/snap.%s.user-fstab", dirs.SnapMountPolicyDir, snapName) 367 err := os.MkdirAll(filepath.Dir(desiredProfilePath), 0755) 368 c.Assert(err, IsNil) 369 err = ioutil.WriteFile(desiredProfilePath, []byte(desiredProfileContent), 0644) 370 c.Assert(err, IsNil) 371 372 upCtx := update.NewUserProfileUpdateContext(snapName, true, 1000) 373 err = update.ExecuteMountProfileUpdate(upCtx) 374 c.Assert(err, IsNil) 375 376 xdgRuntimeDir := fmt.Sprintf("%s/%d", dirs.XdgRuntimeDirBase, 1000) 377 378 c.Assert(changes, HasLen, 1) 379 c.Assert(changes[0].Action, Equals, update.Mount) 380 c.Assert(changes[0].Entry.Name, Equals, xdgRuntimeDir+"/doc/by-app/snap.foo") 381 c.Assert(changes[0].Entry.Dir, Matches, xdgRuntimeDir+"/doc") 382 }