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  }