github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/overlord/configstate/configcore/picfg_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2017 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package configcore_test
    21  
    22  import (
    23  	"io/ioutil"
    24  	"os"
    25  	"path/filepath"
    26  	"strings"
    27  
    28  	. "gopkg.in/check.v1"
    29  
    30  	"github.com/snapcore/snapd/boot"
    31  	"github.com/snapcore/snapd/dirs"
    32  	"github.com/snapcore/snapd/logger"
    33  	"github.com/snapcore/snapd/overlord/configstate/configcore"
    34  	"github.com/snapcore/snapd/testutil"
    35  )
    36  
    37  type piCfgSuite struct {
    38  	configcoreSuite
    39  
    40  	mockConfigPath string
    41  }
    42  
    43  var _ = Suite(&piCfgSuite{})
    44  
    45  var mockConfigTxt = `
    46  # For more options and information see
    47  # http://www.raspberrypi.org/documentation/configuration/config-txt.md
    48  #hdmi_group=1
    49  # uncomment this if your display has a black border of unused pixels visible
    50  # and your display can output without overscan
    51  #disable_overscan=1
    52  unrelated_options=are-kept
    53  #gpu_mem_512=true
    54  `
    55  
    56  func (s *piCfgSuite) SetUpTest(c *C) {
    57  	s.configcoreSuite.SetUpTest(c)
    58  	c.Assert(os.MkdirAll(filepath.Join(dirs.GlobalRootDir, "etc"), 0755), IsNil)
    59  
    60  	s.mockConfigPath = filepath.Join(dirs.GlobalRootDir, "/boot/uboot/config.txt")
    61  	err := os.MkdirAll(filepath.Dir(s.mockConfigPath), 0755)
    62  	c.Assert(err, IsNil)
    63  	s.mockConfig(c, mockConfigTxt)
    64  }
    65  
    66  func (s *piCfgSuite) mockConfig(c *C, txt string) {
    67  	err := ioutil.WriteFile(s.mockConfigPath, []byte(txt), 0644)
    68  	c.Assert(err, IsNil)
    69  }
    70  
    71  func (s *piCfgSuite) checkMockConfig(c *C, expected string) {
    72  	c.Check(s.mockConfigPath, testutil.FileEquals, expected)
    73  }
    74  
    75  func (s *piCfgSuite) TestConfigurePiConfigUncommentExisting(c *C) {
    76  	err := configcore.UpdatePiConfig(s.mockConfigPath, map[string]string{"disable_overscan": "1"})
    77  	c.Assert(err, IsNil)
    78  
    79  	expected := strings.Replace(mockConfigTxt, "#disable_overscan=1", "disable_overscan=1", -1)
    80  	s.checkMockConfig(c, expected)
    81  }
    82  
    83  func (s *piCfgSuite) TestConfigurePiConfigCommentExisting(c *C) {
    84  	s.mockConfig(c, mockConfigTxt+"avoid_warnings=1\n")
    85  
    86  	err := configcore.UpdatePiConfig(s.mockConfigPath, map[string]string{"avoid_warnings": ""})
    87  	c.Assert(err, IsNil)
    88  
    89  	expected := mockConfigTxt + "#avoid_warnings=1\n"
    90  	s.checkMockConfig(c, expected)
    91  }
    92  
    93  func (s *piCfgSuite) TestConfigurePiConfigAddNewOption(c *C) {
    94  	err := configcore.UpdatePiConfig(s.mockConfigPath, map[string]string{"framebuffer_depth": "16"})
    95  	c.Assert(err, IsNil)
    96  
    97  	expected := mockConfigTxt + "framebuffer_depth=16\n"
    98  	s.checkMockConfig(c, expected)
    99  
   100  	// add again, verify its not added twice but updated
   101  	err = configcore.UpdatePiConfig(s.mockConfigPath, map[string]string{"framebuffer_depth": "32"})
   102  	c.Assert(err, IsNil)
   103  	expected = mockConfigTxt + "framebuffer_depth=32\n"
   104  	s.checkMockConfig(c, expected)
   105  }
   106  
   107  func (s *piCfgSuite) TestConfigurePiConfigNoChangeUnset(c *C) {
   108  	// ensure we cannot write to the dir to test that we really
   109  	// do not update the file
   110  	err := os.Chmod(filepath.Dir(s.mockConfigPath), 0500)
   111  	c.Assert(err, IsNil)
   112  	defer os.Chmod(filepath.Dir(s.mockConfigPath), 0755)
   113  
   114  	err = configcore.UpdatePiConfig(s.mockConfigPath, map[string]string{"hdmi_group": ""})
   115  	c.Assert(err, IsNil)
   116  }
   117  
   118  func (s *piCfgSuite) TestConfigurePiConfigNoChangeSet(c *C) {
   119  	// ensure we cannot write to the dir to test that we really
   120  	// do not update the file
   121  	err := os.Chmod(filepath.Dir(s.mockConfigPath), 0500)
   122  	c.Assert(err, IsNil)
   123  	defer os.Chmod(filepath.Dir(s.mockConfigPath), 0755)
   124  
   125  	err = configcore.UpdatePiConfig(s.mockConfigPath, map[string]string{"unrelated_options": "cannot-be-set"})
   126  	c.Assert(err, ErrorMatches, `cannot set unsupported configuration value "unrelated_options"`)
   127  }
   128  
   129  func (s *piCfgSuite) TestConfigurePiConfigIntegration(c *C) {
   130  	err := configcore.Run(coreDev, &mockConf{
   131  		state: s.state,
   132  		conf: map[string]interface{}{
   133  			"pi-config.disable-overscan": 1,
   134  		},
   135  	})
   136  	c.Assert(err, IsNil)
   137  
   138  	expected := strings.Replace(mockConfigTxt, "#disable_overscan=1", "disable_overscan=1", -1)
   139  	s.checkMockConfig(c, expected)
   140  
   141  	err = configcore.Run(coreDev, &mockConf{
   142  		state: s.state,
   143  		conf: map[string]interface{}{
   144  			"pi-config.disable-overscan": "",
   145  		},
   146  	})
   147  	c.Assert(err, IsNil)
   148  
   149  	s.checkMockConfig(c, mockConfigTxt)
   150  }
   151  
   152  func (s *piCfgSuite) TestConfigurePiConfigRegression(c *C) {
   153  	err := configcore.Run(coreDev, &mockConf{
   154  		state: s.state,
   155  		conf: map[string]interface{}{
   156  			"pi-config.gpu-mem-512": true,
   157  		},
   158  	})
   159  	c.Assert(err, IsNil)
   160  	expected := strings.Replace(mockConfigTxt, "#gpu_mem_512=true", "gpu_mem_512=true", -1)
   161  	s.checkMockConfig(c, expected)
   162  }
   163  
   164  func (s *piCfgSuite) TestUpdateConfigUC20RunMode(c *C) {
   165  	uc20DevRunMode := mockDev{
   166  		mode: "run",
   167  		uc20: true,
   168  	}
   169  
   170  	// write default config at both the uc18 style runtime location and uc20 run
   171  	// mode location to show that we only modify the uc20 one
   172  	piCfg := filepath.Join(boot.InitramfsUbuntuSeedDir, "config.txt")
   173  	uc18PiCfg := filepath.Join(dirs.GlobalRootDir, "/boot/uboot/config.txt")
   174  
   175  	err := os.MkdirAll(filepath.Dir(piCfg), 0755)
   176  	c.Assert(err, IsNil)
   177  	err = os.MkdirAll(filepath.Dir(uc18PiCfg), 0755)
   178  	c.Assert(err, IsNil)
   179  
   180  	err = ioutil.WriteFile(piCfg, []byte(mockConfigTxt), 0644)
   181  	c.Assert(err, IsNil)
   182  	err = ioutil.WriteFile(uc18PiCfg, []byte(mockConfigTxt), 0644)
   183  	c.Assert(err, IsNil)
   184  
   185  	// apply the config
   186  	err = configcore.Run(uc20DevRunMode, &mockConf{
   187  		state: s.state,
   188  		conf: map[string]interface{}{
   189  			"pi-config.gpu-mem-512": true,
   190  		},
   191  	})
   192  	c.Assert(err, IsNil)
   193  
   194  	// make sure that the original pi config.txt in /boot/uboot/config.txt
   195  	// didn't change
   196  	c.Check(uc18PiCfg, testutil.FileEquals, mockConfigTxt)
   197  
   198  	// but the real one did change*
   199  	expected := strings.Replace(mockConfigTxt, "#gpu_mem_512=true", "gpu_mem_512=true", -1)
   200  	c.Check(piCfg, testutil.FileEquals, expected)
   201  }
   202  
   203  func (s *piCfgSuite) testUpdateConfigUC20NonRunMode(c *C, mode string) {
   204  	uc20DevMode := mockDev{
   205  		mode: mode,
   206  		uc20: true,
   207  	}
   208  
   209  	piCfg := filepath.Join(boot.InitramfsUbuntuSeedDir, "config.txt")
   210  
   211  	err := os.MkdirAll(filepath.Dir(piCfg), 0755)
   212  	c.Assert(err, IsNil)
   213  
   214  	err = ioutil.WriteFile(piCfg, []byte(mockConfigTxt), 0644)
   215  	c.Assert(err, IsNil)
   216  
   217  	// apply the config
   218  	err = configcore.Run(uc20DevMode, &mockConf{
   219  		state: s.state,
   220  		conf: map[string]interface{}{
   221  			"pi-config.gpu-mem-512": true,
   222  		},
   223  	})
   224  	c.Assert(err, IsNil)
   225  
   226  	// the config.txt didn't change at all
   227  	c.Check(piCfg, testutil.FileEquals, mockConfigTxt)
   228  }
   229  
   230  func (s *piCfgSuite) TestUpdateConfigUC20RecoverModeDoesNothing(c *C) {
   231  	s.testUpdateConfigUC20NonRunMode(c, "recover")
   232  }
   233  
   234  func (s *piCfgSuite) TestUpdateConfigUC20InstallModeDoesNothing(c *C) {
   235  	s.testUpdateConfigUC20NonRunMode(c, "install")
   236  }
   237  
   238  func (s *piCfgSuite) TestFilesystemOnlyApply(c *C) {
   239  	conf := configcore.PlainCoreConfig(map[string]interface{}{
   240  		"pi-config.gpu-mem-512": true,
   241  	})
   242  
   243  	tmpDir := c.MkDir()
   244  	c.Assert(os.MkdirAll(filepath.Join(tmpDir, "/boot/uboot"), 0755), IsNil)
   245  
   246  	// write default config
   247  	piCfg := filepath.Join(tmpDir, "/boot/uboot/config.txt")
   248  	c.Assert(ioutil.WriteFile(piCfg, []byte(mockConfigTxt), 0644), IsNil)
   249  
   250  	c.Assert(configcore.FilesystemOnlyApply(coreDev, tmpDir, conf), IsNil)
   251  
   252  	expected := strings.Replace(mockConfigTxt, "#gpu_mem_512=true", "gpu_mem_512=true", -1)
   253  	c.Check(piCfg, testutil.FileEquals, expected)
   254  }
   255  
   256  func (s *piCfgSuite) TestConfigurePiConfigSkippedOnAvnetKernel(c *C) {
   257  	logbuf, r := logger.MockLogger()
   258  	defer r()
   259  
   260  	avnetDev := mockDev{classic: false, kernel: "avnet-avt-iiotg20-kernel"}
   261  
   262  	err := configcore.Run(avnetDev, &mockConf{
   263  		state: s.state,
   264  		conf: map[string]interface{}{
   265  			"pi-config.disable-overscan": 1,
   266  		},
   267  	})
   268  	c.Assert(err, IsNil)
   269  
   270  	c.Check(logbuf.String(), testutil.Contains, "ignoring pi-config settings: configuration cannot be applied: boot measures config.txt")
   271  	// change was ignored
   272  	s.checkMockConfig(c, mockConfigTxt)
   273  }
   274  
   275  func (s *piCfgSuite) TestConfigurePiConfigSkippedOnWrongMode(c *C) {
   276  	logbuf, r := logger.MockLogger()
   277  	defer r()
   278  
   279  	uc20DevInstallMode := mockDev{
   280  		classic: false,
   281  		mode:    "install",
   282  		uc20:    true,
   283  	}
   284  
   285  	err := configcore.Run(uc20DevInstallMode, &mockConf{
   286  		state: s.state,
   287  		conf: map[string]interface{}{
   288  			"pi-config.disable-overscan": 1,
   289  		},
   290  	})
   291  	c.Assert(err, IsNil)
   292  
   293  	c.Check(logbuf.String(), testutil.Contains, "ignoring pi-config settings: configuration cannot be applied: unsupported system mode")
   294  	// change was ignored
   295  	s.checkMockConfig(c, mockConfigTxt)
   296  }
   297  
   298  func (s *piCfgSuite) TestConfigurePiConfigSkippedOnIgnoreHeader(c *C) {
   299  	logbuf, r := logger.MockLogger()
   300  	defer r()
   301  
   302  	tests := []struct {
   303  		header       string
   304  		shouldIgnore bool
   305  	}{
   306  		// ignored
   307  		{"# Snapd-Edit: no", true},
   308  		{"#    Snapd-Edit:     no   ", true},
   309  		{"# snapd-edit: No", true},
   310  		{"# SNAPD-EDIT: NO", true},
   311  		// not ignored
   312  		{"# Snapd-Edit: noAND THEN random words", false},
   313  		{"not first line \n# SNAPD-EDIT: NO", false},
   314  		{"# random things and then SNAPD-EDIT: NO", false},
   315  	}
   316  
   317  	for _, tc := range tests {
   318  		mockConfigWithHeader := tc.header + mockConfigTxt
   319  		s.mockConfig(c, mockConfigWithHeader)
   320  		err := configcore.Run(coreDev, &mockConf{
   321  			state: s.state,
   322  			conf: map[string]interface{}{
   323  				"pi-config.disable-overscan": 1,
   324  			},
   325  		})
   326  		c.Assert(err, IsNil)
   327  
   328  		if tc.shouldIgnore {
   329  			c.Check(logbuf.String(), testutil.Contains, "ignoring pi-config settings: configuration cannot be applied: no-editing header found")
   330  			// change was ignored
   331  			s.checkMockConfig(c, mockConfigWithHeader)
   332  		} else {
   333  			c.Check(logbuf.String(), HasLen, 0)
   334  			expected := strings.Replace(mockConfigWithHeader, "#disable_overscan=1", "disable_overscan=1", -1)
   335  			s.checkMockConfig(c, expected)
   336  		}
   337  
   338  		logbuf.Reset()
   339  	}
   340  }