4
0
Fork 0
raspi-alpine-builder/update.go

145 lines
3.0 KiB
Go

package alpine_builder
import (
"compress/gzip"
"encoding/binary"
"errors"
"fmt"
"hash/crc32"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
)
var mountCommand = "mount"
var ubootFile = "/uboot/uboot.dat"
var ubootRemountRW = []string{"-o", "remount,rw", "/uboot"}
var ubootRemountRO = []string{"-o", "remount,ro", "/uboot"}
var rootPartitionA = "/dev/mmcblk0p2"
var rootPartitionB = "/dev/mmcblk0p3"
// loadUbootDat file to byte array
func loadUbootDat() ([]byte, error) {
defaultData := make([]byte, 1024)
defaultData[0] = 1 // file version
defaultData[1] = 0 // boot counter
defaultData[2] = 2 // boot partition A=2, B=3
data, err := ioutil.ReadFile(ubootFile)
if err != nil {
return defaultData, fmt.Errorf("failed to open file: %w", err)
}
// invalid dat file?
if len(data) < 1024 {
return defaultData, errors.New("invalid dat file -> fallback to defaults")
}
crc := binary.LittleEndian.Uint32(data[1020:])
bla := crc32.ChecksumIEEE(data[:1020])
if crc != bla {
return defaultData, errors.New("invalid crc -> fallback to defaults")
}
return data, nil
}
// saveUbootDat from byte array
func saveUbootDat(data []byte) error {
// update crc
binary.LittleEndian.PutUint32(data[1020:],
crc32.ChecksumIEEE(data[:1020]))
// remount boot partition - writable
cmd := exec.Command(mountCommand, ubootRemountRW...)
err := cmd.Run()
if err != nil {
return fmt.Errorf("failed to remount RW: %w", err)
}
// update uboot dat file
err = ioutil.WriteFile(ubootFile, data[:1024], os.ModePerm)
if err != nil {
return fmt.Errorf("failed write uboot dat: %w", err)
}
// remount boot partition - readonly
cmd = exec.Command(mountCommand, ubootRemountRO...)
err = cmd.Run()
if err != nil {
return fmt.Errorf("failed to remount RO: %w", err)
}
return nil
}
// UBootResetCounter to 0
func UBootResetCounter() error {
data, _ := loadUbootDat()
data[1] = 0
return saveUbootDat(data)
}
// UBootActive returns the active partition. A=2, B=3
func UBootActive() uint8 {
data, _ := loadUbootDat()
return data[2]
}
// UBootSetActive sets the active partition. A=2, B=3
func UBootSetActive(active uint8) error {
data, _ := loadUbootDat()
if active == 2 {
data[2] = 2
} else {
data[2] = 3
}
return saveUbootDat(data)
}
// UpdateSystem with the given image
func UpdateSystem(image string) error {
data, _ := loadUbootDat()
rootPart := rootPartitionA
if data[2] == 2 {
rootPart = rootPartitionB
}
// open image file
inFile, err := os.Open(image)
if err != nil {
log.Fatal(err)
}
defer inFile.Close()
// decompress image
inDecompress, err := gzip.NewReader(inFile)
if err != nil {
log.Fatal(err)
}
defer inDecompress.Close()
// open root partition
out, err := os.OpenFile(rootPart,
os.O_WRONLY|os.O_TRUNC|os.O_SYNC, os.ModePerm)
if err != nil {
log.Fatal(err)
}
defer out.Close()
// write update
_, err = io.Copy(out, inDecompress)
if err != nil {
log.Fatal(err)
}
// switch active partition
if data[2] == 2 {
return UBootSetActive(3)
} else {
return UBootSetActive(2)
}
}