github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/bootloader/ubootenv/env.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 ubootenv 21 22 import ( 23 "bufio" 24 "bytes" 25 "encoding/binary" 26 "fmt" 27 "hash/crc32" 28 "io" 29 "io/ioutil" 30 "os" 31 "path/filepath" 32 "sort" 33 "strings" 34 ) 35 36 // FIXME: add config option for that so that the user can select if 37 // he/she wants env with or without flags 38 var headerSize = 5 39 40 // Env contains the data of the uboot environment 41 type Env struct { 42 fname string 43 size int 44 data map[string]string 45 } 46 47 // little endian helpers 48 func readUint32(data []byte) uint32 { 49 var ret uint32 50 buf := bytes.NewBuffer(data) 51 binary.Read(buf, binary.LittleEndian, &ret) 52 return ret 53 } 54 55 func writeUint32(u uint32) []byte { 56 buf := bytes.NewBuffer(nil) 57 binary.Write(buf, binary.LittleEndian, &u) 58 return buf.Bytes() 59 } 60 61 // Create a new empty uboot env file with the given size 62 func Create(fname string, size int) (*Env, error) { 63 f, err := os.Create(fname) 64 if err != nil { 65 return nil, err 66 } 67 defer f.Close() 68 69 env := &Env{ 70 fname: fname, 71 size: size, 72 data: make(map[string]string), 73 } 74 75 return env, nil 76 } 77 78 // OpenFlags instructs open how to alter its behavior. 79 type OpenFlags int 80 81 const ( 82 // OpenBestEffort instructs OpenWithFlags to skip malformed data without returning an error. 83 OpenBestEffort OpenFlags = 1 << iota 84 ) 85 86 // Open opens a existing uboot env file 87 func Open(fname string) (*Env, error) { 88 return OpenWithFlags(fname, OpenFlags(0)) 89 } 90 91 // OpenWithFlags opens a existing uboot env file, passing additional flags. 92 func OpenWithFlags(fname string, flags OpenFlags) (*Env, error) { 93 f, err := os.Open(fname) 94 if err != nil { 95 return nil, err 96 } 97 defer f.Close() 98 99 contentWithHeader, err := ioutil.ReadAll(f) 100 if err != nil { 101 return nil, err 102 } 103 104 if len(contentWithHeader) < headerSize { 105 return nil, fmt.Errorf("cannot open %q: smaller than expected header", fname) 106 } 107 108 crc := readUint32(contentWithHeader) 109 110 payload := contentWithHeader[headerSize:] 111 actualCRC := crc32.ChecksumIEEE(payload) 112 if crc != actualCRC { 113 return nil, fmt.Errorf("cannot open %q: bad CRC %v != %v", fname, crc, actualCRC) 114 } 115 116 if eof := bytes.Index(payload, []byte{0, 0}); eof >= 0 { 117 payload = payload[:eof] 118 } 119 120 data, err := parseData(payload, flags) 121 if err != nil { 122 return nil, err 123 } 124 125 env := &Env{ 126 fname: fname, 127 size: len(contentWithHeader), 128 data: data, 129 } 130 131 return env, nil 132 } 133 134 func parseData(data []byte, flags OpenFlags) (map[string]string, error) { 135 out := make(map[string]string) 136 137 for _, envStr := range bytes.Split(data, []byte{0}) { 138 if len(envStr) == 0 || envStr[0] == 0 || envStr[0] == 255 { 139 continue 140 } 141 l := strings.SplitN(string(envStr), "=", 2) 142 if len(l) != 2 || l[0] == "" { 143 if flags&OpenBestEffort == OpenBestEffort { 144 continue 145 } 146 return nil, fmt.Errorf("cannot parse line %q as key=value pair", envStr) 147 } 148 key := l[0] 149 value := l[1] 150 out[key] = value 151 } 152 153 return out, nil 154 } 155 156 func (env *Env) String() string { 157 out := "" 158 159 env.iterEnv(func(key, value string) { 160 out += fmt.Sprintf("%s=%s\n", key, value) 161 }) 162 163 return out 164 } 165 166 func (env *Env) Size() int { 167 return env.size 168 } 169 170 // Get the value of the environment variable 171 func (env *Env) Get(name string) string { 172 return env.data[name] 173 } 174 175 // Set an environment name to the given value, if the value is empty 176 // the variable will be removed from the environment 177 func (env *Env) Set(name, value string) { 178 if name == "" { 179 panic(fmt.Sprintf("Set() can not be called with empty key for value: %q", value)) 180 } 181 if value == "" { 182 delete(env.data, name) 183 return 184 } 185 env.data[name] = value 186 } 187 188 // iterEnv calls the passed function f with key, value for environment 189 // vars. The order is guaranteed (unlike just iterating over the map) 190 func (env *Env) iterEnv(f func(key, value string)) { 191 keys := make([]string, 0, len(env.data)) 192 for k := range env.data { 193 keys = append(keys, k) 194 } 195 sort.Strings(keys) 196 197 for _, k := range keys { 198 if k == "" { 199 panic("iterEnv iterating over a empty key") 200 } 201 202 f(k, env.data[k]) 203 } 204 } 205 206 // Save will write out the environment data 207 func (env *Env) Save() error { 208 w := bytes.NewBuffer(nil) 209 // will panic if the buffer can't grow, all writes to 210 // the buffer will be ok because we sized it correctly 211 w.Grow(env.size - headerSize) 212 213 // write the payload 214 env.iterEnv(func(key, value string) { 215 w.Write([]byte(fmt.Sprintf("%s=%s", key, value))) 216 w.Write([]byte{0}) 217 }) 218 219 // write double \0 to mark the end of the env 220 w.Write([]byte{0}) 221 222 // no keys, so no previous \0 was written so we write one here 223 if len(env.data) == 0 { 224 w.Write([]byte{0}) 225 } 226 227 // write ff into the remaining parts 228 writtenSoFar := w.Len() 229 for i := 0; i < env.size-headerSize-writtenSoFar; i++ { 230 w.Write([]byte{0xff}) 231 } 232 233 // checksum 234 crc := crc32.ChecksumIEEE(w.Bytes()) 235 236 // ensure dir sync 237 dir, err := os.Open(filepath.Dir(env.fname)) 238 if err != nil { 239 return err 240 } 241 defer dir.Close() 242 243 // Note that we overwrite the existing file and do not do 244 // the usual write-rename. The rationale is that we want to 245 // minimize the amount of writes happening on a potential 246 // FAT partition where the env is loaded from. The file will 247 // always be of a fixed size so we know the writes will not 248 // fail because of ENOSPC. 249 // 250 // The size of the env file never changes so we do not 251 // truncate it. 252 // 253 // We also do not O_TRUNC to avoid reallocations on the FS 254 // to minimize risk of fs corruption. 255 f, err := os.OpenFile(env.fname, os.O_WRONLY, 0666) 256 if err != nil { 257 return err 258 } 259 defer f.Close() 260 261 if _, err := f.Write(writeUint32(crc)); err != nil { 262 return err 263 } 264 // padding bytes (e.g. for redundant header) 265 pad := make([]byte, headerSize-binary.Size(crc)) 266 if _, err := f.Write(pad); err != nil { 267 return err 268 } 269 if _, err := f.Write(w.Bytes()); err != nil { 270 return err 271 } 272 273 if err := f.Sync(); err != nil { 274 return err 275 } 276 277 return dir.Sync() 278 } 279 280 // Import is a helper that imports a given text file that contains 281 // "key=value" paris into the uboot env. Lines starting with ^# are 282 // ignored (like the input file on mkenvimage) 283 func (env *Env) Import(r io.Reader) error { 284 scanner := bufio.NewScanner(r) 285 for scanner.Scan() { 286 line := scanner.Text() 287 if strings.HasPrefix(line, "#") || len(line) == 0 { 288 continue 289 } 290 l := strings.SplitN(line, "=", 2) 291 if len(l) == 1 { 292 return fmt.Errorf("Invalid line: %q", line) 293 } 294 env.data[l[0]] = l[1] 295 296 } 297 298 return scanner.Err() 299 }