github.com/rigado/snapd@v2.42.5-go-mod+incompatible/cmd/cmd_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 cmd_test
    21  
    22  import (
    23  	"fmt"
    24  	"io/ioutil"
    25  	"os"
    26  	"path/filepath"
    27  	"testing"
    28  
    29  	. "gopkg.in/check.v1"
    30  
    31  	"github.com/snapcore/snapd/cmd"
    32  	"github.com/snapcore/snapd/dirs"
    33  	"github.com/snapcore/snapd/logger"
    34  	"github.com/snapcore/snapd/release"
    35  )
    36  
    37  func Test(t *testing.T) { TestingT(t) }
    38  
    39  type cmdSuite struct {
    40  	restoreExec   func()
    41  	restoreLogger func()
    42  	execCalled    int
    43  	lastExecArgv0 string
    44  	lastExecArgv  []string
    45  	lastExecEnvv  []string
    46  	fakeroot      string
    47  	snapdPath     string
    48  	corePath      string
    49  }
    50  
    51  var _ = Suite(&cmdSuite{})
    52  
    53  func (s *cmdSuite) SetUpTest(c *C) {
    54  	s.restoreExec = cmd.MockSyscallExec(s.syscallExec)
    55  	_, s.restoreLogger = logger.MockLogger()
    56  	s.execCalled = 0
    57  	s.lastExecArgv0 = ""
    58  	s.lastExecArgv = nil
    59  	s.lastExecEnvv = nil
    60  	s.fakeroot = c.MkDir()
    61  	dirs.SetRootDir(s.fakeroot)
    62  	s.snapdPath = filepath.Join(dirs.SnapMountDir, "/snapd/42")
    63  	s.corePath = filepath.Join(dirs.SnapMountDir, "/core/21")
    64  	c.Assert(os.MkdirAll(filepath.Join(s.fakeroot, "proc/self"), 0755), IsNil)
    65  }
    66  
    67  func (s *cmdSuite) TearDownTest(c *C) {
    68  	s.restoreExec()
    69  	s.restoreLogger()
    70  }
    71  
    72  func (s *cmdSuite) syscallExec(argv0 string, argv []string, envv []string) (err error) {
    73  	s.execCalled++
    74  	s.lastExecArgv0 = argv0
    75  	s.lastExecArgv = argv
    76  	s.lastExecEnvv = envv
    77  	return fmt.Errorf(">exec of %q in tests<", argv0)
    78  }
    79  
    80  func (s *cmdSuite) fakeCoreVersion(c *C, coreDir, version string) {
    81  	p := filepath.Join(coreDir, "/usr/lib/snapd")
    82  	c.Assert(os.MkdirAll(p, 0755), IsNil)
    83  	c.Assert(ioutil.WriteFile(filepath.Join(p, "info"), []byte("VERSION="+version), 0644), IsNil)
    84  }
    85  
    86  func (s *cmdSuite) fakeInternalTool(c *C, coreDir, toolName string) string {
    87  	s.fakeCoreVersion(c, coreDir, "42")
    88  	p := filepath.Join(coreDir, "/usr/lib/snapd", toolName)
    89  	c.Assert(ioutil.WriteFile(p, nil, 0755), IsNil)
    90  
    91  	return p
    92  }
    93  
    94  func (s *cmdSuite) mockReExecingEnv() func() {
    95  	restore := []func(){
    96  		release.MockOnClassic(true),
    97  		release.MockReleaseInfo(&release.OS{ID: "ubuntu"}),
    98  		cmd.MockCoreSnapdPaths(s.corePath, s.snapdPath),
    99  		cmd.MockVersion("2"),
   100  	}
   101  
   102  	return func() {
   103  		for i := len(restore) - 1; i >= 0; i-- {
   104  			restore[i]()
   105  		}
   106  	}
   107  }
   108  
   109  func (s *cmdSuite) mockReExecFor(c *C, coreDir, toolName string) func() {
   110  	selfExe := filepath.Join(s.fakeroot, "proc/self/exe")
   111  	restore := []func(){
   112  		s.mockReExecingEnv(),
   113  		cmd.MockSelfExe(selfExe),
   114  	}
   115  	s.fakeInternalTool(c, coreDir, toolName)
   116  	c.Assert(os.Symlink(filepath.Join("/usr/lib/snapd", toolName), selfExe), IsNil)
   117  
   118  	return func() {
   119  		for i := len(restore) - 1; i >= 0; i-- {
   120  			restore[i]()
   121  		}
   122  	}
   123  }
   124  
   125  func (s *cmdSuite) TestDistroSupportsReExec(c *C) {
   126  	restore := release.MockOnClassic(true)
   127  	defer restore()
   128  
   129  	// Some distributions don't support re-execution yet.
   130  	for _, id := range []string{"fedora", "centos", "rhel", "opensuse", "suse", "poky"} {
   131  		restore = release.MockReleaseInfo(&release.OS{ID: id})
   132  		defer restore()
   133  		c.Check(cmd.DistroSupportsReExec(), Equals, false, Commentf("ID: %q", id))
   134  	}
   135  
   136  	// While others do.
   137  	for _, id := range []string{"debian", "ubuntu"} {
   138  		restore = release.MockReleaseInfo(&release.OS{ID: id})
   139  		defer restore()
   140  		c.Check(cmd.DistroSupportsReExec(), Equals, true, Commentf("ID: %q", id))
   141  	}
   142  }
   143  
   144  func (s *cmdSuite) TestNonClassicDistroNoSupportsReExec(c *C) {
   145  	restore := release.MockOnClassic(false)
   146  	defer restore()
   147  
   148  	// no distro supports re-exec when not on classic :-)
   149  	for _, id := range []string{
   150  		"fedora", "centos", "rhel", "opensuse", "suse", "poky",
   151  		"debian", "ubuntu", "arch", "archlinux",
   152  	} {
   153  		restore = release.MockReleaseInfo(&release.OS{ID: id})
   154  		defer restore()
   155  		c.Check(cmd.DistroSupportsReExec(), Equals, false, Commentf("ID: %q", id))
   156  	}
   157  }
   158  
   159  func (s *cmdSuite) TestCoreSupportsReExecNoInfo(c *C) {
   160  	// there's no snapd/info in a just-created tmpdir :-p
   161  	c.Check(cmd.CoreSupportsReExec(c.MkDir()), Equals, false)
   162  }
   163  
   164  func (s *cmdSuite) TestCoreSupportsReExecBadInfo(c *C) {
   165  	// can't read snapd/info if it's a directory
   166  	p := s.snapdPath + "/usr/lib/snapd/info"
   167  	c.Assert(os.MkdirAll(p, 0755), IsNil)
   168  
   169  	c.Check(cmd.CoreSupportsReExec(s.snapdPath), Equals, false)
   170  }
   171  
   172  func (s *cmdSuite) TestCoreSupportsReExecBadInfoContent(c *C) {
   173  	// can't understand snapd/info if all it holds are potatoes
   174  	p := s.snapdPath + "/usr/lib/snapd"
   175  	c.Assert(os.MkdirAll(p, 0755), IsNil)
   176  	c.Assert(ioutil.WriteFile(p+"/info", []byte("potatoes"), 0644), IsNil)
   177  
   178  	c.Check(cmd.CoreSupportsReExec(s.snapdPath), Equals, false)
   179  }
   180  
   181  func (s *cmdSuite) TestCoreSupportsReExecBadVersion(c *C) {
   182  	// can't understand snapd/info if all its version is gibberish
   183  	s.fakeCoreVersion(c, s.snapdPath, "0:")
   184  
   185  	c.Check(cmd.CoreSupportsReExec(s.snapdPath), Equals, false)
   186  }
   187  
   188  func (s *cmdSuite) TestCoreSupportsReExecOldVersion(c *C) {
   189  	// can't re-exec if core version is too old
   190  	defer cmd.MockVersion("2")()
   191  	s.fakeCoreVersion(c, s.snapdPath, "0")
   192  
   193  	c.Check(cmd.CoreSupportsReExec(s.snapdPath), Equals, false)
   194  }
   195  
   196  func (s *cmdSuite) TestCoreSupportsReExec(c *C) {
   197  	defer cmd.MockVersion("2")()
   198  	s.fakeCoreVersion(c, s.snapdPath, "9999")
   199  
   200  	c.Check(cmd.CoreSupportsReExec(s.snapdPath), Equals, true)
   201  }
   202  
   203  func (s *cmdSuite) TestInternalToolPathNoReexec(c *C) {
   204  	restore := cmd.MockOsReadlink(func(string) (string, error) {
   205  		return filepath.Join(dirs.DistroLibExecDir, "snapd"), nil
   206  	})
   207  	defer restore()
   208  
   209  	path, err := cmd.InternalToolPath("potato")
   210  	c.Check(err, IsNil)
   211  	c.Check(path, Equals, filepath.Join(dirs.DistroLibExecDir, "potato"))
   212  }
   213  
   214  func (s *cmdSuite) TestInternalToolPathWithReexec(c *C) {
   215  	s.fakeInternalTool(c, s.snapdPath, "potato")
   216  	restore := cmd.MockOsReadlink(func(string) (string, error) {
   217  		return filepath.Join(s.snapdPath, "/usr/lib/snapd/snapd"), nil
   218  	})
   219  	defer restore()
   220  
   221  	path, err := cmd.InternalToolPath("potato")
   222  	c.Check(err, IsNil)
   223  	c.Check(path, Equals, filepath.Join(dirs.SnapMountDir, "snapd/42/usr/lib/snapd/potato"))
   224  }
   225  
   226  func (s *cmdSuite) TestInternalToolPathWithOtherLocation(c *C) {
   227  	s.fakeInternalTool(c, s.snapdPath, "potato")
   228  	restore := cmd.MockOsReadlink(func(string) (string, error) {
   229  		return filepath.Join("/tmp/tmp.foo_1234/usr/lib/snapd/snapd"), nil
   230  	})
   231  	defer restore()
   232  
   233  	path, err := cmd.InternalToolPath("potato")
   234  	c.Check(err, IsNil)
   235  	c.Check(path, Equals, "/tmp/tmp.foo_1234/usr/lib/snapd/potato")
   236  }
   237  
   238  func (s *cmdSuite) TestInternalToolSnapPathWithOtherLocation(c *C) {
   239  	restore := cmd.MockOsReadlink(func(string) (string, error) {
   240  		return filepath.Join("/tmp/tmp.foo_1234/usr/bin/snap"), nil
   241  	})
   242  	defer restore()
   243  
   244  	path, err := cmd.InternalToolPath("potato")
   245  	c.Check(err, IsNil)
   246  	c.Check(path, Equals, "/tmp/tmp.foo_1234/usr/lib/snapd/potato")
   247  }
   248  
   249  func (s *cmdSuite) TestInternalToolPathWithOtherCrazyLocation(c *C) {
   250  	restore := cmd.MockOsReadlink(func(string) (string, error) {
   251  		return filepath.Join("/usr/foo/usr/tmp/tmp.foo_1234/usr/bin/snap"), nil
   252  	})
   253  	defer restore()
   254  
   255  	path, err := cmd.InternalToolPath("potato")
   256  	c.Check(err, IsNil)
   257  	c.Check(path, Equals, "/usr/foo/usr/tmp/tmp.foo_1234/usr/lib/snapd/potato")
   258  }
   259  
   260  func (s *cmdSuite) TestInternalToolPathWithDevLocationFallback(c *C) {
   261  	restore := cmd.MockOsReadlink(func(string) (string, error) {
   262  		return filepath.Join("/home/dev/snapd/snapd"), nil
   263  	})
   264  	defer restore()
   265  
   266  	path, err := cmd.InternalToolPath("potato")
   267  	c.Check(err, IsNil)
   268  	c.Check(path, Equals, filepath.Join(dirs.DistroLibExecDir, "potato"))
   269  }
   270  
   271  func (s *cmdSuite) TestInternalToolPathWithOtherDevLocationWhenExecutable(c *C) {
   272  	restore := cmd.MockOsReadlink(func(string) (string, error) {
   273  		return filepath.Join(dirs.GlobalRootDir, "/tmp/snapd"), nil
   274  	})
   275  	defer restore()
   276  
   277  	devTool := filepath.Join(dirs.GlobalRootDir, "/tmp/potato")
   278  	err := os.MkdirAll(filepath.Dir(devTool), 0755)
   279  	c.Assert(err, IsNil)
   280  	err = ioutil.WriteFile(devTool, []byte(""), 0755)
   281  	c.Assert(err, IsNil)
   282  
   283  	path, err := cmd.InternalToolPath("potato")
   284  	c.Check(err, IsNil)
   285  	c.Check(path, Equals, filepath.Join(dirs.GlobalRootDir, "/tmp/potato"))
   286  }
   287  
   288  func (s *cmdSuite) TestInternalToolPathWithOtherDevLocationNonExecutable(c *C) {
   289  	restore := cmd.MockOsReadlink(func(string) (string, error) {
   290  		return filepath.Join(dirs.GlobalRootDir, "/tmp/snapd"), nil
   291  	})
   292  	defer restore()
   293  
   294  	devTool := filepath.Join(dirs.GlobalRootDir, "/tmp/non-executable-potato")
   295  	err := os.MkdirAll(filepath.Dir(devTool), 0755)
   296  	c.Assert(err, IsNil)
   297  	err = ioutil.WriteFile(devTool, []byte(""), 0644)
   298  	c.Assert(err, IsNil)
   299  
   300  	path, err := cmd.InternalToolPath("non-executable-potato")
   301  	c.Check(err, IsNil)
   302  	c.Check(path, Equals, filepath.Join(dirs.DistroLibExecDir, "non-executable-potato"))
   303  }
   304  
   305  func (s *cmdSuite) TestInternalToolPathSnapdPathReexec(c *C) {
   306  	restore := cmd.MockOsReadlink(func(string) (string, error) {
   307  		return filepath.Join(dirs.SnapMountDir, "core/111/usr/bin/snap"), nil
   308  	})
   309  	defer restore()
   310  
   311  	p, err := cmd.InternalToolPath("snapd")
   312  	c.Assert(err, IsNil)
   313  	c.Check(p, Equals, filepath.Join(dirs.SnapMountDir, "/core/111/usr/lib/snapd/snapd"))
   314  }
   315  
   316  func (s *cmdSuite) TestInternalToolPathSnapdSnap(c *C) {
   317  	restore := cmd.MockOsReadlink(func(string) (string, error) {
   318  		return filepath.Join(dirs.SnapMountDir, "snapd/22/usr/bin/snap"), nil
   319  	})
   320  	defer restore()
   321  	p, err := cmd.InternalToolPath("snapd")
   322  	c.Assert(err, IsNil)
   323  	c.Check(p, Equals, filepath.Join(dirs.SnapMountDir, "/snapd/22/usr/lib/snapd/snapd"))
   324  }
   325  
   326  func (s *cmdSuite) TestInternalToolPathWithLibexecdirLocation(c *C) {
   327  	defer dirs.SetRootDir(s.fakeroot)
   328  	restore := release.MockReleaseInfo(&release.OS{ID: "fedora"})
   329  	defer restore()
   330  	// reload directory paths
   331  	dirs.SetRootDir("/")
   332  
   333  	restore = cmd.MockOsReadlink(func(string) (string, error) {
   334  		return filepath.Join("/usr/bin/snap"), nil
   335  	})
   336  	defer restore()
   337  
   338  	path, err := cmd.InternalToolPath("potato")
   339  	c.Check(err, IsNil)
   340  	c.Check(path, Equals, filepath.Join("/usr/libexec/snapd/potato"))
   341  }
   342  
   343  func (s *cmdSuite) TestExecInSnapdOrCoreSnap(c *C) {
   344  	defer s.mockReExecFor(c, s.snapdPath, "potato")()
   345  
   346  	c.Check(cmd.ExecInSnapdOrCoreSnap, PanicMatches, `>exec of "[^"]+/potato" in tests<`)
   347  	c.Check(s.execCalled, Equals, 1)
   348  	c.Check(s.lastExecArgv0, Equals, filepath.Join(s.snapdPath, "/usr/lib/snapd/potato"))
   349  	c.Check(s.lastExecArgv, DeepEquals, os.Args)
   350  }
   351  
   352  func (s *cmdSuite) TestExecInOldCoreSnap(c *C) {
   353  	defer s.mockReExecFor(c, s.corePath, "potato")()
   354  
   355  	c.Check(cmd.ExecInSnapdOrCoreSnap, PanicMatches, `>exec of "[^"]+/potato" in tests<`)
   356  	c.Check(s.execCalled, Equals, 1)
   357  	c.Check(s.lastExecArgv0, Equals, filepath.Join(s.corePath, "/usr/lib/snapd/potato"))
   358  	c.Check(s.lastExecArgv, DeepEquals, os.Args)
   359  }
   360  
   361  func (s *cmdSuite) TestExecInSnapdOrCoreSnapBailsNoCoreSupport(c *C) {
   362  	defer s.mockReExecFor(c, s.snapdPath, "potato")()
   363  
   364  	// no "info" -> no core support:
   365  	c.Assert(os.Remove(filepath.Join(s.snapdPath, "/usr/lib/snapd/info")), IsNil)
   366  
   367  	cmd.ExecInSnapdOrCoreSnap()
   368  	c.Check(s.execCalled, Equals, 0)
   369  }
   370  
   371  func (s *cmdSuite) TestExecInSnapdOrCoreSnapMissingExe(c *C) {
   372  	defer s.mockReExecFor(c, s.snapdPath, "potato")()
   373  
   374  	// missing exe:
   375  	c.Assert(os.Remove(filepath.Join(s.snapdPath, "/usr/lib/snapd/potato")), IsNil)
   376  
   377  	cmd.ExecInSnapdOrCoreSnap()
   378  	c.Check(s.execCalled, Equals, 0)
   379  }
   380  
   381  func (s *cmdSuite) TestExecInSnapdOrCoreSnapBadSelfExe(c *C) {
   382  	defer s.mockReExecFor(c, s.snapdPath, "potato")()
   383  
   384  	// missing self/exe:
   385  	c.Assert(os.Remove(filepath.Join(s.fakeroot, "proc/self/exe")), IsNil)
   386  
   387  	cmd.ExecInSnapdOrCoreSnap()
   388  	c.Check(s.execCalled, Equals, 0)
   389  }
   390  
   391  func (s *cmdSuite) TestExecInSnapdOrCoreSnapBailsNoDistroSupport(c *C) {
   392  	defer s.mockReExecFor(c, s.snapdPath, "potato")()
   393  
   394  	// no distro support:
   395  	defer release.MockOnClassic(false)()
   396  
   397  	cmd.ExecInSnapdOrCoreSnap()
   398  	c.Check(s.execCalled, Equals, 0)
   399  }
   400  
   401  func (s *cmdSuite) TestExecInSnapdOrCoreSnapNoDouble(c *C) {
   402  	selfExe := filepath.Join(s.fakeroot, "proc/self/exe")
   403  	err := os.Symlink(filepath.Join(s.fakeroot, "/snap/core/42/usr/lib/snapd"), selfExe)
   404  	c.Assert(err, IsNil)
   405  	cmd.MockSelfExe(selfExe)
   406  
   407  	cmd.ExecInSnapdOrCoreSnap()
   408  	c.Check(s.execCalled, Equals, 0)
   409  }
   410  
   411  func (s *cmdSuite) TestExecInSnapdOrCoreSnapDisabled(c *C) {
   412  	defer s.mockReExecFor(c, s.snapdPath, "potato")()
   413  
   414  	os.Setenv("SNAP_REEXEC", "0")
   415  	defer os.Unsetenv("SNAP_REEXEC")
   416  
   417  	cmd.ExecInSnapdOrCoreSnap()
   418  	c.Check(s.execCalled, Equals, 0)
   419  }