github.com/Lephar/snapd@v0.0.0-20210825215435-c7fba9cef4d2/overlord/configstate/configcore/picfg.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
    21  
    22  import (
    23  	"bufio"
    24  	"fmt"
    25  	"os"
    26  	"path/filepath"
    27  	"regexp"
    28  	"strings"
    29  
    30  	"github.com/snapcore/snapd/boot"
    31  	"github.com/snapcore/snapd/dirs"
    32  	"github.com/snapcore/snapd/logger"
    33  	"github.com/snapcore/snapd/osutil"
    34  	"github.com/snapcore/snapd/overlord/configstate/config"
    35  	"github.com/snapcore/snapd/strutil"
    36  	"github.com/snapcore/snapd/sysconfig"
    37  )
    38  
    39  // valid pi config keys
    40  var piConfigKeys = map[string]bool{
    41  	"disable_overscan":         true,
    42  	"force_turbo":              true,
    43  	"framebuffer_width":        true,
    44  	"framebuffer_height":       true,
    45  	"framebuffer_depth":        true,
    46  	"framebuffer_ignore_alpha": true,
    47  	"overscan_left":            true,
    48  	"overscan_right":           true,
    49  	"overscan_top":             true,
    50  	"overscan_bottom":          true,
    51  	"overscan_scale":           true,
    52  	"display_rotate":           true,
    53  	"hdmi_cvt":                 true,
    54  	"hdmi_group":               true,
    55  	"hdmi_mode":                true,
    56  	"hdmi_timings":             true,
    57  	"hdmi_drive":               true,
    58  	"avoid_warnings":           true,
    59  	"gpu_mem_256":              true,
    60  	"gpu_mem_512":              true,
    61  	"gpu_mem":                  true,
    62  	"sdtv_aspect":              true,
    63  	"config_hdmi_boost":        true,
    64  	"hdmi_force_hotplug":       true,
    65  	"start_x":                  true,
    66  }
    67  
    68  func init() {
    69  	// add supported config keys
    70  	for k := range piConfigKeys {
    71  		s := fmt.Sprintf("core.pi-config.%s", strings.Replace(k, "_", "-", -1))
    72  		supportedConfigurations[s] = true
    73  	}
    74  }
    75  
    76  func updatePiConfig(path string, config map[string]string) error {
    77  	f, err := os.Open(path)
    78  	if err != nil {
    79  		return err
    80  	}
    81  	defer f.Close()
    82  
    83  	toWrite, err := updateKeyValueStream(f, piConfigKeys, config)
    84  	if err != nil {
    85  		return err
    86  	}
    87  
    88  	if toWrite != nil {
    89  		s := strings.Join(toWrite, "\n")
    90  		// ensure we have a final newline in the file
    91  		s += "\n"
    92  		return osutil.AtomicWriteFile(path, []byte(s), 0644, 0)
    93  	}
    94  
    95  	return nil
    96  }
    97  
    98  type piConfigNotSupportedError struct {
    99  	reason string
   100  }
   101  
   102  func newPiConfigNotSupportedError(msg string) *piConfigNotSupportedError {
   103  	return &piConfigNotSupportedError{msg}
   104  }
   105  
   106  func (e *piConfigNotSupportedError) Error() string {
   107  	return fmt.Sprintf("configuration cannot be applied: %s", e.reason)
   108  }
   109  
   110  var reIgnorePrefix = regexp.MustCompile(`(?i)^#\s+Snapd-Edit:\s+no\s*$`)
   111  
   112  func piConfigFileIgnoreMarkerSet(configFile string) bool {
   113  	f, err := os.Open(configFile)
   114  	if err != nil {
   115  		return false
   116  	}
   117  	defer f.Close()
   118  
   119  	scanner := bufio.NewScanner(f)
   120  	// read the first line
   121  	scanner.Scan()
   122  	if scanner.Err() != nil {
   123  		return false
   124  	}
   125  
   126  	return reIgnorePrefix.Match(scanner.Bytes())
   127  }
   128  
   129  // Some of the pi devices (avnet) ship with measured boot enabled and
   130  // the config.txt is part of the measurements. We cannot modify the
   131  // configuration here or measurements are wrong.
   132  var piMeasuredBootKernels = []string{
   133  	// see https://bugs.launchpad.net/denver/+bug/1928613
   134  	"avnet-avt-iiotg20-kernel",
   135  }
   136  
   137  func piConfigFile(dev sysconfig.Device, opts *fsOnlyContext) (string, error) {
   138  	rootDir := dirs.GlobalRootDir
   139  	subdir := "/boot/uboot"
   140  
   141  	if strutil.ListContains(piMeasuredBootKernels, dev.Kernel()) {
   142  		return "", newPiConfigNotSupportedError("boot measures config.txt")
   143  	}
   144  
   145  	if opts != nil {
   146  		rootDir = opts.RootDir
   147  	} else if dev.HasModeenv() {
   148  		// not a filesystem only apply, so we may be operating on a run system
   149  		// on UC20, in which case we shouldn't use the /boot/uboot/ option and
   150  		// instead should use /run/mnt/ubuntu-seed/
   151  		if dev.RunMode() {
   152  			rootDir = boot.InitramfsUbuntuSeedDir
   153  			subdir = ""
   154  		} else {
   155  			// we don't support configuring pi-config in these modes as it is
   156  			// unclear what the right behavior is
   157  			return "", newPiConfigNotSupportedError("unsupported system mode")
   158  		}
   159  	}
   160  	configPath := filepath.Join(rootDir, subdir, "config.txt")
   161  	if piConfigFileIgnoreMarkerSet(configPath) {
   162  		return "", newPiConfigNotSupportedError("no-editing header found")
   163  	}
   164  
   165  	return configPath, nil
   166  }
   167  
   168  func handlePiConfiguration(dev sysconfig.Device, tr config.ConfGetter, opts *fsOnlyContext) error {
   169  	configFile, err := piConfigFile(dev, opts)
   170  	if _, ok := err.(*piConfigNotSupportedError); ok {
   171  		logger.Noticef("ignoring pi-config settings: %v", err)
   172  		return nil
   173  	}
   174  	if err != nil {
   175  		return err
   176  	}
   177  	if osutil.FileExists(configFile) {
   178  		// snapctl can actually give us the whole dict in
   179  		// JSON, in a single call; use that instead of this.
   180  		config := map[string]string{}
   181  		for key := range piConfigKeys {
   182  			output, err := coreCfg(tr, fmt.Sprintf("pi-config.%s", strings.Replace(key, "_", "-", -1)))
   183  			if err != nil {
   184  				return err
   185  			}
   186  			config[key] = output
   187  		}
   188  		if err := updatePiConfig(configFile, config); err != nil {
   189  			return err
   190  		}
   191  	}
   192  	return nil
   193  }