github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/snap/validate_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016 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 snap_test
    21  
    22  import (
    23  	"fmt"
    24  	"regexp"
    25  	"strconv"
    26  	"strings"
    27  
    28  	. "gopkg.in/check.v1"
    29  
    30  	. "github.com/snapcore/snapd/snap"
    31  
    32  	"github.com/snapcore/snapd/testutil"
    33  )
    34  
    35  type ValidateSuite struct {
    36  	testutil.BaseTest
    37  }
    38  
    39  var _ = Suite(&ValidateSuite{})
    40  
    41  func createSampleApp() *AppInfo {
    42  	socket := &SocketInfo{
    43  		Name:         "sock",
    44  		ListenStream: "$SNAP_COMMON/socket",
    45  	}
    46  	app := &AppInfo{
    47  		Snap: &Info{
    48  			SideInfo: SideInfo{
    49  				RealName: "mysnap",
    50  				Revision: R(20),
    51  			},
    52  		},
    53  		Name:        "foo",
    54  		Daemon:      "simple",
    55  		DaemonScope: SystemDaemon,
    56  		Plugs:       map[string]*PlugInfo{"network-bind": {}},
    57  		Sockets: map[string]*SocketInfo{
    58  			"sock": socket,
    59  		},
    60  	}
    61  	socket.App = app
    62  	return app
    63  }
    64  
    65  func (s *ValidateSuite) SetUpTest(c *C) {
    66  	s.BaseTest.SetUpTest(c)
    67  	s.BaseTest.AddCleanup(MockSanitizePlugsSlots(func(snapInfo *Info) {}))
    68  }
    69  
    70  func (s *ValidateSuite) TearDownTest(c *C) {
    71  	s.BaseTest.TearDownTest(c)
    72  }
    73  
    74  func (s *ValidateSuite) TestValidateVersion(c *C) {
    75  	validVersions := []string{
    76  		"0", "v1.0", "0.12+16.04.20160126-0ubuntu1",
    77  		"1:6.0.1+r16-3", "1.0~", "1.0+", "README.~1~",
    78  		"a+++++++++++++++++++++++++++++++",
    79  		"AZaz:.+~-123",
    80  	}
    81  	for _, version := range validVersions {
    82  		err := ValidateVersion(version)
    83  		c.Assert(err, IsNil)
    84  	}
    85  	invalidVersionsTable := [][2]string{
    86  		{"~foo", `must start with an ASCII alphanumeric (and not '~')`},
    87  		{"+foo", `must start with an ASCII alphanumeric (and not '+')`},
    88  
    89  		{"foo:", `must end with an ASCII alphanumeric or one of '+' or '~' (and not ':')`},
    90  		{"foo.", `must end with an ASCII alphanumeric or one of '+' or '~' (and not '.')`},
    91  		{"foo-", `must end with an ASCII alphanumeric or one of '+' or '~' (and not '-')`},
    92  
    93  		{"horrible_underscores", `contains invalid characters: "_"`},
    94  		{"foo($bar^baz$)meep", `contains invalid characters: "($", "^", "$)"`},
    95  
    96  		{"árbol", `must be printable, non-whitespace ASCII`},
    97  		{"日本語", `must be printable, non-whitespace ASCII`},
    98  		{"한글", `must be printable, non-whitespace ASCII`},
    99  		{"ру́сский язы́к", `must be printable, non-whitespace ASCII`},
   100  
   101  		{"~foo$bar:", `must start with an ASCII alphanumeric (and not '~'),` +
   102  			` must end with an ASCII alphanumeric or one of '+' or '~' (and not ':'),` +
   103  			` and contains invalid characters: "$"`},
   104  	}
   105  	for _, t := range invalidVersionsTable {
   106  		version, reason := t[0], t[1]
   107  		err := ValidateVersion(version)
   108  		c.Assert(err, NotNil)
   109  		c.Assert(err.Error(), Equals, fmt.Sprintf("invalid snap version %s: %s", strconv.QuoteToASCII(version), reason))
   110  	}
   111  	// version cannot be empty
   112  	c.Assert(ValidateVersion(""), ErrorMatches, `invalid snap version: cannot be empty`)
   113  	// version length cannot be >32
   114  	c.Assert(ValidateVersion("this-version-is-a-little-bit-older"), ErrorMatches,
   115  		`invalid snap version "this-version-is-a-little-bit-older": cannot be longer than 32 characters \(got: 34\)`)
   116  }
   117  
   118  func (s *ValidateSuite) TestValidateLicense(c *C) {
   119  	validLicenses := []string{
   120  		"GPL-3.0", "(GPL-3.0)", "GPL-3.0+", "GPL-3.0 AND GPL-2.0", "GPL-3.0 OR GPL-2.0", "MIT OR (GPL-3.0 AND GPL-2.0)", "MIT OR(GPL-3.0 AND GPL-2.0)",
   121  	}
   122  	for _, epoch := range validLicenses {
   123  		err := ValidateLicense(epoch)
   124  		c.Assert(err, IsNil)
   125  	}
   126  	invalidLicenses := []string{
   127  		"GPL~3.0", "3.0-GPL", "(GPL-3.0", "(GPL-3.0))", "GPL-3.0++", "+GPL-3.0", "GPL-3.0 GPL-2.0",
   128  	}
   129  	for _, epoch := range invalidLicenses {
   130  		err := ValidateLicense(epoch)
   131  		c.Assert(err, NotNil)
   132  	}
   133  }
   134  
   135  func (s *ValidateSuite) TestValidateHook(c *C) {
   136  	validHooks := []*HookInfo{
   137  		{Name: "a"},
   138  		{Name: "aaa"},
   139  		{Name: "a-a"},
   140  		{Name: "aa-a"},
   141  		{Name: "a-aa"},
   142  		{Name: "a-b-c"},
   143  		{Name: "valid", CommandChain: []string{"valid"}},
   144  	}
   145  	for _, hook := range validHooks {
   146  		err := ValidateHook(hook)
   147  		c.Assert(err, IsNil)
   148  	}
   149  	invalidHooks := []*HookInfo{
   150  		{Name: ""},
   151  		{Name: "a a"},
   152  		{Name: "a--a"},
   153  		{Name: "-a"},
   154  		{Name: "a-"},
   155  		{Name: "0"},
   156  		{Name: "123"},
   157  		{Name: "123abc"},
   158  		{Name: "日本語"},
   159  	}
   160  	for _, hook := range invalidHooks {
   161  		err := ValidateHook(hook)
   162  		c.Assert(err, ErrorMatches, `invalid hook name: ".*"`)
   163  	}
   164  	invalidHooks = []*HookInfo{
   165  		{Name: "valid", CommandChain: []string{"in'valid"}},
   166  		{Name: "valid", CommandChain: []string{"in valid"}},
   167  	}
   168  	for _, hook := range invalidHooks {
   169  		err := ValidateHook(hook)
   170  		c.Assert(err, ErrorMatches, `hook command-chain contains illegal.*`)
   171  	}
   172  }
   173  
   174  // ValidateApp
   175  
   176  func (s *ValidateSuite) TestValidateAppSockets(c *C) {
   177  	app := createSampleApp()
   178  	app.Sockets["sock"].SocketMode = 0600
   179  	c.Check(ValidateApp(app), IsNil)
   180  }
   181  
   182  func (s *ValidateSuite) TestValidateAppSocketsEmptyPermsOk(c *C) {
   183  	app := createSampleApp()
   184  	c.Check(ValidateApp(app), IsNil)
   185  }
   186  
   187  func (s *ValidateSuite) TestValidateAppSocketsWrongPerms(c *C) {
   188  	app := createSampleApp()
   189  	app.Sockets["sock"].SocketMode = 1234
   190  	err := ValidateApp(app)
   191  	c.Assert(err, ErrorMatches, `invalid definition of socket "sock": cannot use mode: 2322`)
   192  }
   193  
   194  func (s *ValidateSuite) TestValidateAppSocketsMissingNetworkBindPlug(c *C) {
   195  	app := createSampleApp()
   196  	delete(app.Plugs, "network-bind")
   197  	err := ValidateApp(app)
   198  	c.Assert(
   199  		err, ErrorMatches,
   200  		`"network-bind" interface plug is required when sockets are used`)
   201  }
   202  
   203  func (s *ValidateSuite) TestValidateAppSocketsEmptyListenStream(c *C) {
   204  	app := createSampleApp()
   205  	app.Sockets["sock"].ListenStream = ""
   206  	err := ValidateApp(app)
   207  	c.Assert(err, ErrorMatches, `invalid definition of socket "sock": "listen-stream" is not defined`)
   208  }
   209  
   210  func (s *ValidateSuite) TestValidateAppSocketsInvalidName(c *C) {
   211  	app := createSampleApp()
   212  	app.Sockets["sock"].Name = "invalid name"
   213  	err := ValidateApp(app)
   214  	c.Assert(err, ErrorMatches, `invalid definition of socket "invalid name": invalid socket name: "invalid name"`)
   215  }
   216  
   217  func (s *ValidateSuite) TestValidateAppSocketsValidListenStreamAddresses(c *C) {
   218  	app := createSampleApp()
   219  	validListenAddresses := []string{
   220  		// socket paths using variables as prefix
   221  		"$SNAP_DATA/my.socket",
   222  		"$SNAP_COMMON/my.socket",
   223  		"$XDG_RUNTIME_DIR/my.socket",
   224  		// abstract sockets
   225  		"@snap.mysnap.my.socket",
   226  		// addresses and ports
   227  		"1",
   228  		"1023",
   229  		"1024",
   230  		"65535",
   231  		"127.0.0.1:8080",
   232  		"[::]:8080",
   233  		"[::1]:8080",
   234  	}
   235  	socket := app.Sockets["sock"]
   236  	for _, validAddress := range validListenAddresses {
   237  		socket.ListenStream = validAddress
   238  		err := ValidateApp(app)
   239  		c.Check(err, IsNil, Commentf(validAddress))
   240  	}
   241  }
   242  
   243  func (s *ValidateSuite) TestValidateAppSocketsInvalidListenStreamPath(c *C) {
   244  	app := createSampleApp()
   245  	invalidListenAddresses := []string{
   246  		// socket paths out of the snap dirs
   247  		"/some/path/my.socket",
   248  		"/var/snap/mysnap/20/my.socket", // path is correct but has hardcoded prefix
   249  		// Variables only valid for user mode services
   250  		"$SNAP_USER_DATA/my.socket",
   251  		"$SNAP_USER_COMMON/my.socket",
   252  	}
   253  	socket := app.Sockets["sock"]
   254  	for _, invalidAddress := range invalidListenAddresses {
   255  		socket.ListenStream = invalidAddress
   256  		err := ValidateApp(app)
   257  		c.Assert(err, ErrorMatches, `invalid definition of socket "sock": invalid "listen-stream": system daemon sockets must have a prefix of .*`)
   258  	}
   259  }
   260  
   261  func (s *ValidateSuite) TestValidateAppSocketsInvalidListenStreamPathContainsDots(c *C) {
   262  	app := createSampleApp()
   263  	app.Sockets["sock"].ListenStream = "$SNAP/../some.path"
   264  	err := ValidateApp(app)
   265  	c.Assert(
   266  		err, ErrorMatches,
   267  		`invalid definition of socket "sock": invalid "listen-stream": "\$SNAP/../some.path" should be written as "some.path"`)
   268  }
   269  
   270  func (s *ValidateSuite) TestValidateAppSocketsInvalidListenStreamPathPrefix(c *C) {
   271  	app := createSampleApp()
   272  	invalidListenAddresses := []string{
   273  		"$SNAP/my.socket", // snap dir is not writable
   274  		"$SOMEVAR/my.socket",
   275  	}
   276  	socket := app.Sockets["sock"]
   277  	for _, invalidAddress := range invalidListenAddresses {
   278  		socket.ListenStream = invalidAddress
   279  		err := ValidateApp(app)
   280  		c.Assert(
   281  			err, ErrorMatches,
   282  			`invalid definition of socket "sock": invalid "listen-stream": system daemon sockets must have a prefix of \$SNAP_DATA, \$SNAP_COMMON or \$XDG_RUNTIME_DIR`)
   283  	}
   284  }
   285  
   286  func (s *ValidateSuite) TestValidateAppSocketsInvalidListenStreamAbstractSocket(c *C) {
   287  	app := createSampleApp()
   288  	invalidListenAddresses := []string{
   289  		"@snap.mysnap",
   290  		"@snap.mysnap\000.foo",
   291  		"@snap.notmysnap.my.socket",
   292  		"@some.other.name",
   293  		"@snap.myappiswrong.foo",
   294  	}
   295  	socket := app.Sockets["sock"]
   296  	for _, invalidAddress := range invalidListenAddresses {
   297  		socket.ListenStream = invalidAddress
   298  		err := ValidateApp(app)
   299  		c.Assert(err, ErrorMatches, `invalid definition of socket "sock": path for "listen-stream" must be prefixed with.*`)
   300  	}
   301  }
   302  
   303  func (s *ValidateSuite) TestValidateAppSocketsInvalidListenStreamAddress(c *C) {
   304  	app := createSampleApp()
   305  	app.Daemon = "simple"
   306  	app.DaemonScope = SystemDaemon
   307  	invalidListenAddresses := []string{
   308  		"10.0.1.1:8080",
   309  		"[fafa::baba]:8080",
   310  		"127.0.0.1\000:8080",
   311  		"127.0.0.1::8080",
   312  	}
   313  	socket := app.Sockets["sock"]
   314  	for _, invalidAddress := range invalidListenAddresses {
   315  		socket.ListenStream = invalidAddress
   316  		err := ValidateApp(app)
   317  		c.Assert(err, ErrorMatches, `invalid definition of socket "sock": invalid "listen-stream" address ".*", must be one of: 127\.0\.0\.1, \[::1\], \[::\]`)
   318  	}
   319  }
   320  
   321  func (s *ValidateSuite) TestValidateAppSocketsInvalidListenStreamPort(c *C) {
   322  	app := createSampleApp()
   323  	invalidPorts := []string{
   324  		"0",
   325  		"66536",
   326  		"-8080",
   327  		"12312345345",
   328  		"[::]:-123",
   329  		"[::1]:3452345234",
   330  		"invalid",
   331  		"[::]:invalid",
   332  	}
   333  	socket := app.Sockets["sock"]
   334  	for _, invalidPort := range invalidPorts {
   335  		socket.ListenStream = invalidPort
   336  		err := ValidateApp(app)
   337  		c.Assert(err, ErrorMatches, `invalid definition of socket "sock": invalid "listen-stream" port number.*`)
   338  	}
   339  }
   340  
   341  func (s *ValidateSuite) TestValidateAppUserSocketsValidListenStreamAddresses(c *C) {
   342  	app := createSampleApp()
   343  	app.DaemonScope = UserDaemon
   344  	validListenAddresses := []string{
   345  		// socket paths using variables as prefix
   346  		"$SNAP_USER_DATA/my.socket",
   347  		"$SNAP_USER_COMMON/my.socket",
   348  		"$XDG_RUNTIME_DIR/my.socket",
   349  		// abstract sockets
   350  		"@snap.mysnap.my.socket",
   351  		// addresses and ports
   352  		"1",
   353  		"1023",
   354  		"1024",
   355  		"65535",
   356  		"127.0.0.1:8080",
   357  		"[::]:8080",
   358  		"[::1]:8080",
   359  	}
   360  	socket := app.Sockets["sock"]
   361  	for _, validAddress := range validListenAddresses {
   362  		socket.ListenStream = validAddress
   363  		err := ValidateApp(app)
   364  		c.Check(err, IsNil, Commentf(validAddress))
   365  	}
   366  }
   367  
   368  func (s *ValidateSuite) TestValidateAppUserSocketsInvalidListenStreamPath(c *C) {
   369  	app := createSampleApp()
   370  	app.DaemonScope = UserDaemon
   371  	invalidListenAddresses := []string{
   372  		// socket paths out of the snap dirs
   373  		"/some/path/my.socket",
   374  		// Variables only valid for system mode services
   375  		"$SNAP_DATA/my.socket",
   376  		"$SNAP_COMMON/my.socket",
   377  	}
   378  	socket := app.Sockets["sock"]
   379  	for _, invalidAddress := range invalidListenAddresses {
   380  		socket.ListenStream = invalidAddress
   381  		err := ValidateApp(app)
   382  		c.Check(err, ErrorMatches, `invalid definition of socket "sock": invalid "listen-stream": user daemon sockets must have a prefix of .*`)
   383  	}
   384  }
   385  
   386  func (s *ValidateSuite) TestValidateAppUserSocketsInvalidListenStreamAbstractSocket(c *C) {
   387  	app := createSampleApp()
   388  	app.DaemonScope = UserDaemon
   389  	invalidListenAddresses := []string{
   390  		"@snap.mysnap",
   391  		"@snap.mysnap\000.foo",
   392  		"@snap.notmysnap.my.socket",
   393  		"@some.other.name",
   394  		"@snap.myappiswrong.foo",
   395  	}
   396  	socket := app.Sockets["sock"]
   397  	for _, invalidAddress := range invalidListenAddresses {
   398  		socket.ListenStream = invalidAddress
   399  		err := ValidateApp(app)
   400  		c.Assert(err, ErrorMatches, `invalid definition of socket "sock": path for "listen-stream" must be prefixed with.*`)
   401  	}
   402  }
   403  
   404  func (s *ValidateSuite) TestValidateAppUserSocketsInvalidListenStreamPort(c *C) {
   405  	app := createSampleApp()
   406  	app.DaemonScope = UserDaemon
   407  	invalidListenAddresses := []string{
   408  		"0",
   409  		"66536",
   410  		"-8080",
   411  		"12312345345",
   412  		"[::]:-123",
   413  		"[::1]:3452345234",
   414  		"invalid",
   415  		"[::]:invalid",
   416  	}
   417  	socket := app.Sockets["sock"]
   418  	for _, invalidAddress := range invalidListenAddresses {
   419  		socket.ListenStream = invalidAddress
   420  		err := ValidateApp(app)
   421  		c.Check(err, ErrorMatches, `invalid definition of socket "sock": invalid "listen-stream" port number .*`)
   422  	}
   423  }
   424  
   425  func (s *ValidateSuite) TestAppWhitelistSimple(c *C) {
   426  	c.Check(ValidateApp(&AppInfo{Name: "foo", Command: "foo"}), IsNil)
   427  	c.Check(ValidateApp(&AppInfo{Name: "foo", StopCommand: "foo"}), IsNil)
   428  	c.Check(ValidateApp(&AppInfo{Name: "foo", PostStopCommand: "foo"}), IsNil)
   429  }
   430  
   431  func (s *ValidateSuite) TestAppWhitelistWithVars(c *C) {
   432  	c.Check(ValidateApp(&AppInfo{Name: "foo", Command: "foo $SNAP_DATA"}), IsNil)
   433  	c.Check(ValidateApp(&AppInfo{Name: "foo", StopCommand: "foo $SNAP_DATA"}), IsNil)
   434  	c.Check(ValidateApp(&AppInfo{Name: "foo", PostStopCommand: "foo $SNAP_DATA"}), IsNil)
   435  }
   436  
   437  func (s *ValidateSuite) TestAppWhitelistIllegal(c *C) {
   438  	c.Check(ValidateApp(&AppInfo{Name: "x\n"}), NotNil)
   439  	c.Check(ValidateApp(&AppInfo{Name: "test!me"}), NotNil)
   440  	c.Check(ValidateApp(&AppInfo{Name: "test'me"}), NotNil)
   441  	c.Check(ValidateApp(&AppInfo{Name: "foo", Command: "foo\n"}), NotNil)
   442  	c.Check(ValidateApp(&AppInfo{Name: "foo", StopCommand: "foo\n"}), NotNil)
   443  	c.Check(ValidateApp(&AppInfo{Name: "foo", PostStopCommand: "foo\n"}), NotNil)
   444  	c.Check(ValidateApp(&AppInfo{Name: "foo", BusName: "foo\n"}), NotNil)
   445  	c.Check(ValidateApp(&AppInfo{Name: "foo", CommandChain: []string{"bar'baz"}}), NotNil)
   446  	c.Check(ValidateApp(&AppInfo{Name: "foo", CommandChain: []string{"bar baz"}}), NotNil)
   447  }
   448  
   449  func (s *ValidateSuite) TestAppDaemonValue(c *C) {
   450  	for _, t := range []struct {
   451  		daemon string
   452  		ok     bool
   453  	}{
   454  		// good
   455  		{"", true},
   456  		{"simple", true},
   457  		{"forking", true},
   458  		{"oneshot", true},
   459  		{"dbus", true},
   460  		{"notify", true},
   461  		// bad
   462  		{"invalid-thing", false},
   463  	} {
   464  		var daemonScope DaemonScope
   465  		if t.daemon != "" {
   466  			daemonScope = SystemDaemon
   467  		}
   468  		if t.ok {
   469  			c.Check(ValidateApp(&AppInfo{Name: "foo", Daemon: t.daemon, DaemonScope: daemonScope}), IsNil)
   470  		} else {
   471  			c.Check(ValidateApp(&AppInfo{Name: "foo", Daemon: t.daemon, DaemonScope: daemonScope}), ErrorMatches, fmt.Sprintf(`"daemon" field contains invalid value %q`, t.daemon))
   472  		}
   473  	}
   474  }
   475  
   476  func (s *ValidateSuite) TestAppDaemonScopeValue(c *C) {
   477  	for _, t := range []struct {
   478  		daemon      string
   479  		daemonScope DaemonScope
   480  		ok          bool
   481  	}{
   482  		// good
   483  		{"", "", true},
   484  		{"simple", SystemDaemon, true},
   485  		{"simple", UserDaemon, true},
   486  		// bad
   487  		{"simple", "", false},
   488  		{"", SystemDaemon, false},
   489  		{"", UserDaemon, false},
   490  		{"simple", "invalid-mode", false},
   491  	} {
   492  		app := &AppInfo{Name: "foo", Daemon: t.daemon, DaemonScope: t.daemonScope}
   493  		err := ValidateApp(app)
   494  		if t.ok {
   495  			c.Check(err, IsNil)
   496  		} else if t.daemon == "" {
   497  			c.Check(err, ErrorMatches, `"daemon-scope" can only be set for daemons`)
   498  		} else if t.daemonScope == "" {
   499  			c.Check(err, ErrorMatches, `"daemon-scope" must be set for daemons`)
   500  		} else {
   501  			c.Check(err, ErrorMatches, fmt.Sprintf(`invalid "daemon-scope": %q`, t.daemonScope))
   502  		}
   503  	}
   504  }
   505  
   506  func (s *ValidateSuite) TestAppStopMode(c *C) {
   507  	// check services
   508  	for _, t := range []struct {
   509  		stopMode StopModeType
   510  		ok       bool
   511  	}{
   512  		// good
   513  		{"", true},
   514  		{"sigterm", true},
   515  		{"sigterm-all", true},
   516  		{"sighup", true},
   517  		{"sighup-all", true},
   518  		{"sigusr1", true},
   519  		{"sigusr1-all", true},
   520  		{"sigusr2", true},
   521  		{"sigusr2-all", true},
   522  		// bad
   523  		{"invalid-thing", false},
   524  	} {
   525  		if t.ok {
   526  			c.Check(ValidateApp(&AppInfo{Name: "foo", Daemon: "simple", DaemonScope: SystemDaemon, StopMode: t.stopMode}), IsNil)
   527  		} else {
   528  			c.Check(ValidateApp(&AppInfo{Name: "foo", Daemon: "simple", DaemonScope: SystemDaemon, StopMode: t.stopMode}), ErrorMatches, fmt.Sprintf(`"stop-mode" field contains invalid value %q`, t.stopMode))
   529  		}
   530  	}
   531  
   532  	// non-services cannot have a stop-mode
   533  	err := ValidateApp(&AppInfo{Name: "foo", Daemon: "", StopMode: "sigterm"})
   534  	c.Check(err, ErrorMatches, `"stop-mode" cannot be used for "foo", only for services`)
   535  }
   536  
   537  func (s *ValidateSuite) TestAppRefreshMode(c *C) {
   538  	// check services
   539  	for _, t := range []struct {
   540  		refreshMode string
   541  		ok          bool
   542  	}{
   543  		// good
   544  		{"", true},
   545  		{"endure", true},
   546  		{"restart", true},
   547  		// bad
   548  		{"invalid-thing", false},
   549  	} {
   550  		err := ValidateApp(&AppInfo{Name: "foo", Daemon: "simple", DaemonScope: SystemDaemon, RefreshMode: t.refreshMode})
   551  		if t.ok {
   552  			c.Check(err, IsNil)
   553  		} else {
   554  			c.Check(err, ErrorMatches, fmt.Sprintf(`"refresh-mode" field contains invalid value %q`, t.refreshMode))
   555  		}
   556  	}
   557  
   558  	// non-services cannot have a refresh-mode
   559  	err := ValidateApp(&AppInfo{Name: "foo", Daemon: "", RefreshMode: "endure"})
   560  	c.Check(err, ErrorMatches, `"refresh-mode" cannot be used for "foo", only for services`)
   561  }
   562  
   563  func (s *ValidateSuite) TestAppWhitelistError(c *C) {
   564  	err := ValidateApp(&AppInfo{Name: "foo", Command: "x\n"})
   565  	c.Assert(err, NotNil)
   566  	c.Check(err.Error(), Equals, `app description field 'command' contains illegal "x\n" (legal: '^[A-Za-z0-9/. _#:$-]*$')`)
   567  }
   568  
   569  func (s *ValidateSuite) TestAppActivatesOn(c *C) {
   570  	info, err := InfoFromSnapYaml([]byte(`name: foo
   571  version: 1.0
   572  slots:
   573    dbus-slot:
   574      interface: dbus
   575      bus: system
   576  apps:
   577    server:
   578      daemon: simple
   579      activates-on: [dbus-slot]
   580  `))
   581  	c.Assert(err, IsNil)
   582  	app := info.Apps["server"]
   583  	c.Check(ValidateApp(app), IsNil)
   584  }
   585  
   586  func (s *ValidateSuite) TestAppActivatesOnNotDaemon(c *C) {
   587  	info, err := InfoFromSnapYaml([]byte(`name: foo
   588  version: 1.0
   589  slots:
   590    dbus-slot:
   591  apps:
   592    server:
   593      activates-on: [dbus-slot]
   594  `))
   595  	c.Assert(err, IsNil)
   596  	app := info.Apps["server"]
   597  	c.Check(ValidateApp(app), ErrorMatches, `activates-on is only applicable to services`)
   598  }
   599  
   600  func (s *ValidateSuite) TestAppActivatesOnSlotNotDbus(c *C) {
   601  	info, err := InfoFromSnapYaml([]byte(`name: foo
   602  version: 1.0
   603  apps:
   604    server:
   605      daemon: simple
   606      slots: [network-bind]
   607      activates-on: [network-bind]
   608  `))
   609  	c.Assert(err, IsNil)
   610  	app := info.Apps["server"]
   611  	c.Check(ValidateApp(app), ErrorMatches, `invalid activates-on value "network-bind": slot does not use dbus interface`)
   612  }
   613  
   614  func (s *ValidateSuite) TestAppActivatesOnDaemonScopeMismatch(c *C) {
   615  	info, err := InfoFromSnapYaml([]byte(`name: foo
   616  version: 1.0
   617  slots:
   618    dbus-slot:
   619      interface: dbus
   620      bus: session
   621  apps:
   622    server:
   623      daemon: simple
   624      activates-on: [dbus-slot]
   625  `))
   626  	c.Assert(err, IsNil)
   627  	app := info.Apps["server"]
   628  	c.Check(ValidateApp(app), ErrorMatches, `invalid activates-on value "dbus-slot": bus "session" does not match daemon-scope "system"`)
   629  
   630  	info, err = InfoFromSnapYaml([]byte(`name: foo
   631  version: 1.0
   632  slots:
   633    dbus-slot:
   634      interface: dbus
   635      bus: system
   636  apps:
   637    server:
   638      daemon: simple
   639      daemon-scope: user
   640      activates-on: [dbus-slot]
   641  `))
   642  	c.Assert(err, IsNil)
   643  	app = info.Apps["server"]
   644  	c.Check(ValidateApp(app), ErrorMatches, `invalid activates-on value "dbus-slot": bus "system" does not match daemon-scope "user"`)
   645  }
   646  
   647  func (s *ValidateSuite) TestAppActivatesOnDuplicateApp(c *C) {
   648  	info, err := InfoFromSnapYaml([]byte(`name: foo
   649  version: 1.0
   650  slots:
   651    dbus-slot:
   652      interface: dbus
   653      bus: system
   654  apps:
   655    server:
   656      daemon: simple
   657      activates-on: [dbus-slot]
   658    dup:
   659      daemon: simple
   660      activates-on: [dbus-slot]
   661  `))
   662  	c.Assert(err, IsNil)
   663  	app := info.Apps["server"]
   664  	c.Check(ValidateApp(app), ErrorMatches, `invalid activates-on value "dbus-slot": slot is also activatable on app "dup"`)
   665  }
   666  
   667  // Validate
   668  
   669  func (s *ValidateSuite) TestDetectIllegalYamlBinaries(c *C) {
   670  	info, err := InfoFromSnapYaml([]byte(`name: foo
   671  version: 1.0
   672  apps:
   673   tes!me:
   674     command: someething
   675  `))
   676  	c.Assert(err, IsNil)
   677  
   678  	err = Validate(info)
   679  	c.Check(err, NotNil)
   680  }
   681  
   682  func (s *ValidateSuite) TestDetectIllegalYamlService(c *C) {
   683  	info, err := InfoFromSnapYaml([]byte(`name: foo
   684  version: 1.0
   685  apps:
   686   tes!me:
   687     command: something
   688     daemon: forking
   689  `))
   690  	c.Assert(err, IsNil)
   691  
   692  	err = Validate(info)
   693  	c.Check(err, NotNil)
   694  }
   695  
   696  func (s *ValidateSuite) TestIllegalSnapName(c *C) {
   697  	info, err := InfoFromSnapYaml([]byte(`name: foo.something
   698  version: 1.0
   699  `))
   700  	c.Assert(err, IsNil)
   701  
   702  	err = Validate(info)
   703  	c.Check(err, ErrorMatches, `invalid snap name: "foo.something"`)
   704  }
   705  
   706  func (s *ValidateSuite) TestValidateChecksName(c *C) {
   707  	info, err := InfoFromSnapYaml([]byte(`
   708  version: 1.0
   709  `))
   710  	c.Assert(err, IsNil)
   711  
   712  	err = Validate(info)
   713  	c.Check(err, ErrorMatches, `snap name cannot be empty`)
   714  }
   715  
   716  func (s *ValidateSuite) TestIllegalSnapEpoch(c *C) {
   717  	_, err := InfoFromSnapYaml([]byte(`name: foo
   718  version: 1.0
   719  epoch: 0*
   720  `))
   721  	c.Assert(err, ErrorMatches, `.*invalid epoch.*`)
   722  }
   723  
   724  func (s *ValidateSuite) TestMissingSnapEpochIsOkay(c *C) {
   725  	info, err := InfoFromSnapYaml([]byte(`name: foo
   726  version: 1.0
   727  `))
   728  	c.Assert(err, IsNil)
   729  	c.Assert(Validate(info), IsNil)
   730  }
   731  
   732  func (s *ValidateSuite) TestIllegalSnapLicense(c *C) {
   733  	info, err := InfoFromSnapYaml([]byte(`name: foo
   734  version: 1.0
   735  license: GPL~3.0
   736  `))
   737  	c.Assert(err, IsNil)
   738  
   739  	err = Validate(info)
   740  	c.Check(err, ErrorMatches, `cannot validate license "GPL~3.0": unknown license: GPL~3.0`)
   741  }
   742  
   743  func (s *ValidateSuite) TestMissingSnapLicenseIsOkay(c *C) {
   744  	info, err := InfoFromSnapYaml([]byte(`name: foo
   745  version: 1.0
   746  `))
   747  	c.Assert(err, IsNil)
   748  	c.Assert(Validate(info), IsNil)
   749  }
   750  
   751  func (s *ValidateSuite) TestIllegalHookName(c *C) {
   752  	hookType := NewHookType(regexp.MustCompile(".*"))
   753  	restore := MockSupportedHookTypes([]*HookType{hookType})
   754  	defer restore()
   755  
   756  	info, err := InfoFromSnapYaml([]byte(`name: foo
   757  version: 1.0
   758  hooks:
   759    123abc:
   760  `))
   761  	c.Assert(err, IsNil)
   762  
   763  	err = Validate(info)
   764  	c.Check(err, ErrorMatches, `invalid hook name: "123abc"`)
   765  }
   766  
   767  func (s *ValidateSuite) TestPlugSlotNamesUnique(c *C) {
   768  	info, err := InfoFromSnapYaml([]byte(`name: snap
   769  version: 0
   770  plugs:
   771   foo:
   772  slots:
   773   foo:
   774  `))
   775  	c.Assert(err, IsNil)
   776  	err = Validate(info)
   777  	c.Check(err, ErrorMatches, `cannot have plug and slot with the same name: "foo"`)
   778  }
   779  
   780  func (s *ValidateSuite) TestIllegalAliasName(c *C) {
   781  	info, err := InfoFromSnapYaml([]byte(`name: foo
   782  version: 1.0
   783  apps:
   784    foo:
   785      aliases: [foo$]
   786  `))
   787  	c.Assert(err, IsNil)
   788  
   789  	err = Validate(info)
   790  	c.Check(err, ErrorMatches, `cannot have "foo\$" as alias name for app "foo" - use only letters, digits, dash, underscore and dot characters`)
   791  }
   792  
   793  func (s *ValidateSuite) TestValidatePlugSlotName(c *C) {
   794  	const yaml1 = `
   795  name: invalid-plugs
   796  version: 1
   797  plugs:
   798    p--lug: null
   799  `
   800  	strk := NewScopedTracker()
   801  	info, err := InfoFromSnapYamlWithSideInfo([]byte(yaml1), nil, strk)
   802  	c.Assert(err, IsNil)
   803  	c.Assert(info.Plugs, HasLen, 1)
   804  	err = Validate(info)
   805  	c.Assert(err, ErrorMatches, `invalid plug name: "p--lug"`)
   806  
   807  	const yaml2 = `
   808  name: invalid-slots
   809  version: 1
   810  slots:
   811    s--lot: null
   812  `
   813  	strk = NewScopedTracker()
   814  	info, err = InfoFromSnapYamlWithSideInfo([]byte(yaml2), nil, strk)
   815  	c.Assert(err, IsNil)
   816  	c.Assert(info.Slots, HasLen, 1)
   817  	err = Validate(info)
   818  	c.Assert(err, ErrorMatches, `invalid slot name: "s--lot"`)
   819  
   820  	const yaml3 = `
   821  name: invalid-plugs-iface
   822  version: 1
   823  plugs:
   824    plug:
   825      interface: i--face
   826  `
   827  	strk = NewScopedTracker()
   828  	info, err = InfoFromSnapYamlWithSideInfo([]byte(yaml3), nil, strk)
   829  	c.Assert(err, IsNil)
   830  	c.Assert(info.Plugs, HasLen, 1)
   831  	err = Validate(info)
   832  	c.Assert(err, ErrorMatches, `invalid interface name "i--face" for plug "plug"`)
   833  
   834  	const yaml4 = `
   835  name: invalid-slots-iface
   836  version: 1
   837  slots:
   838    slot:
   839      interface: i--face
   840  `
   841  	strk = NewScopedTracker()
   842  	info, err = InfoFromSnapYamlWithSideInfo([]byte(yaml4), nil, strk)
   843  	c.Assert(err, IsNil)
   844  	c.Assert(info.Slots, HasLen, 1)
   845  	err = Validate(info)
   846  	c.Assert(err, ErrorMatches, `invalid interface name "i--face" for slot "slot"`)
   847  }
   848  
   849  func (s *ValidateSuite) TestValidateBaseNone(c *C) {
   850  	const yaml = `name: requires-base
   851  version: 1
   852  base: none
   853  `
   854  	strk := NewScopedTracker()
   855  	info, err := InfoFromSnapYamlWithSideInfo([]byte(yaml), nil, strk)
   856  	c.Assert(err, IsNil)
   857  	err = Validate(info)
   858  	c.Assert(err, IsNil)
   859  	c.Check(info.Base, Equals, "none")
   860  }
   861  
   862  func (s *ValidateSuite) TestValidateBaseNoneError(c *C) {
   863  	yamlTemplate := `name: use-base-none
   864  version: 1
   865  base: none
   866  
   867  %APPS_OR_HOOKS%
   868  `
   869  	const apps = `
   870  apps:
   871    useradd:
   872      command: bin/true
   873  `
   874  	const hooks = `
   875  hooks:
   876    configure:
   877  `
   878  
   879  	for _, appsOrHooks := range []string{apps, hooks} {
   880  		yaml := strings.Replace(yamlTemplate, "%APPS_OR_HOOKS%", appsOrHooks, -1)
   881  		strk := NewScopedTracker()
   882  		info, err := InfoFromSnapYamlWithSideInfo([]byte(yaml), nil, strk)
   883  		c.Assert(err, IsNil)
   884  		err = Validate(info)
   885  		c.Assert(err, ErrorMatches, `cannot have apps or hooks with base "none"`)
   886  	}
   887  }
   888  
   889  type testConstraint string
   890  
   891  func (constraint testConstraint) IsOffLimits(path string) bool {
   892  	return true
   893  }
   894  
   895  func (s *ValidateSuite) TestValidateLayout(c *C) {
   896  	si := &Info{SuggestedName: "foo"}
   897  	// Several invalid layouts.
   898  	c.Check(ValidateLayout(&Layout{Snap: si}, nil),
   899  		ErrorMatches, "layout cannot use an empty path")
   900  	c.Check(ValidateLayout(&Layout{Snap: si, Path: "/foo"}, nil),
   901  		ErrorMatches, `layout "/foo" must define a bind mount, a filesystem mount or a symlink`)
   902  	c.Check(ValidateLayout(&Layout{Snap: si, Path: "/foo", Bind: "/bar", Type: "tmpfs"}, nil),
   903  		ErrorMatches, `layout "/foo" must define a bind mount, a filesystem mount or a symlink`)
   904  	c.Check(ValidateLayout(&Layout{Snap: si, Path: "/foo", Bind: "/bar", BindFile: "/froz"}, nil),
   905  		ErrorMatches, `layout "/foo" must define a bind mount, a filesystem mount or a symlink`)
   906  	c.Check(ValidateLayout(&Layout{Snap: si, Path: "/foo", Symlink: "/bar", BindFile: "/froz"}, nil),
   907  		ErrorMatches, `layout "/foo" must define a bind mount, a filesystem mount or a symlink`)
   908  	c.Check(ValidateLayout(&Layout{Snap: si, Path: "/foo", Type: "tmpfs", BindFile: "/froz"}, nil),
   909  		ErrorMatches, `layout "/foo" must define a bind mount, a filesystem mount or a symlink`)
   910  	c.Check(ValidateLayout(&Layout{Snap: si, Path: "/foo", Bind: "/bar", Symlink: "/froz"}, nil),
   911  		ErrorMatches, `layout "/foo" must define a bind mount, a filesystem mount or a symlink`)
   912  	c.Check(ValidateLayout(&Layout{Snap: si, Path: "/foo", Type: "tmpfs", Symlink: "/froz"}, nil),
   913  		ErrorMatches, `layout "/foo" must define a bind mount, a filesystem mount or a symlink`)
   914  	c.Check(ValidateLayout(&Layout{Snap: si, Path: "/foo", Type: "ext4"}, nil),
   915  		ErrorMatches, `layout "/foo" uses invalid filesystem "ext4"`)
   916  	c.Check(ValidateLayout(&Layout{Snap: si, Path: "/foo/bar", Type: "tmpfs", User: "foo"}, nil),
   917  		ErrorMatches, `layout "/foo/bar" uses invalid user "foo"`)
   918  	c.Check(ValidateLayout(&Layout{Snap: si, Path: "/foo/bar", Type: "tmpfs", Group: "foo"}, nil),
   919  		ErrorMatches, `layout "/foo/bar" uses invalid group "foo"`)
   920  	c.Check(ValidateLayout(&Layout{Snap: si, Path: "/foo", Type: "tmpfs", Mode: 02755}, nil),
   921  		ErrorMatches, `layout "/foo" uses invalid mode 02755`)
   922  	c.Check(ValidateLayout(&Layout{Snap: si, Path: "$FOO", Type: "tmpfs"}, nil),
   923  		ErrorMatches, `layout "\$FOO" uses invalid mount point: reference to unknown variable "\$FOO"`)
   924  	c.Check(ValidateLayout(&Layout{Snap: si, Path: "/foo", Bind: "$BAR"}, nil),
   925  		ErrorMatches, `layout "/foo" uses invalid bind mount source "\$BAR": reference to unknown variable "\$BAR"`)
   926  	c.Check(ValidateLayout(&Layout{Snap: si, Path: "$SNAP/evil", Bind: "/etc"}, nil),
   927  		ErrorMatches, `layout "\$SNAP/evil" uses invalid bind mount source "/etc": must start with \$SNAP, \$SNAP_DATA or \$SNAP_COMMON`)
   928  	c.Check(ValidateLayout(&Layout{Snap: si, Path: "/foo", Symlink: "$BAR"}, nil),
   929  		ErrorMatches, `layout "/foo" uses invalid symlink old name "\$BAR": reference to unknown variable "\$BAR"`)
   930  	c.Check(ValidateLayout(&Layout{Snap: si, Path: "$SNAP/evil", Symlink: "/etc"}, nil),
   931  		ErrorMatches, `layout "\$SNAP/evil" uses invalid symlink old name "/etc": must start with \$SNAP, \$SNAP_DATA or \$SNAP_COMMON`)
   932  	c.Check(ValidateLayout(&Layout{Snap: si, Path: "/foo/bar", Bind: "$SNAP/bar/foo"}, []LayoutConstraint{testConstraint("/foo")}),
   933  		ErrorMatches, `layout "/foo/bar" underneath prior layout item "/foo"`)
   934  
   935  	c.Check(ValidateLayout(&Layout{Snap: si, Path: "/dev", Type: "tmpfs"}, nil),
   936  		ErrorMatches, `layout "/dev" in an off-limits area`)
   937  	c.Check(ValidateLayout(&Layout{Snap: si, Path: "/dev/foo", Type: "tmpfs"}, nil),
   938  		ErrorMatches, `layout "/dev/foo" in an off-limits area`)
   939  	c.Check(ValidateLayout(&Layout{Snap: si, Path: "/home", Type: "tmpfs"}, nil),
   940  		ErrorMatches, `layout "/home" in an off-limits area`)
   941  	c.Check(ValidateLayout(&Layout{Snap: si, Path: "/proc", Type: "tmpfs"}, nil),
   942  		ErrorMatches, `layout "/proc" in an off-limits area`)
   943  	c.Check(ValidateLayout(&Layout{Snap: si, Path: "/sys", Type: "tmpfs"}, nil),
   944  		ErrorMatches, `layout "/sys" in an off-limits area`)
   945  	c.Check(ValidateLayout(&Layout{Snap: si, Path: "/run", Type: "tmpfs"}, nil),
   946  		ErrorMatches, `layout "/run" in an off-limits area`)
   947  	c.Check(ValidateLayout(&Layout{Snap: si, Path: "/boot", Type: "tmpfs"}, nil),
   948  		ErrorMatches, `layout "/boot" in an off-limits area`)
   949  	c.Check(ValidateLayout(&Layout{Snap: si, Path: "/lost+found", Type: "tmpfs"}, nil),
   950  		ErrorMatches, `layout "/lost\+found" in an off-limits area`)
   951  	c.Check(ValidateLayout(&Layout{Snap: si, Path: "/media", Type: "tmpfs"}, nil),
   952  		ErrorMatches, `layout "/media" in an off-limits area`)
   953  	c.Check(ValidateLayout(&Layout{Snap: si, Path: "/var/snap", Type: "tmpfs"}, nil),
   954  		ErrorMatches, `layout "/var/snap" in an off-limits area`)
   955  	c.Check(ValidateLayout(&Layout{Snap: si, Path: "/var/lib/snapd", Type: "tmpfs"}, nil),
   956  		ErrorMatches, `layout "/var/lib/snapd" in an off-limits area`)
   957  	c.Check(ValidateLayout(&Layout{Snap: si, Path: "/var/lib/snapd/hostfs", Type: "tmpfs"}, nil),
   958  		ErrorMatches, `layout "/var/lib/snapd/hostfs" in an off-limits area`)
   959  	c.Check(ValidateLayout(&Layout{Snap: si, Path: "/lib/firmware", Type: "tmpfs"}, nil),
   960  		ErrorMatches, `layout "/lib/firmware" in an off-limits area`)
   961  	c.Check(ValidateLayout(&Layout{Snap: si, Path: "/lib/modules", Type: "tmpfs"}, nil),
   962  		ErrorMatches, `layout "/lib/modules" in an off-limits area`)
   963  	c.Check(ValidateLayout(&Layout{Snap: si, Path: "/tmp", Type: "tmpfs"}, nil),
   964  		ErrorMatches, `layout "/tmp" in an off-limits area`)
   965  
   966  	// Several valid layouts.
   967  	c.Check(ValidateLayout(&Layout{Snap: si, Path: "/foo", Type: "tmpfs", Mode: 01755}, nil), IsNil)
   968  	c.Check(ValidateLayout(&Layout{Snap: si, Path: "/usr", Bind: "$SNAP/usr"}, nil), IsNil)
   969  	c.Check(ValidateLayout(&Layout{Snap: si, Path: "/var", Bind: "$SNAP_DATA/var"}, nil), IsNil)
   970  	c.Check(ValidateLayout(&Layout{Snap: si, Path: "/var", Bind: "$SNAP_COMMON/var"}, nil), IsNil)
   971  	c.Check(ValidateLayout(&Layout{Snap: si, Path: "/etc/foo.conf", Symlink: "$SNAP_DATA/etc/foo.conf"}, nil), IsNil)
   972  	c.Check(ValidateLayout(&Layout{Snap: si, Path: "/a/b", Type: "tmpfs", User: "root"}, nil), IsNil)
   973  	c.Check(ValidateLayout(&Layout{Snap: si, Path: "/a/b", Type: "tmpfs", Group: "root"}, nil), IsNil)
   974  	c.Check(ValidateLayout(&Layout{Snap: si, Path: "/a/b", Type: "tmpfs", Mode: 0655}, nil), IsNil)
   975  	c.Check(ValidateLayout(&Layout{Snap: si, Path: "/usr", Symlink: "$SNAP/usr"}, nil), IsNil)
   976  	c.Check(ValidateLayout(&Layout{Snap: si, Path: "/var", Symlink: "$SNAP_DATA/var"}, nil), IsNil)
   977  	c.Check(ValidateLayout(&Layout{Snap: si, Path: "/var", Symlink: "$SNAP_COMMON/var"}, nil), IsNil)
   978  	c.Check(ValidateLayout(&Layout{Snap: si, Path: "$SNAP/data", Symlink: "$SNAP_DATA"}, nil), IsNil)
   979  }
   980  
   981  func (s *ValidateSuite) TestValidateLayoutAll(c *C) {
   982  	// /usr/foo prevents /usr/foo/bar from being valid (tmpfs)
   983  	const yaml1 = `
   984  name: broken-layout-1
   985  layout:
   986    /usr/foo:
   987      type: tmpfs
   988    /usr/foo/bar:
   989      type: tmpfs
   990  `
   991  	const yaml1rev = `
   992  name: broken-layout-1
   993  layout:
   994    /usr/foo/bar:
   995      type: tmpfs
   996    /usr/foo:
   997      type: tmpfs
   998  `
   999  
  1000  	for _, yaml := range []string{yaml1, yaml1rev} {
  1001  		strk := NewScopedTracker()
  1002  		info, err := InfoFromSnapYamlWithSideInfo([]byte(yaml), &SideInfo{Revision: R(42)}, strk)
  1003  		c.Assert(err, IsNil)
  1004  		c.Assert(info.Layout, HasLen, 2)
  1005  		err = ValidateLayoutAll(info)
  1006  		c.Assert(err, ErrorMatches, `layout "/usr/foo/bar" underneath prior layout item "/usr/foo"`)
  1007  	}
  1008  
  1009  	// Same as above but with bind-mounts instead of filesystem mounts.
  1010  	const yaml2 = `
  1011  name: broken-layout-2
  1012  layout:
  1013    /usr/foo:
  1014      bind: $SNAP
  1015    /usr/foo/bar:
  1016      bind: $SNAP
  1017  `
  1018  	const yaml2rev = `
  1019  name: broken-layout-2
  1020  layout:
  1021    /usr/foo/bar:
  1022      bind: $SNAP
  1023    /usr/foo:
  1024      bind: $SNAP
  1025  `
  1026  	for _, yaml := range []string{yaml2, yaml2rev} {
  1027  		strk := NewScopedTracker()
  1028  		info, err := InfoFromSnapYamlWithSideInfo([]byte(yaml), &SideInfo{Revision: R(42)}, strk)
  1029  		c.Assert(err, IsNil)
  1030  		c.Assert(info.Layout, HasLen, 2)
  1031  		err = ValidateLayoutAll(info)
  1032  		c.Assert(err, ErrorMatches, `layout "/usr/foo/bar" underneath prior layout item "/usr/foo"`)
  1033  	}
  1034  
  1035  	// /etc/foo (directory) is not clashing with /etc/foo.conf (file)
  1036  	const yaml3 = `
  1037  name: valid-layout-1
  1038  layout:
  1039    /etc/foo:
  1040      bind: $SNAP_DATA/foo
  1041    /etc/foo.conf:
  1042      symlink: $SNAP_DATA/foo.conf
  1043  `
  1044  	const yaml3rev = `
  1045  name: valid-layout-1
  1046  layout:
  1047    /etc/foo.conf:
  1048      symlink: $SNAP_DATA/foo.conf
  1049    /etc/foo:
  1050      bind: $SNAP_DATA/foo
  1051  `
  1052  	for _, yaml := range []string{yaml3, yaml3rev} {
  1053  		strk := NewScopedTracker()
  1054  		info, err := InfoFromSnapYamlWithSideInfo([]byte(yaml), &SideInfo{Revision: R(42)}, strk)
  1055  		c.Assert(err, IsNil)
  1056  		c.Assert(info.Layout, HasLen, 2)
  1057  		err = ValidateLayoutAll(info)
  1058  		c.Assert(err, IsNil)
  1059  	}
  1060  
  1061  	// /etc/foo file is not clashing with /etc/foobar
  1062  	const yaml4 = `
  1063  name: valid-layout-2
  1064  layout:
  1065    /etc/foo:
  1066      symlink: $SNAP_DATA/foo
  1067    /etc/foobar:
  1068      symlink: $SNAP_DATA/foobar
  1069  `
  1070  	const yaml4rev = `
  1071  name: valid-layout-2
  1072  layout:
  1073    /etc/foobar:
  1074      symlink: $SNAP_DATA/foobar
  1075    /etc/foo:
  1076      symlink: $SNAP_DATA/foo
  1077  `
  1078  	for _, yaml := range []string{yaml4, yaml4rev} {
  1079  		strk := NewScopedTracker()
  1080  		info, err := InfoFromSnapYamlWithSideInfo([]byte(yaml), &SideInfo{Revision: R(42)}, strk)
  1081  		c.Assert(err, IsNil)
  1082  		c.Assert(info.Layout, HasLen, 2)
  1083  		err = ValidateLayoutAll(info)
  1084  		c.Assert(err, IsNil)
  1085  	}
  1086  
  1087  	// /etc/foo file is also clashing with /etc/foo/bar
  1088  	const yaml5 = `
  1089  name: valid-layout-2
  1090  layout:
  1091    /usr/foo:
  1092      symlink: $SNAP_DATA/foo
  1093    /usr/foo/bar:
  1094      bind: $SNAP_DATA/foo/bar
  1095  `
  1096  	const yaml5rev = `
  1097  name: valid-layout-2
  1098  layout:
  1099    /usr/foo/bar:
  1100      bind: $SNAP_DATA/foo/bar
  1101    /usr/foo:
  1102      symlink: $SNAP_DATA/foo
  1103  `
  1104  	for _, yaml := range []string{yaml5, yaml5rev} {
  1105  		strk := NewScopedTracker()
  1106  		info, err := InfoFromSnapYamlWithSideInfo([]byte(yaml), &SideInfo{Revision: R(42)}, strk)
  1107  		c.Assert(err, IsNil)
  1108  		c.Assert(info.Layout, HasLen, 2)
  1109  		err = ValidateLayoutAll(info)
  1110  		c.Assert(err, ErrorMatches, `layout "/usr/foo/bar" underneath prior layout item "/usr/foo"`)
  1111  	}
  1112  
  1113  	const yaml6 = `
  1114  name: tricky-layout-1
  1115  layout:
  1116    /etc/norf:
  1117      bind: $SNAP/etc/norf
  1118    /etc/norf:
  1119      bind-file: $SNAP/etc/norf
  1120  `
  1121  	strk := NewScopedTracker()
  1122  	info, err := InfoFromSnapYamlWithSideInfo([]byte(yaml6), &SideInfo{Revision: R(42)}, strk)
  1123  	c.Assert(err, IsNil)
  1124  	c.Assert(info.Layout, HasLen, 1)
  1125  	err = ValidateLayoutAll(info)
  1126  	c.Assert(err, IsNil)
  1127  	c.Assert(info.Layout["/etc/norf"].Bind, Equals, "")
  1128  	c.Assert(info.Layout["/etc/norf"].BindFile, Equals, "$SNAP/etc/norf")
  1129  
  1130  	// Two layouts refer to the same path as a directory and a file.
  1131  	const yaml7 = `
  1132  name: clashing-source-path-1
  1133  layout:
  1134    /etc/norf:
  1135      bind: $SNAP/etc/norf
  1136    /etc/corge:
  1137      bind-file: $SNAP/etc/norf
  1138  `
  1139  
  1140  	strk = NewScopedTracker()
  1141  	info, err = InfoFromSnapYamlWithSideInfo([]byte(yaml7), &SideInfo{Revision: R(42)}, strk)
  1142  	c.Assert(err, IsNil)
  1143  	c.Assert(info.Layout, HasLen, 2)
  1144  	err = ValidateLayoutAll(info)
  1145  	c.Assert(err, ErrorMatches, `layout "/etc/norf" refers to directory "\$SNAP/etc/norf" but another layout treats it as file`)
  1146  
  1147  	// Two layouts refer to the same path as a directory and a file (other way around).
  1148  	const yaml8 = `
  1149  name: clashing-source-path-2
  1150  layout:
  1151    /etc/norf:
  1152      bind-file: $SNAP/etc/norf
  1153    /etc/corge:
  1154      bind: $SNAP/etc/norf
  1155  `
  1156  	strk = NewScopedTracker()
  1157  	info, err = InfoFromSnapYamlWithSideInfo([]byte(yaml8), &SideInfo{Revision: R(42)}, strk)
  1158  	c.Assert(err, IsNil)
  1159  	c.Assert(info.Layout, HasLen, 2)
  1160  	err = ValidateLayoutAll(info)
  1161  	c.Assert(err, ErrorMatches, `layout "/etc/norf" refers to file "\$SNAP/etc/norf" but another layout treats it as a directory`)
  1162  
  1163  	// Two layouts refer to the same path, but one uses variable and the other doesn't.
  1164  	const yaml9 = `
  1165  name: clashing-source-path-3
  1166  layout:
  1167    /etc/norf:
  1168      bind-file: $SNAP/etc/norf
  1169    /etc/corge:
  1170      bind: /snap/clashing-source-path-3/42/etc/norf
  1171  `
  1172  	strk = NewScopedTracker()
  1173  	info, err = InfoFromSnapYamlWithSideInfo([]byte(yaml9), &SideInfo{Revision: R(42)}, strk)
  1174  	c.Assert(err, IsNil)
  1175  	c.Assert(info.Layout, HasLen, 2)
  1176  	err = ValidateLayoutAll(info)
  1177  	c.Assert(err, ErrorMatches, `layout "/etc/norf" refers to file "\$SNAP/etc/norf" but another layout treats it as a directory`)
  1178  
  1179  	// Same source path referred from a bind mount and symlink doesn't clash.
  1180  	const yaml10 = `
  1181  name: non-clashing-source-1
  1182  layout:
  1183    /etc/norf:
  1184      bind: $SNAP/etc/norf
  1185    /etc/corge:
  1186      symlink: $SNAP/etc/norf
  1187  `
  1188  
  1189  	strk = NewScopedTracker()
  1190  	info, err = InfoFromSnapYamlWithSideInfo([]byte(yaml10), &SideInfo{Revision: R(42)}, strk)
  1191  	c.Assert(err, IsNil)
  1192  	c.Assert(info.Layout, HasLen, 2)
  1193  	err = ValidateLayoutAll(info)
  1194  	c.Assert(err, IsNil)
  1195  
  1196  	// Same source path referred from a file bind mount and symlink doesn't clash.
  1197  	const yaml11 = `
  1198  name: non-clashing-source-1
  1199  layout:
  1200    /etc/norf:
  1201      bind-file: $SNAP/etc/norf
  1202    /etc/corge:
  1203      symlink: $SNAP/etc/norf
  1204  `
  1205  
  1206  	strk = NewScopedTracker()
  1207  	info, err = InfoFromSnapYamlWithSideInfo([]byte(yaml11), &SideInfo{Revision: R(42)}, strk)
  1208  	c.Assert(err, IsNil)
  1209  	c.Assert(info.Layout, HasLen, 2)
  1210  	err = ValidateLayoutAll(info)
  1211  	c.Assert(err, IsNil)
  1212  
  1213  	// Layout replacing files in another snap's mount point
  1214  	const yaml12 = `
  1215  name: this-snap
  1216  layout:
  1217    /snap/that-snap/current/stuff:
  1218      symlink: $SNAP/stuff
  1219  `
  1220  
  1221  	strk = NewScopedTracker()
  1222  	info, err = InfoFromSnapYamlWithSideInfo([]byte(yaml12), &SideInfo{Revision: R(42)}, strk)
  1223  	c.Assert(err, IsNil)
  1224  	c.Assert(info.Layout, HasLen, 1)
  1225  	err = ValidateLayoutAll(info)
  1226  	c.Assert(err, ErrorMatches, `layout "/snap/that-snap/current/stuff" defines a layout in space belonging to another snap`)
  1227  
  1228  	const yaml13 = `
  1229  name: this-snap
  1230  layout:
  1231    $SNAP/relative:
  1232      symlink: $SNAP/existent-dir
  1233  `
  1234  
  1235  	// Layout using $SNAP/... as source
  1236  	strk = NewScopedTracker()
  1237  	info, err = InfoFromSnapYamlWithSideInfo([]byte(yaml13), &SideInfo{Revision: R(42)}, strk)
  1238  	c.Assert(err, IsNil)
  1239  	c.Assert(info.Layout, HasLen, 1)
  1240  	err = ValidateLayoutAll(info)
  1241  	c.Assert(err, IsNil)
  1242  
  1243  	var yaml14Pattern = `
  1244  name: this-snap
  1245  layout:
  1246    %s:
  1247      symlink: $SNAP/existent-dir
  1248  `
  1249  
  1250  	for _, testCase := range []struct {
  1251  		str         string
  1252  		topLevelDir string
  1253  	}{
  1254  		{"/nonexistent-dir", "/nonexistent-dir"},
  1255  		{"/nonexistent-dir/subdir", "/nonexistent-dir"},
  1256  		{"///////unclean-absolute-dir", "/unclean-absolute-dir"},
  1257  	} {
  1258  		// Layout adding a new top-level directory
  1259  		strk = NewScopedTracker()
  1260  		yaml14 := fmt.Sprintf(yaml14Pattern, testCase.str)
  1261  		info, err = InfoFromSnapYamlWithSideInfo([]byte(yaml14), &SideInfo{Revision: R(42)}, strk)
  1262  		c.Assert(err, IsNil)
  1263  		c.Assert(info.Layout, HasLen, 1)
  1264  		err = ValidateLayoutAll(info)
  1265  		c.Assert(err, ErrorMatches, fmt.Sprintf(`layout %q defines a new top-level directory %q`, testCase.str, testCase.topLevelDir))
  1266  	}
  1267  
  1268  }
  1269  
  1270  func (s *YamlSuite) TestValidateAppStartupOrder(c *C) {
  1271  	meta := []byte(`
  1272  name: foo
  1273  version: 1.0
  1274  `)
  1275  	fooAfterBaz := []byte(`
  1276  apps:
  1277    foo:
  1278      after: [baz]
  1279      daemon: simple
  1280    bar:
  1281      daemon: forking
  1282  `)
  1283  	fooBeforeBaz := []byte(`
  1284  apps:
  1285    foo:
  1286      before: [baz]
  1287      daemon: simple
  1288    bar:
  1289      daemon: forking
  1290  `)
  1291  
  1292  	fooNotADaemon := []byte(`
  1293  apps:
  1294    foo:
  1295      after: [bar]
  1296    bar:
  1297      daemon: forking
  1298  `)
  1299  
  1300  	fooBarNotADaemon := []byte(`
  1301  apps:
  1302    foo:
  1303      after: [bar]
  1304      daemon: forking
  1305    bar:
  1306  `)
  1307  	fooSelfCycle := []byte(`
  1308  apps:
  1309    foo:
  1310      after: [foo]
  1311      daemon: forking
  1312    bar:
  1313  `)
  1314  	// cycle between foo and bar
  1315  	badOrder1 := []byte(`
  1316  apps:
  1317   foo:
  1318     after: [bar]
  1319     daemon: forking
  1320   bar:
  1321     after: [foo]
  1322     daemon: forking
  1323  `)
  1324  	// conflicting schedule for baz
  1325  	badOrder2 := []byte(`
  1326  apps:
  1327   foo:
  1328     before: [bar]
  1329     daemon: forking
  1330   bar:
  1331     after: [foo]
  1332     daemon: forking
  1333   baz:
  1334     before: [foo]
  1335     after: [bar]
  1336     daemon: forking
  1337  `)
  1338  	// conflicting schedule for baz
  1339  	badOrder3Cycle := []byte(`
  1340  apps:
  1341   foo:
  1342     before: [bar]
  1343     after: [zed]
  1344     daemon: forking
  1345   bar:
  1346     before: [baz]
  1347     daemon: forking
  1348   baz:
  1349     before: [zed]
  1350     daemon: forking
  1351   zed:
  1352     daemon: forking
  1353  `)
  1354  	goodOrder1 := []byte(`
  1355  apps:
  1356   foo:
  1357     after: [bar, zed]
  1358     daemon: oneshot
  1359   bar:
  1360     before: [foo]
  1361     daemon: dbus
  1362   baz:
  1363     after: [foo]
  1364     daemon: forking
  1365   zed:
  1366     daemon: dbus
  1367  `)
  1368  	goodOrder2 := []byte(`
  1369  apps:
  1370   foo:
  1371     after: [baz]
  1372     daemon: oneshot
  1373   bar:
  1374     before: [baz]
  1375     daemon: dbus
  1376   baz:
  1377     daemon: forking
  1378   zed:
  1379     daemon: dbus
  1380     after: [foo, bar, baz]
  1381  `)
  1382  	mixedSystemUserDaemons := []byte(`
  1383  apps:
  1384   foo:
  1385     daemon: simple
  1386   bar:
  1387     daemon: simple
  1388     daemon-scope: user
  1389     after: [foo]
  1390  `)
  1391  
  1392  	tcs := []struct {
  1393  		name string
  1394  		desc []byte
  1395  		err  string
  1396  	}{{
  1397  		name: "foo after baz",
  1398  		desc: fooAfterBaz,
  1399  		err:  `invalid definition of application "foo": before/after references a missing application "baz"`,
  1400  	}, {
  1401  		name: "foo before baz",
  1402  		desc: fooBeforeBaz,
  1403  		err:  `invalid definition of application "foo": before/after references a missing application "baz"`,
  1404  	}, {
  1405  		name: "foo not a daemon",
  1406  		desc: fooNotADaemon,
  1407  		err:  `invalid definition of application "foo": must be a service to define before/after ordering`,
  1408  	}, {
  1409  		name: "foo wants bar, bar not a daemon",
  1410  		desc: fooBarNotADaemon,
  1411  		err:  `invalid definition of application "foo": before/after references a non-service application "bar"`,
  1412  	}, {
  1413  		name: "bad order 1",
  1414  		desc: badOrder1,
  1415  		err:  `applications are part of a before/after cycle: (foo, bar)|(bar, foo)`,
  1416  	}, {
  1417  		name: "bad order 2",
  1418  		desc: badOrder2,
  1419  		err:  `applications are part of a before/after cycle: ((foo|bar|baz)(, )?){3}`,
  1420  	}, {
  1421  		name: "bad order 3 - cycle",
  1422  		desc: badOrder3Cycle,
  1423  		err:  `applications are part of a before/after cycle: ((foo|bar|baz|zed)(, )?){4}`,
  1424  	}, {
  1425  		name: "all good, 3 apps",
  1426  		desc: goodOrder1,
  1427  	}, {
  1428  		name: "all good, 4 apps",
  1429  		desc: goodOrder2,
  1430  	}, {
  1431  		name: "self cycle",
  1432  		desc: fooSelfCycle,
  1433  		err:  `applications are part of a before/after cycle: foo`,
  1434  	}, {
  1435  		name: "user daemon wants system daemon",
  1436  		desc: mixedSystemUserDaemons,
  1437  		err:  `invalid definition of application "bar": before/after references service with different daemon-scope "foo"`,
  1438  	}}
  1439  	for _, tc := range tcs {
  1440  		c.Logf("trying %q", tc.name)
  1441  		info, err := InfoFromSnapYaml(append(meta, tc.desc...))
  1442  		c.Assert(err, IsNil)
  1443  
  1444  		err = Validate(info)
  1445  		if tc.err != "" {
  1446  			c.Assert(err, ErrorMatches, tc.err)
  1447  		} else {
  1448  			c.Assert(err, IsNil)
  1449  		}
  1450  	}
  1451  }
  1452  
  1453  func (s *ValidateSuite) TestValidateAppWatchdogTimeout(c *C) {
  1454  	s.testValidateAppTimeout(c, "watchdog")
  1455  }
  1456  func (s *ValidateSuite) TestValidateAppStartTimeout(c *C) {
  1457  	s.testValidateAppTimeout(c, "start")
  1458  }
  1459  func (s *ValidateSuite) TestValidateAppStopTimeout(c *C) {
  1460  	s.testValidateAppTimeout(c, "stop")
  1461  }
  1462  
  1463  func (s *ValidateSuite) testValidateAppTimeout(c *C, timeout string) {
  1464  	timeout += "-timeout"
  1465  	meta := []byte(`
  1466  name: foo
  1467  version: 1.0
  1468  `)
  1469  	fooAllGood := []byte(fmt.Sprintf(`
  1470  apps:
  1471    foo:
  1472      daemon: simple
  1473      %s: 12s
  1474  `, timeout))
  1475  	fooNotADaemon := []byte(fmt.Sprintf(`
  1476  apps:
  1477    foo:
  1478      %s: 12s
  1479  `, timeout))
  1480  
  1481  	fooNegative := []byte(fmt.Sprintf(`
  1482  apps:
  1483    foo:
  1484      daemon: simple
  1485      %s: -12s
  1486  `, timeout))
  1487  
  1488  	tcs := []struct {
  1489  		name string
  1490  		desc []byte
  1491  		err  string
  1492  	}{{
  1493  		name: "foo all good",
  1494  		desc: fooAllGood,
  1495  	}, {
  1496  		name: "foo not a service",
  1497  		desc: fooNotADaemon,
  1498  		err:  timeout + ` is only applicable to services`,
  1499  	}, {
  1500  		name: "negative timeout",
  1501  		desc: fooNegative,
  1502  		err:  timeout + ` cannot be negative`,
  1503  	}}
  1504  	for _, tc := range tcs {
  1505  		c.Logf("trying %q", tc.name)
  1506  		info, err := InfoFromSnapYaml(append(meta, tc.desc...))
  1507  		c.Assert(err, IsNil)
  1508  		c.Assert(info, NotNil)
  1509  
  1510  		err = Validate(info)
  1511  		if tc.err != "" {
  1512  			c.Assert(err, ErrorMatches, `invalid definition of application "foo": `+tc.err)
  1513  		} else {
  1514  			c.Assert(err, IsNil)
  1515  		}
  1516  	}
  1517  }
  1518  
  1519  func (s *YamlSuite) TestValidateAppTimer(c *C) {
  1520  	meta := []byte(`
  1521  name: foo
  1522  version: 1.0
  1523  `)
  1524  	allGood := []byte(`
  1525  apps:
  1526    foo:
  1527      daemon: simple
  1528      timer: 10:00-12:00
  1529  `)
  1530  	notAService := []byte(`
  1531  apps:
  1532    foo:
  1533      timer: 10:00-12:00
  1534  `)
  1535  	badTimer := []byte(`
  1536  apps:
  1537    foo:
  1538      daemon: oneshot
  1539      timer: mon,10:00-12:00,mon2-wed3
  1540  `)
  1541  
  1542  	tcs := []struct {
  1543  		name string
  1544  		desc []byte
  1545  		err  string
  1546  	}{{
  1547  		name: "all correct",
  1548  		desc: allGood,
  1549  	}, {
  1550  		name: "not a service",
  1551  		desc: notAService,
  1552  		err:  `timer is only applicable to services`,
  1553  	}, {
  1554  		name: "invalid timer",
  1555  		desc: badTimer,
  1556  		err:  `timer has invalid format: cannot parse "mon2-wed3": invalid schedule fragment`,
  1557  	}}
  1558  	for _, tc := range tcs {
  1559  		c.Logf("trying %q", tc.name)
  1560  		info, err := InfoFromSnapYaml(append(meta, tc.desc...))
  1561  		c.Assert(err, IsNil)
  1562  
  1563  		err = Validate(info)
  1564  		if tc.err != "" {
  1565  			c.Assert(err, ErrorMatches, `invalid definition of application "foo": `+tc.err)
  1566  		} else {
  1567  			c.Assert(err, IsNil)
  1568  		}
  1569  	}
  1570  }
  1571  
  1572  func (s *ValidateSuite) TestValidateOsCannotHaveBase(c *C) {
  1573  	info, err := InfoFromSnapYaml([]byte(`name: foo
  1574  version: 1.0
  1575  type: os
  1576  base: bar
  1577  `))
  1578  	c.Assert(err, IsNil)
  1579  
  1580  	err = Validate(info)
  1581  	c.Check(err, ErrorMatches, `cannot have "base" field on "os" snap "foo"`)
  1582  }
  1583  
  1584  func (s *ValidateSuite) TestValidateOsCanHaveBaseNone(c *C) {
  1585  	info, err := InfoFromSnapYaml([]byte(`name: foo
  1586  version: 1.0
  1587  type: os
  1588  base: none
  1589  `))
  1590  	c.Assert(err, IsNil)
  1591  	c.Assert(Validate(info), IsNil)
  1592  }
  1593  
  1594  func (s *ValidateSuite) TestValidateBaseInorrectSnapName(c *C) {
  1595  	info, err := InfoFromSnapYaml([]byte(`name: foo
  1596  version: 1.0
  1597  base: aAAAA
  1598  `))
  1599  	c.Assert(err, IsNil)
  1600  
  1601  	err = Validate(info)
  1602  	c.Check(err, ErrorMatches, `invalid base name: invalid snap name: \"aAAAA\"`)
  1603  }
  1604  
  1605  func (s *ValidateSuite) TestValidateBaseSnapInstanceNameNotAllowed(c *C) {
  1606  	info, err := InfoFromSnapYaml([]byte(`name: foo
  1607  version: 1.0
  1608  base: foo_abc
  1609  `))
  1610  	c.Assert(err, IsNil)
  1611  
  1612  	err = Validate(info)
  1613  	c.Check(err, ErrorMatches, `base cannot specify a snap instance name: "foo_abc"`)
  1614  }
  1615  
  1616  func (s *ValidateSuite) TestValidateBaseCannotHaveBase(c *C) {
  1617  	info, err := InfoFromSnapYaml([]byte(`name: foo
  1618  version: 1.0
  1619  type: base
  1620  base: bar
  1621  `))
  1622  	c.Assert(err, IsNil)
  1623  
  1624  	err = Validate(info)
  1625  	c.Check(err, ErrorMatches, `cannot have "base" field on "base" snap "foo"`)
  1626  }
  1627  
  1628  func (s *ValidateSuite) TestValidateBaseCanHaveBaseNone(c *C) {
  1629  	info, err := InfoFromSnapYaml([]byte(`name: foo
  1630  version: 1.0
  1631  type: base
  1632  base: none
  1633  `))
  1634  	c.Assert(err, IsNil)
  1635  	c.Assert(Validate(info), IsNil)
  1636  }
  1637  
  1638  func (s *ValidateSuite) TestValidateCommonIDs(c *C) {
  1639  	meta := `
  1640  name: foo
  1641  version: 1.0
  1642  `
  1643  	good := meta + `
  1644  apps:
  1645    foo:
  1646      common-id: org.foo.foo
  1647    bar:
  1648      common-id: org.foo.bar
  1649    baz:
  1650  `
  1651  	bad := meta + `
  1652  apps:
  1653    foo:
  1654      common-id: org.foo.foo
  1655    bar:
  1656      common-id: org.foo.foo
  1657    baz:
  1658  `
  1659  	for i, tc := range []struct {
  1660  		meta string
  1661  		err  string
  1662  	}{
  1663  		{good, ""},
  1664  		{bad, `application ("bar" common-id "org.foo.foo" must be unique, already used by application "foo"|"foo" common-id "org.foo.foo" must be unique, already used by application "bar")`},
  1665  	} {
  1666  		c.Logf("tc #%v", i)
  1667  		info, err := InfoFromSnapYaml([]byte(tc.meta))
  1668  		c.Assert(err, IsNil)
  1669  
  1670  		err = Validate(info)
  1671  		if tc.err == "" {
  1672  			c.Assert(err, IsNil)
  1673  		} else {
  1674  			c.Assert(err, NotNil)
  1675  			c.Check(err, ErrorMatches, tc.err)
  1676  		}
  1677  	}
  1678  }
  1679  
  1680  func (s *validateSuite) TestValidateDescription(c *C) {
  1681  	for _, s := range []string{
  1682  		"xx", // boringest ASCII
  1683  		"🐧🐧", // len("🐧🐧") == 8
  1684  		"á", // á (combining)
  1685  	} {
  1686  		c.Check(ValidateDescription(s), IsNil)
  1687  		c.Check(ValidateDescription(strings.Repeat(s, 2049)), ErrorMatches, `description can have up to 4096 codepoints, got 4098`)
  1688  		c.Check(ValidateDescription(strings.Repeat(s, 2048)), IsNil)
  1689  	}
  1690  }
  1691  
  1692  func (s *validateSuite) TestValidateTitle(c *C) {
  1693  	for _, s := range []string{
  1694  		"xx", // boringest ASCII
  1695  		"🐧🐧", // len("🐧🐧") == 8
  1696  		"á", // á (combining)
  1697  	} {
  1698  		c.Check(ValidateTitle(strings.Repeat(s, 21)), ErrorMatches, `title can have up to 40 codepoints, got 42`)
  1699  		c.Check(ValidateTitle(strings.Repeat(s, 20)), IsNil)
  1700  	}
  1701  }
  1702  
  1703  func (s *validateSuite) TestValidatePlugSlotName(c *C) {
  1704  	validNames := []string{
  1705  		"a", "aa", "aaa", "aaaa",
  1706  		"a-a", "aa-a", "a-aa", "a-b-c",
  1707  		"a0", "a-0", "a-0a",
  1708  	}
  1709  	for _, name := range validNames {
  1710  		c.Assert(ValidatePlugName(name), IsNil)
  1711  		c.Assert(ValidateSlotName(name), IsNil)
  1712  		c.Assert(ValidateInterfaceName(name), IsNil)
  1713  	}
  1714  	invalidNames := []string{
  1715  		// name cannot be empty
  1716  		"",
  1717  		// dashes alone are not a name
  1718  		"-", "--",
  1719  		// double dashes in a name are not allowed
  1720  		"a--a",
  1721  		// name should not end with a dash
  1722  		"a-",
  1723  		// name cannot have any spaces in it
  1724  		"a ", " a", "a a",
  1725  		// a number alone is not a name
  1726  		"0", "123",
  1727  		// identifier must be plain ASCII
  1728  		"日本語", "한글", "ру́сский язы́к",
  1729  	}
  1730  	for _, name := range invalidNames {
  1731  		c.Assert(ValidatePlugName(name), ErrorMatches, `invalid plug name: ".*"`)
  1732  		c.Assert(ValidateSlotName(name), ErrorMatches, `invalid slot name: ".*"`)
  1733  		c.Assert(ValidateInterfaceName(name), ErrorMatches, `invalid interface name: ".*"`)
  1734  	}
  1735  }
  1736  
  1737  func (s *ValidateSuite) TestValidateSnapInstanceNameBadSnapName(c *C) {
  1738  	info, err := InfoFromSnapYaml([]byte(`name: foo_bad
  1739  version: 1.0
  1740  `))
  1741  	c.Assert(err, IsNil)
  1742  
  1743  	err = Validate(info)
  1744  	c.Check(err, ErrorMatches, `invalid snap name: "foo_bad"`)
  1745  }
  1746  
  1747  func (s *ValidateSuite) TestValidateSnapInstanceNameBadInstanceKey(c *C) {
  1748  	info, err := InfoFromSnapYaml([]byte(`name: foo
  1749  version: 1.0
  1750  `))
  1751  	c.Assert(err, IsNil)
  1752  
  1753  	for _, s := range []string{"toolonginstance", "ABCD", "_", "inst@nce", "012345678901"} {
  1754  		info.InstanceKey = s
  1755  		err = Validate(info)
  1756  		c.Check(err, ErrorMatches, fmt.Sprintf(`invalid instance key: %q`, s))
  1757  	}
  1758  }
  1759  
  1760  func (s *ValidateSuite) TestValidateAppRestart(c *C) {
  1761  	meta := []byte(`
  1762  name: foo
  1763  version: 1.0
  1764  `)
  1765  	fooAllGood := []byte(`
  1766  apps:
  1767    foo:
  1768      daemon: simple
  1769      restart-condition: on-abort
  1770      restart-delay: 12s
  1771  `)
  1772  	fooAllGoodDefault := []byte(`
  1773  apps:
  1774    foo:
  1775      daemon: simple
  1776  `)
  1777  	fooAllGoodJustDelay := []byte(`
  1778  apps:
  1779    foo:
  1780      daemon: simple
  1781      restart-delay: 12s
  1782  `)
  1783  	fooConditionNotADaemon := []byte(`
  1784  apps:
  1785    foo:
  1786      restart-condition: on-abort
  1787  `)
  1788  	fooDelayNotADaemon := []byte(`
  1789  apps:
  1790    foo:
  1791      restart-delay: 12s
  1792  `)
  1793  	fooNegativeDelay := []byte(`
  1794  apps:
  1795    foo:
  1796      daemon: simple
  1797      restart-delay: -12s
  1798  `)
  1799  
  1800  	tcs := []struct {
  1801  		name string
  1802  		desc []byte
  1803  		err  string
  1804  	}{{
  1805  		name: "foo all good",
  1806  		desc: fooAllGood,
  1807  	}, {
  1808  		name: "foo all good with default values",
  1809  		desc: fooAllGoodDefault,
  1810  	}, {
  1811  		name: "foo all good with restart-delay only",
  1812  		desc: fooAllGoodJustDelay,
  1813  	}, {
  1814  		name: "foo restart-delay but not a service",
  1815  		desc: fooDelayNotADaemon,
  1816  		err:  `restart-delay is only applicable to services`,
  1817  	}, {
  1818  		name: "foo restart-delay but not a service",
  1819  		desc: fooConditionNotADaemon,
  1820  		err:  `restart-condition is only applicable to services`,
  1821  	}, {
  1822  		name: "negative restart-delay",
  1823  		desc: fooNegativeDelay,
  1824  		err:  `restart-delay cannot be negative`,
  1825  	}}
  1826  	for _, tc := range tcs {
  1827  		c.Logf("trying %q", tc.name)
  1828  		info, err := InfoFromSnapYaml(append(meta, tc.desc...))
  1829  		c.Assert(err, IsNil)
  1830  		c.Assert(info, NotNil)
  1831  
  1832  		err = Validate(info)
  1833  		if tc.err != "" {
  1834  			c.Assert(err, ErrorMatches, `invalid definition of application "foo": `+tc.err)
  1835  		} else {
  1836  			c.Assert(err, IsNil)
  1837  		}
  1838  	}
  1839  }
  1840  
  1841  func (s *ValidateSuite) TestValidateSystemUsernames(c *C) {
  1842  	const yaml1 = `name: binary
  1843  version: 1.0
  1844  system-usernames:
  1845    "b@d": shared
  1846  `
  1847  
  1848  	strk := NewScopedTracker()
  1849  	info, err := InfoFromSnapYamlWithSideInfo([]byte(yaml1), nil, strk)
  1850  	c.Assert(err, IsNil)
  1851  	c.Assert(info.SystemUsernames, HasLen, 1)
  1852  	err = Validate(info)
  1853  	c.Assert(err, ErrorMatches, `invalid system username "b@d"`)
  1854  }
  1855  
  1856  const yamlNeedDf = `name: need-df
  1857  version: 1.0
  1858  plugs:
  1859    gtk-3-themes:
  1860      interface: content
  1861      content: gtk-3-themes
  1862      default-provider: gtk-common-themes
  1863    icon-themes:
  1864      interface: content
  1865      content: icon-themes
  1866      default-provider: gtk-common-themes
  1867  
  1868  `
  1869  
  1870  func (s *ValidateSuite) TestNeededDefaultProviders(c *C) {
  1871  	strk := NewScopedTracker()
  1872  	info, err := InfoFromSnapYamlWithSideInfo([]byte(yamlNeedDf), nil, strk)
  1873  	c.Assert(err, IsNil)
  1874  
  1875  	dps := NeededDefaultProviders(info)
  1876  	c.Check(dps, DeepEquals, map[string][]string{"gtk-common-themes": {"gtk-3-themes", "icon-themes"}})
  1877  }
  1878  
  1879  const yamlNeedDfWithSlot = `name: need-df
  1880  version: 1.0
  1881  plugs:
  1882    gtk-3-themes:
  1883      interface: content
  1884      default-provider: gtk-common-themes2:with-slot
  1885  `
  1886  
  1887  func (s *ValidateSuite) TestNeededDefaultProvidersLegacyColonSyntax(c *C) {
  1888  	strk := NewScopedTracker()
  1889  	info, err := InfoFromSnapYamlWithSideInfo([]byte(yamlNeedDfWithSlot), nil, strk)
  1890  	c.Assert(err, IsNil)
  1891  
  1892  	dps := NeededDefaultProviders(info)
  1893  	c.Check(dps, DeepEquals, map[string][]string{"gtk-common-themes2": {""}})
  1894  }
  1895  
  1896  func (s *validateSuite) TestValidateSnapMissingCore(c *C) {
  1897  	const yaml = `name: some-snap
  1898  version: 1.0`
  1899  
  1900  	strk := NewScopedTracker()
  1901  	info, err := InfoFromSnapYamlWithSideInfo([]byte(yaml), nil, strk)
  1902  	c.Assert(err, IsNil)
  1903  
  1904  	infos := []*Info{info}
  1905  	errors := ValidateBasesAndProviders(infos)
  1906  	c.Assert(errors, HasLen, 1)
  1907  	c.Assert(errors[0], ErrorMatches, `cannot use snap "some-snap": required snap "core" missing`)
  1908  }
  1909  
  1910  func (s *validateSuite) TestValidateSnapMissingBase(c *C) {
  1911  	const yaml = `name: some-snap
  1912  base: some-base
  1913  version: 1.0`
  1914  
  1915  	strk := NewScopedTracker()
  1916  	info, err := InfoFromSnapYamlWithSideInfo([]byte(yaml), nil, strk)
  1917  	c.Assert(err, IsNil)
  1918  
  1919  	infos := []*Info{info}
  1920  	errors := ValidateBasesAndProviders(infos)
  1921  	c.Assert(errors, HasLen, 1)
  1922  	c.Assert(errors[0], ErrorMatches, `cannot use snap "some-snap": base "some-base" is missing`)
  1923  }
  1924  
  1925  func (s *validateSuite) TestValidateSnapMissingDefaultProvider(c *C) {
  1926  	strk := NewScopedTracker()
  1927  	snapInfo, err := InfoFromSnapYamlWithSideInfo([]byte(yamlNeedDf), nil, strk)
  1928  	c.Assert(err, IsNil)
  1929  
  1930  	var coreYaml = `name: core
  1931  version: 1.0
  1932  type: os`
  1933  
  1934  	coreInfo, err := InfoFromSnapYamlWithSideInfo([]byte(coreYaml), nil, strk)
  1935  	c.Assert(err, IsNil)
  1936  
  1937  	infos := []*Info{snapInfo, coreInfo}
  1938  	errors := ValidateBasesAndProviders(infos)
  1939  	c.Assert(errors, HasLen, 1)
  1940  	c.Assert(errors[0], ErrorMatches, `cannot use snap "need-df": default provider "gtk-common-themes" is missing`)
  1941  }
  1942  
  1943  func (s *validateSuite) TestValidateSnapBaseNoneOK(c *C) {
  1944  	const yaml = `name: some-snap
  1945  base: none
  1946  version: 1.0`
  1947  
  1948  	strk := NewScopedTracker()
  1949  	info, err := InfoFromSnapYamlWithSideInfo([]byte(yaml), nil, strk)
  1950  	c.Assert(err, IsNil)
  1951  
  1952  	infos := []*Info{info}
  1953  	errors := ValidateBasesAndProviders(infos)
  1954  	c.Assert(errors, IsNil)
  1955  }
  1956  
  1957  func (s *validateSuite) TestValidateSnapSnapd(c *C) {
  1958  	const yaml = `name: snapd
  1959  type: snapd
  1960  version: 1.0`
  1961  
  1962  	strk := NewScopedTracker()
  1963  	info, err := InfoFromSnapYamlWithSideInfo([]byte(yaml), nil, strk)
  1964  	c.Assert(err, IsNil)
  1965  
  1966  	infos := []*Info{info}
  1967  	errors := ValidateBasesAndProviders(infos)
  1968  	c.Assert(errors, IsNil)
  1969  }
  1970  
  1971  func (s *validateSuite) TestValidateDesktopPrefix(c *C) {
  1972  	// these are extensively tested elsewhere, so just try some common ones
  1973  	for i, tc := range []struct {
  1974  		prefix string
  1975  		exp    bool
  1976  	}{
  1977  		{"good", true},
  1978  		{"also-good", true},
  1979  		{"also-good+instance", true},
  1980  		{"", false},
  1981  		{"+", false},
  1982  		{"@", false},
  1983  		{"+good", false},
  1984  		{"good+", false},
  1985  		{"good+@", false},
  1986  		{"old-style_instance", false},
  1987  		{"bad+bad+bad", false},
  1988  	} {
  1989  		c.Logf("tc #%v", i)
  1990  		res := ValidateDesktopPrefix(tc.prefix)
  1991  		c.Check(res, Equals, tc.exp)
  1992  	}
  1993  }