ZRY před 1 rokem
rodič
revize
36a9114a61
6 změnil soubory, kde provedl 470 přidání a 0 odebrání
  1. 8 0
      .idea/.gitignore
  2. 8 0
      config.default.yaml
  3. 21 0
      go.mod
  4. 362 0
      main.go
  5. 35 0
      sizable_zero_reader.go
  6. 36 0
      sizable_zero_reader_test.go

+ 8 - 0
.idea/.gitignore

@@ -0,0 +1,8 @@
+# 默认忽略的文件
+/shelf/
+/workspace.xml
+# 基于编辑器的 HTTP 客户端请求
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml

+ 8 - 0
config.default.yaml

@@ -0,0 +1,8 @@
+u_boot_img: "u-boot-sunxi-with-spl.bin"
+boot_dir: "boot"
+# In megabytes
+boot_fs_size: 128
+root_img: "rootfs.sq.img"
+upper_img: "empty.512M.ext4"
+# In megabytes
+swap_size: 512

+ 21 - 0
go.mod

@@ -0,0 +1,21 @@
+module git.swzry.com/zry/allwinner-sdimg-generator
+
+go 1.19
+
+require (
+	git.swzry.com/zry/go-cli-ack v0.0.0-20210408214519-d7ad6c58c0a8
+	github.com/diskfs/go-diskfs v1.3.0
+	github.com/dustin/go-humanize v1.0.1
+	github.com/liushuochen/gotable v0.0.0-20221119160816-1113793e7092
+	gopkg.in/yaml.v3 v3.0.1
+)
+
+require (
+	github.com/google/uuid v1.1.1 // indirect
+	github.com/pierrec/lz4 v2.3.0+incompatible // indirect
+	github.com/pkg/xattr v0.4.1 // indirect
+	github.com/sirupsen/logrus v1.7.0 // indirect
+	github.com/ulikunitz/xz v0.5.10 // indirect
+	golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect
+	gopkg.in/djherbis/times.v1 v1.2.0 // indirect
+)

+ 362 - 0
main.go

@@ -0,0 +1,362 @@
+package main
+
+import (
+	"flag"
+	"fmt"
+	"github.com/diskfs/go-diskfs/disk"
+	"github.com/diskfs/go-diskfs/filesystem"
+	"github.com/diskfs/go-diskfs/filesystem/fat32"
+	"io"
+	"io/fs"
+	"os"
+	"path/filepath"
+	"strconv"
+	"strings"
+
+	ack "git.swzry.com/zry/go-cli-ack"
+	diskfs "github.com/diskfs/go-diskfs"
+	"github.com/diskfs/go-diskfs/partition/mbr"
+	humanize "github.com/dustin/go-humanize"
+	"github.com/liushuochen/gotable"
+	yaml "gopkg.in/yaml.v3"
+)
+
+const (
+	BLOCK_SIZE        = 512
+	UBOOT_OFFSET      = 8192
+	FIRST_PART_OFFSET = 1024 * 1024
+	LAST_RESERVED     = 4 * 1024 * 1024
+)
+
+type ConfigDef struct {
+	UBootImg   string `yaml:"u_boot_img"`
+	BootDir    string `yaml:"boot_dir"`
+	BootFsSize int64  `yaml:"boot_fs_size"`
+	RootImg    string `yaml:"root_img"`
+	UpperImg   string `yaml:"upper_img"`
+	SwapSize   int64  `yaml:"swap_size"`
+}
+
+var OutputImg string
+var PrintHelp bool
+var ConfigFile string
+var Cfg ConfigDef
+var QuietMode bool
+
+func main() {
+	flag.BoolVar(&PrintHelp, "h", false, "print this help")
+	flag.StringVar(&OutputImg, "o", "sdcard.img", "output image file")
+	flag.StringVar(&ConfigFile, "i", "config.yaml", "input config file")
+	flag.BoolVar(&QuietMode, "q", false, "quiet mode, which does not ask the user whether to continue")
+	flag.Parse()
+	if PrintHelp {
+		flag.PrintDefaults()
+		return
+	}
+	fcfg, err := os.ReadFile(ConfigFile)
+	if err != nil {
+		panic(fmt.Sprint("failed read config file: ", err))
+	}
+	err = yaml.Unmarshal(fcfg, &Cfg)
+	if err != nil {
+		panic(fmt.Sprint("failed parse yaml config: ", err))
+	}
+	if Cfg.SwapSize < 4 {
+		panic("swap size should not less than 4MB.")
+	}
+	if Cfg.BootFsSize < 4 {
+		panic("swap size should not less than 4MB.")
+	}
+	ubootLen, err := CheckSize("u-boot", Cfg.UBootImg)
+	if err != nil {
+		panic(err)
+	}
+	ubootMaxSize := int64(FIRST_PART_OFFSET - UBOOT_OFFSET)
+	if ubootLen > ubootMaxSize {
+		panic(fmt.Sprintf(
+			"The maximum size of u-boot is %d bytes, but yourimage has %d bytes, which exceeds %d bytes.",
+			ubootMaxSize, ubootLen, ubootMaxSize-ubootLen,
+		))
+	}
+	CheckDir(Cfg.BootDir)
+	rootLen, err := CheckSize("root filesystem", Cfg.RootImg)
+	if err != nil {
+		panic(err)
+	}
+	upperLen, err := CheckSize("upper filesystem", Cfg.UpperImg)
+	if err != nil {
+		panic(err)
+	}
+	bootActualSize := Cfg.BootFsSize * 1024 * 1024
+	swapActualSize := Cfg.SwapSize * 1024 * 1024
+	p1start := AlignToBlockSize(FIRST_PART_OFFSET)
+	p1size := AlignToBlockSize(bootActualSize)
+	p2start := p1start + p1size
+	p2size := AlignToBlockSize(rootLen)
+	p3start := p2start + p2size
+	p3size := AlignToBlockSize(swapActualSize)
+	p4start := p3start + p3size
+	p4size := AlignToBlockSize(upperLen)
+	diskSize := int64(p4start+p4size)*BLOCK_SIZE + LAST_RESERVED
+	fmt.Println("==== Partition Table Brief ====")
+	PrintPartitionTableBrief([]PTBrief{
+		{
+			StartSector: int(p1start),
+			SizeSector:  int(p1size),
+			ShowName:    "boot",
+		},
+		{
+			StartSector: int(p2start),
+			SizeSector:  int(p2size),
+			ShowName:    "root",
+		},
+		{
+			StartSector: int(p3start),
+			SizeSector:  int(p3size),
+			ShowName:    "swap",
+		},
+		{
+			StartSector: int(p4start),
+			SizeSector:  int(p4size),
+			ShowName:    "data",
+		},
+	})
+	fmt.Println("Total Size: ", humanize.IBytes(uint64(diskSize)))
+	fmt.Println("================================")
+	if !QuietMode {
+		if !ack.Acknowledge("Continue?", true) {
+			fmt.Println("User canceled.")
+			return
+		}
+	}
+	if !CheckExists() {
+		return
+	}
+	idisk, err := diskfs.Create(OutputImg, diskSize, diskfs.Raw, diskfs.SectorSizeDefault)
+	table := &mbr.Table{
+		Partitions: []*mbr.Partition{
+			{
+				Bootable: false,
+				Type:     mbr.Fat32LBA,
+				Start:    p1start,
+				Size:     p1size,
+			},
+			{
+				Bootable: false,
+				Type:     mbr.Linux,
+				Start:    p2start,
+				Size:     p2size,
+			},
+			{
+				Bootable: false,
+				Type:     mbr.LinuxSwap,
+				Start:    p3start,
+				Size:     p3size,
+			},
+			{
+				Bootable: false,
+				Type:     mbr.Linux,
+				Start:    p4start,
+				Size:     p4size,
+			},
+		},
+		LogicalSectorSize:  BLOCK_SIZE,
+		PhysicalSectorSize: BLOCK_SIZE,
+	}
+	err = idisk.Partition(table)
+	if err != nil {
+		panic(fmt.Sprint("failed create disk partition: ", err))
+	}
+	fmt.Println("processing Partition 1: boot...")
+	CreateFat32Fs(idisk, 1, Cfg.BootDir)
+	fmt.Println("processing Partition 2: root...")
+	CopyImgToPartition(idisk, 2, Cfg.RootImg)
+	fmt.Println("processing Partition 3: swap...")
+	FillPartition(idisk, 3, int(swapActualSize))
+	fmt.Println("processing Partition 4: data...")
+	CopyImgToPartition(idisk, 4, Cfg.UpperImg)
+	fmt.Println("writing bootloader...")
+	WriteUboot()
+	fmt.Println("==== Done ====")
+}
+
+func CheckExists() bool {
+	stat, err := os.Stat(OutputImg)
+	if err != nil {
+		return true
+	}
+	if !QuietMode {
+		if !ack.Acknowledge(fmt.Sprintf("'%s' already exists, override?", OutputImg), true) {
+			fmt.Println("User canceled.")
+			return false
+		}
+	}
+	if stat.IsDir() {
+		panic(fmt.Sprintf("'%s' is a existed dir", OutputImg))
+	}
+	err = os.Remove(OutputImg)
+	if err != nil {
+		panic(fmt.Sprintf("'%s' is a existed file, but can not remove: %v", OutputImg, err))
+	}
+	return true
+}
+
+func AlignToBlockSize(size int64) uint32 {
+	remain := size % BLOCK_SIZE
+	m := size - remain
+	mr := m / BLOCK_SIZE
+	if remain > 0 {
+		mr = mr + 1
+	}
+	return uint32(mr)
+}
+
+func CheckSize(dname, fname string) (int64, error) {
+	fstat, err := os.Stat(fname)
+	if err != nil {
+		return 0, fmt.Errorf("can not stat %s image '%s': %v", dname, fname, err)
+	}
+	l := fstat.Size()
+	if l <= 0 {
+		return 0, fmt.Errorf("%s image is empty", dname)
+	}
+	return l, nil
+}
+
+func CheckDir(dname string) {
+	fstat, err := os.Stat(dname)
+	if err != nil {
+		panic(fmt.Sprintf("can not stat dir '%s': %v", dname, err))
+	}
+	if !fstat.IsDir() {
+		panic(fmt.Sprintf("'%s' is not a dir", dname))
+	}
+}
+
+type PTBrief struct {
+	ShowName    string
+	StartSector int
+	SizeSector  int
+}
+
+func PrintPartitionTableBrief(brief []PTBrief) {
+	tb, err := gotable.Create("#", "Used For", "Offset(Sectors)", "Offset(Bytes)", "Size(Sectors)", "Size(Bytes)")
+	if err != nil {
+		panic(err)
+	}
+	for i, v := range brief {
+		err = tb.AddRow(map[string]string{
+			"#":               strconv.Itoa(i + 1),
+			"Used For":        v.ShowName,
+			"Offset(Sectors)": strconv.Itoa(v.StartSector),
+			"Offset(Bytes)":   humanize.IBytes(uint64(v.StartSector) * BLOCK_SIZE),
+			"Size(Sectors)":   strconv.Itoa(v.SizeSector),
+			"Size(Bytes)":     humanize.IBytes(uint64(v.SizeSector) * BLOCK_SIZE),
+		})
+		if err != nil {
+			panic(err)
+		}
+	}
+	fmt.Println(tb.String())
+}
+
+func CreateFat32Fs(d *disk.Disk, ptn int, from string) {
+	spec := disk.FilesystemSpec{Partition: ptn, FSType: filesystem.TypeFat32, VolumeLabel: "boot"}
+	ifs, err := d.CreateFilesystem(spec)
+	if err != nil {
+		panic(fmt.Sprintf("can not create filesystem for partition %d: %v", ptn, err))
+	}
+	err = filepath.Walk(from, func(path string, info fs.FileInfo, err error) error {
+		if err != nil {
+			fmt.Println("[Warn] error in '", path, "': ", err)
+			return nil
+		}
+		relPath, err := filepath.Rel(from, path)
+		if err != nil {
+			fmt.Println("[Warn] error in '", path, "': can not get rel path: ", err)
+			return nil
+		}
+		relPath = "/" + strings.TrimPrefix(relPath, ".")
+		if info.IsDir() {
+			err = ifs.Mkdir(relPath)
+			if err != nil {
+				fmt.Printf("[Warn] error in 'PART%d://%s': can not mkdir: %v\n", ptn, relPath, err)
+				return nil
+			}
+			return nil
+		} else {
+			rw, xerr := ifs.OpenFile(relPath, os.O_CREATE|os.O_RDWR)
+			if xerr != nil {
+				fmt.Printf("[Warn] error in 'PART%d://%s': can not open file: %v\n", ptn, relPath, xerr)
+				return nil
+			}
+			defer rw.Close()
+			in, xerr := os.Open(path)
+			if xerr != nil {
+				fmt.Println("[Warn] error in '", path, "': can not open file: ", xerr)
+				return nil
+			}
+			defer in.Close()
+			_, xerr = io.Copy(rw, in)
+			if xerr != nil {
+				fmt.Printf("[Warn] io error in copy '%s' to 'PART%d://%s': %v\n", path, ptn, relPath, xerr)
+				return nil
+			}
+		}
+		return nil
+	})
+	if err != nil {
+		panic(fmt.Sprint("error in copy files to partition 1: ", err))
+	}
+	_, ok := ifs.(*fat32.FileSystem)
+	if !ok {
+		panic(fmt.Errorf("create filesystem for partition %d failed: finally it is not a fat32 filesystem"))
+	}
+}
+
+func CopyImgToPartition(d *disk.Disk, ptn int, imgfile string) {
+	in, err := os.Open(imgfile)
+	if err != nil {
+		panic(fmt.Sprint("error in open img file '", imgfile, "': ", err))
+	}
+	defer in.Close()
+	n, err := d.WritePartitionContents(ptn, in)
+	if err != nil {
+		panic(fmt.Sprintf("error in writing img '%s' to partition %d: %v '", imgfile, ptn, err))
+	}
+	fmt.Printf("write %s into partition %d.\n", humanize.IBytes(uint64(n)), ptn)
+}
+
+func FillPartition(d *disk.Disk, ptn int, size int) {
+	in := NewSizableZeroReader(size)
+	n, err := d.WritePartitionContents(ptn, in)
+	if err != nil {
+		panic(fmt.Sprintf("error in filling partition %d: %v '", ptn, err))
+	}
+	fmt.Printf("fill %s into partition %d.\n", humanize.IBytes(uint64(n)), ptn)
+}
+
+func WriteUboot() {
+	fUboot, err := os.OpenFile(Cfg.UBootImg, os.O_RDONLY|os.O_EXCL, 0666)
+	if err != nil {
+		panic(fmt.Sprintf("error in open u-boot img: %v '", err))
+	}
+	defer fUboot.Close()
+	fDiskImg, err := os.OpenFile(OutputImg, os.O_EXCL|os.O_RDWR, 0666)
+	if err != nil {
+		panic(fmt.Sprintf("error in open disk img for writting bootloader: %v '", err))
+	}
+	defer fUboot.Close()
+	n, err := fDiskImg.Seek(UBOOT_OFFSET, 0)
+	if err != nil {
+		panic(fmt.Sprintf("error in seeking img for writting bootloader: %v '", err))
+	}
+	if n != UBOOT_OFFSET {
+		panic(fmt.Sprintf("error in seeking img for writting bootloader: current pos %d != UBOOT_OFFSET(%d) '", n, UBOOT_OFFSET))
+	}
+	n, err = io.Copy(fDiskImg, fUboot)
+	if err != nil {
+		panic(fmt.Sprintf("io error in writting bootloader: %v '", err))
+	}
+	fmt.Printf("%d bytes written.\n", n)
+}

+ 35 - 0
sizable_zero_reader.go

@@ -0,0 +1,35 @@
+package main
+
+import "io"
+
+type SizableZeroReader struct {
+	sz int
+}
+
+func NewSizableZeroReader(sz int) *SizableZeroReader {
+	r := &SizableZeroReader{sz: sz}
+	return r
+}
+
+func (s *SizableZeroReader) Read(p []byte) (n int, err error) {
+	l := len(p)
+	var al int
+	if s.sz > l {
+		al = l
+		s.sz = s.sz - l
+	} else {
+		al = s.sz
+		s.sz = 0
+	}
+	for i := 0; i < al; i++ {
+		p[i] = 0
+	}
+	if al < l {
+		return al, io.EOF
+	}
+	return al, nil
+}
+
+func (s *SizableZeroReader) Close() error {
+	return nil
+}

+ 36 - 0
sizable_zero_reader_test.go

@@ -0,0 +1,36 @@
+package main
+
+import (
+	"io"
+	"testing"
+)
+
+func TestSizableZeroReader(t *testing.T) {
+	t.Log("Testng for SizableZeroReader...")
+	TestingRead(t, 0)
+	TestingRead(t, 1)
+	TestingRead(t, 1024)
+	TestingRead(t, 114514)
+	TestingRead(t, 1919810)
+	t.Log("Test SizableZeroReader OK.")
+}
+
+func TestingRead(t *testing.T, sz int) {
+	rd := NewSizableZeroReader(sz)
+	bin, err := io.ReadAll(rd)
+	if err != nil {
+		t.Errorf("failed test with size %d: %v", sz, err)
+		return
+	}
+	l := len(bin)
+	if l != sz {
+		t.Errorf("length not match: realSize=%d, expectSize=%d", l, sz)
+		return
+	}
+	for _, v := range bin {
+		if v != 0 {
+			t.Errorf("some value not zero")
+			return
+		}
+	}
+}