github.com/rigado/snapd@v2.42.5-go-mod+incompatible/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 crc := readUint32(contentWithHeader) 104 105 payload := contentWithHeader[headerSize:] 106 actualCRC := crc32.ChecksumIEEE(payload) 107 if crc != actualCRC { 108 return nil, fmt.Errorf("cannot open %q: bad CRC %v != %v", fname, crc, actualCRC) 109 } 110 111 if eof := bytes.Index(payload, []byte{0, 0}); eof >= 0 { 112 payload = payload[:eof] 113 } 114 115 data, err := parseData(payload, flags) 116 if err != nil { 117 return nil, err 118 } 119 120 env := &Env{ 121 fname: fname, 122 size: len(contentWithHeader), 123 data: data, 124 } 125 126 return env, nil 127 } 128 129 func parseData(data []byte, flags OpenFlags) (map[string]string, error) { 130 out := make(map[string]string) 131 132 for _, envStr := range bytes.Split(data, []byte{0}) { 133 if len(envStr) == 0 || envStr[0] == 0 || envStr[0] == 255 { 134 continue 135 } 136 l := strings.SplitN(string(envStr), "=", 2) 137 if len(l) != 2 || l[0] == "" { 138 if flags&OpenBestEffort == OpenBestEffort { 139 continue 140 } 141 return nil, fmt.Errorf("cannot parse line %q as key=value pair", envStr) 142 } 143 key := l[0] 144 value := l[1] 145 out[key] = value 146 } 147 148 return out, nil 149 } 150 151 func (env *Env) String() string { 152 out := "" 153 154 env.iterEnv(func(key, value string) { 155 out += fmt.Sprintf("%s=%s\n", key, value) 156 }) 157 158 return out 159 } 160 161 func (env *Env) Size() int { 162 return env.size 163 } 164 165 // Get the value of the environment variable 166 func (env *Env) Get(name string) string { 167 return env.data[name] 168 } 169 170 // Set an environment name to the given value, if the value is empty 171 // the variable will be removed from the environment 172 func (env *Env) Set(name, value string) { 173 if name == "" { 174 panic(fmt.Sprintf("Set() can not be called with empty key for value: %q", value)) 175 } 176 if value == "" { 177 delete(env.data, name) 178 return 179 } 180 env.data[name] = value 181 } 182 183 // iterEnv calls the passed function f with key, value for environment 184 // vars. The order is guaranteed (unlike just iterating over the map) 185 func (env *Env) iterEnv(f func(key, value string)) { 186 keys := make([]string, 0, len(env.data)) 187 for k := range env.data { 188 keys = append(keys, k) 189 } 190 sort.Strings(keys) 191 192 for _, k := range keys { 193 if k == "" { 194 panic("iterEnv iterating over a empty key") 195 } 196 197 f(k, env.data[k]) 198 } 199 } 200 201 // Save will write out the environment data 202 func (env *Env) Save() error { 203 w := bytes.NewBuffer(nil) 204 // will panic if the buffer can't grow, all writes to 205 // the buffer will be ok because we sized it correctly 206 w.Grow(env.size - headerSize) 207 208 // write the payload 209 env.iterEnv(func(key, value string) { 210 w.Write([]byte(fmt.Sprintf("%s=%s", key, value))) 211 w.Write([]byte{0}) 212 }) 213 214 // write double \0 to mark the end of the env 215 w.Write([]byte{0}) 216 217 // no keys, so no previous \0 was written so we write one here 218 if len(env.data) == 0 { 219 w.Write([]byte{0}) 220 } 221 222 // write ff into the remaining parts 223 writtenSoFar := w.Len() 224 for i := 0; i < env.size-headerSize-writtenSoFar; i++ { 225 w.Write([]byte{0xff}) 226 } 227 228 // checksum 229 crc := crc32.ChecksumIEEE(w.Bytes()) 230 231 // ensure dir sync 232 dir, err := os.Open(filepath.Dir(env.fname)) 233 if err != nil { 234 return err 235 } 236 defer dir.Close() 237 238 // Note that we overwrite the existing file and do not do 239 // the usual write-rename. The rationale is that we want to 240 // minimize the amount of writes happening on a potential 241 // FAT partition where the env is loaded from. The file will 242 // always be of a fixed size so we know the writes will not 243 // fail because of ENOSPC. 244 // 245 // The size of the env file never changes so we do not 246 // truncate it. 247 // 248 // We also do not O_TRUNC to avoid reallocations on the FS 249 // to minimize risk of fs corruption. 250 f, err := os.OpenFile(env.fname, os.O_WRONLY, 0666) 251 if err != nil { 252 return err 253 } 254 defer f.Close() 255 256 if _, err := f.Write(writeUint32(crc)); err != nil { 257 return err 258 } 259 // padding bytes (e.g. for redundant header) 260 pad := make([]byte, headerSize-binary.Size(crc)) 261 if _, err := f.Write(pad); err != nil { 262 return err 263 } 264 if _, err := f.Write(w.Bytes()); err != nil { 265 return err 266 } 267 268 if err := f.Sync(); err != nil { 269 return err 270 } 271 272 return dir.Sync() 273 } 274 275 // Import is a helper that imports a given text file that contains 276 // "key=value" paris into the uboot env. Lines starting with ^# are 277 // ignored (like the input file on mkenvimage) 278 func (env *Env) Import(r io.Reader) error { 279 scanner := bufio.NewScanner(r) 280 for scanner.Scan() { 281 line := scanner.Text() 282 if strings.HasPrefix(line, "#") || len(line) == 0 { 283 continue 284 } 285 l := strings.SplitN(line, "=", 2) 286 if len(l) == 1 { 287 return fmt.Errorf("Invalid line: %q", line) 288 } 289 env.data[l[0]] = l[1] 290 291 } 292 293 return scanner.Err() 294 }