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 }