github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/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  }