From cecb06c7ac4d75c4c847ef1d92d04aacb38d681f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20B=C3=B6hmke?= Date: Sun, 1 Dec 2019 21:20:32 +0100 Subject: [PATCH] added update & uboot functions --- resources/scripts/ab_active | 2 +- resources/scripts/ab_flash | 2 +- resources/uboot.c | 2 +- system.go | 2 + update.go | 143 ++++++++++++++++++++++++++++ update_test.go | 179 ++++++++++++++++++++++++++++++++++++ 6 files changed, 327 insertions(+), 3 deletions(-) create mode 100644 update.go create mode 100644 update_test.go diff --git a/resources/scripts/ab_active b/resources/scripts/ab_active index f6c66ec..4350e34 100755 --- a/resources/scripts/ab_active +++ b/resources/scripts/ab_active @@ -1,4 +1,4 @@ - #!/bin/bash +#!/bin/bash set -e # get current partition index diff --git a/resources/scripts/ab_flash b/resources/scripts/ab_flash index def73a3..c44e0c5 100755 --- a/resources/scripts/ab_flash +++ b/resources/scripts/ab_flash @@ -1,4 +1,4 @@ - #!/bin/bash +#!/bin/bash set -e image_file=$1 diff --git a/resources/uboot.c b/resources/uboot.c index d58e8d5..bc00625 100644 --- a/resources/uboot.c +++ b/resources/uboot.c @@ -71,7 +71,7 @@ int main(int argc, char* argv[]) { data[1] = 0; // boot partition - data[2] = 2; // A=2, B=3 + data[2] = 2; // A=1, B=2 } // handle commands diff --git a/system.go b/system.go index ca32547..4b52167 100644 --- a/system.go +++ b/system.go @@ -65,6 +65,7 @@ func SystemDisableSSH() error { return nil } +// SystemShutdown start shutdown of system func SystemShutdown() error { cmd := exec.Command(systemShutdown) err := cmd.Run() @@ -74,6 +75,7 @@ func SystemShutdown() error { return nil } +// SystemReboot start reboot of system func SystemReboot() error { cmd := exec.Command(systemReboot) err := cmd.Run() diff --git a/update.go b/update.go new file mode 100644 index 0000000..fe0316a --- /dev/null +++ b/update.go @@ -0,0 +1,143 @@ +package alpine_builder + +import ( + "compress/gzip" + "encoding/binary" + "errors" + "fmt" + "hash/crc32" + "io" + "io/ioutil" + "log" + "os" + "os/exec" +) + +var ubootFile = "/uboot/uboot.dat" +var ubootRemountRW = "mount -o remount,rw /uboot" +var ubootRemountRO = "mount -o remount,ro /uboot" + +var rootPartitionA = "/dev/mmcblk0p1" +var rootPartitionB = "/dev/mmcblk0p2" + +// 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] = 1 // boot partition A=1, B=2 + + 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(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(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=1, B=2 +func UBootActive() uint8 { + data, _ := loadUbootDat() + return data[2] +} + +// UBootSetActive sets the active partition. A=1, B=2 +func UBootSetActive(active uint8) error { + data, _ := loadUbootDat() + if active == 1 { + data[2] = 1 + } else { + data[2] = 2 + } + return saveUbootDat(data) +} + +// UpdateSystem with the given image +func UpdateSystem(image string) error { + data, _ := loadUbootDat() + rootPart := rootPartitionA + if data[2] == 1 { + 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] == 1 { + return UBootSetActive(2) + } else { + return UBootSetActive(1) + } +} diff --git a/update_test.go b/update_test.go new file mode 100644 index 0000000..19aad20 --- /dev/null +++ b/update_test.go @@ -0,0 +1,179 @@ +package alpine_builder + +import ( + "bytes" + "compress/gzip" + "crypto/rand" + "encoding/binary" + "io" + "io/ioutil" + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +func init() { + ubootFile = "test_uboot" + ubootRemountRW = "true" + ubootRemountRO = "true" + rootPartitionA = "test_rootA" + rootPartitionB = "test_rootB" +} + +func TestLoadUbootDat(t *testing.T) { + ass := assert.New(t) + + // file missing + data, err := loadUbootDat() + ass.EqualError(err, "failed to open file: open test_uboot: no such file or directory") + ass.Equal([]byte{1, 0, 1}, data[:3]) + + // file with invalid data + ass.NoError(ioutil.WriteFile(ubootFile, []byte{}, os.ModePerm)) + + data, err = loadUbootDat() + ass.EqualError(err, "invalid dat file -> fallback to defaults") + ass.Equal([]byte{1, 0, 1}, data[:3]) + + // file with invalid CRC + testData := make([]byte, 1024) + testData[0] = 1 + testData[1] = 2 + testData[2] = 2 + + ass.NoError(ioutil.WriteFile(ubootFile, testData, os.ModePerm)) + data, err = loadUbootDat() + ass.EqualError(err, "invalid crc -> fallback to defaults") + ass.Equal([]byte{1, 0, 1}, data[:3]) + + // file with valid CRC + binary.LittleEndian.PutUint32(testData[1020:], 0x982E8B7A) + + ass.NoError(ioutil.WriteFile(ubootFile, testData, os.ModePerm)) + data, err = loadUbootDat() + ass.NoError(err) + ass.Equal(testData, data) + + _ = os.Remove(ubootFile) +} + +func TestSaveUbootDat(t *testing.T) { + ass := assert.New(t) + + testData := make([]byte, 1024) + testData[0] = 1 + testData[1] = 2 + testData[2] = 2 + ass.NoError(saveUbootDat(testData)) + + binary.LittleEndian.PutUint32(testData[1020:], 0x982E8B7A) + + data, err := ioutil.ReadFile(ubootFile) + ass.NoError(err) + ass.Equal(testData, data) + + _ = os.Remove(ubootFile) +} + +func TestUBootResetCounter(t *testing.T) { + ass := assert.New(t) + + // write test file + testData := make([]byte, 1024) + testData[0] = 1 + testData[1] = 2 + testData[2] = 2 + binary.LittleEndian.PutUint32(testData[1020:], 0x982E8B7A) + ass.NoError(ioutil.WriteFile(ubootFile, testData, os.ModePerm)) + + ass.NoError(UBootResetCounter()) + + data, err := ioutil.ReadFile(ubootFile) + ass.NoError(err) + ass.Zero(data[1]) + + _ = os.Remove(ubootFile) +} + +func TestUBootActive(t *testing.T) { + ass := assert.New(t) + + // write test file + testData := make([]byte, 1024) + testData[0] = 1 + testData[1] = 2 + testData[2] = 2 + binary.LittleEndian.PutUint32(testData[1020:], 0x982E8B7A) + ass.NoError(ioutil.WriteFile(ubootFile, testData, os.ModePerm)) + + ass.Equal(uint8(2), UBootActive()) + + _ = os.Remove(ubootFile) +} + +func TestUBootSetActive(t *testing.T) { + ass := assert.New(t) + + // write test file + testData := make([]byte, 1024) + testData[0] = 1 + testData[1] = 2 + testData[2] = 2 + binary.LittleEndian.PutUint32(testData[1020:], 0x982E8B7A) + ass.NoError(ioutil.WriteFile(ubootFile, testData, os.ModePerm)) + + ass.NoError(UBootSetActive(1)) + + data, err := ioutil.ReadFile(ubootFile) + ass.NoError(err) + ass.Equal(uint8(1), data[2]) + + _ = os.Remove(ubootFile) +} + +func TestUpdateSystem(t *testing.T) { + ass := assert.New(t) + + // test uboot file + testData := make([]byte, 1024) + testData[0] = 1 + testData[1] = 2 + testData[2] = 2 + binary.LittleEndian.PutUint32(testData[1020:], 0x982E8B7A) + ass.NoError(ioutil.WriteFile(ubootFile, testData, os.ModePerm)) + + // generate test image content + size := int64(1024 * 1024 * 5) + testImgData := make([]byte, size) + buffer := bytes.NewBuffer(testImgData) + _, err := io.CopyN(buffer, rand.Reader, size) + ass.NoError(err) + + // write compressed image file + file, err := os.Create("test_image.gz") + ass.NoError(err) + gzipWriter := gzip.NewWriter(file) + _, err = gzipWriter.Write(testImgData) + ass.NoError(err) + ass.NoError(gzipWriter.Close()) + ass.NoError(file.Close()) + + ass.NoError(ioutil.WriteFile(rootPartitionA, nil, os.ModePerm)) + ass.NoError(UpdateSystem("test_image.gz")) + + // check if image was written + data, err := ioutil.ReadFile(rootPartitionA) + ass.NoError(err) + ass.Equal(testImgData, data) + + // check if uboot dat was updated + data, err = ioutil.ReadFile(ubootFile) + ass.NoError(err) + ass.Equal(uint8(1), data[2]) + + _ = os.Remove("test_image.gz") + _ = os.Remove(ubootFile) + _ = os.Remove(rootPartitionA) + _ = os.Remove(rootPartitionB) +}