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