main.go 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. package main
  2. import (
  3. "flag"
  4. "fmt"
  5. "github.com/diskfs/go-diskfs/disk"
  6. "github.com/diskfs/go-diskfs/filesystem"
  7. "github.com/diskfs/go-diskfs/filesystem/fat32"
  8. "io"
  9. "io/fs"
  10. "os"
  11. "path/filepath"
  12. "strconv"
  13. "strings"
  14. ack "git.swzry.com/zry/go-cli-ack"
  15. diskfs "github.com/diskfs/go-diskfs"
  16. "github.com/diskfs/go-diskfs/partition/mbr"
  17. humanize "github.com/dustin/go-humanize"
  18. "github.com/liushuochen/gotable"
  19. yaml "gopkg.in/yaml.v3"
  20. )
  21. const (
  22. BLOCK_SIZE = 512
  23. UBOOT_OFFSET = 8192
  24. FIRST_PART_OFFSET = 1024 * 1024
  25. LAST_RESERVED = 4 * 1024 * 1024
  26. )
  27. type ConfigDef struct {
  28. UBootImg string `yaml:"u_boot_img"`
  29. BootDir string `yaml:"boot_dir"`
  30. BootFsSize int64 `yaml:"boot_fs_size"`
  31. RootImg string `yaml:"root_img"`
  32. UpperImg string `yaml:"upper_img"`
  33. SwapSize int64 `yaml:"swap_size"`
  34. }
  35. var OutputImg string
  36. var PrintHelp bool
  37. var ConfigFile string
  38. var Cfg ConfigDef
  39. var QuietMode bool
  40. func main() {
  41. flag.BoolVar(&PrintHelp, "h", false, "print this help")
  42. flag.StringVar(&OutputImg, "o", "sdcard.img", "output image file")
  43. flag.StringVar(&ConfigFile, "i", "config.yaml", "input config file")
  44. flag.BoolVar(&QuietMode, "q", false, "quiet mode, which does not ask the user whether to continue")
  45. flag.Parse()
  46. if PrintHelp {
  47. flag.PrintDefaults()
  48. return
  49. }
  50. fcfg, err := os.ReadFile(ConfigFile)
  51. if err != nil {
  52. panic(fmt.Sprint("failed read config file: ", err))
  53. }
  54. err = yaml.Unmarshal(fcfg, &Cfg)
  55. if err != nil {
  56. panic(fmt.Sprint("failed parse yaml config: ", err))
  57. }
  58. if Cfg.SwapSize < 4 {
  59. panic("swap size should not less than 4MB.")
  60. }
  61. if Cfg.BootFsSize < 4 {
  62. panic("swap size should not less than 4MB.")
  63. }
  64. ubootLen, err := CheckSize("u-boot", Cfg.UBootImg)
  65. if err != nil {
  66. panic(err)
  67. }
  68. ubootMaxSize := int64(FIRST_PART_OFFSET - UBOOT_OFFSET)
  69. if ubootLen > ubootMaxSize {
  70. panic(fmt.Sprintf(
  71. "The maximum size of u-boot is %d bytes, but yourimage has %d bytes, which exceeds %d bytes.",
  72. ubootMaxSize, ubootLen, ubootMaxSize-ubootLen,
  73. ))
  74. }
  75. CheckDir(Cfg.BootDir)
  76. rootLen, err := CheckSize("root filesystem", Cfg.RootImg)
  77. if err != nil {
  78. panic(err)
  79. }
  80. upperLen, err := CheckSize("upper filesystem", Cfg.UpperImg)
  81. if err != nil {
  82. panic(err)
  83. }
  84. bootActualSize := Cfg.BootFsSize * 1024 * 1024
  85. swapActualSize := Cfg.SwapSize * 1024 * 1024
  86. p1start := AlignToBlockSize(FIRST_PART_OFFSET)
  87. p1size := AlignToBlockSize(bootActualSize)
  88. p2start := p1start + p1size
  89. p2size := AlignToBlockSize(rootLen)
  90. p3start := p2start + p2size
  91. p3size := AlignToBlockSize(swapActualSize)
  92. p4start := p3start + p3size
  93. p4size := AlignToBlockSize(upperLen)
  94. diskSize := int64(p4start+p4size)*BLOCK_SIZE + LAST_RESERVED
  95. fmt.Println("==== Partition Table Brief ====")
  96. PrintPartitionTableBrief([]PTBrief{
  97. {
  98. StartSector: int(p1start),
  99. SizeSector: int(p1size),
  100. ShowName: "boot",
  101. },
  102. {
  103. StartSector: int(p2start),
  104. SizeSector: int(p2size),
  105. ShowName: "root",
  106. },
  107. {
  108. StartSector: int(p3start),
  109. SizeSector: int(p3size),
  110. ShowName: "swap",
  111. },
  112. {
  113. StartSector: int(p4start),
  114. SizeSector: int(p4size),
  115. ShowName: "data",
  116. },
  117. })
  118. fmt.Println("Total Size: ", humanize.IBytes(uint64(diskSize)))
  119. fmt.Println("================================")
  120. if !QuietMode {
  121. if !ack.Acknowledge("Continue?", true) {
  122. fmt.Println("User canceled.")
  123. return
  124. }
  125. }
  126. if !CheckExists() {
  127. return
  128. }
  129. idisk, err := diskfs.Create(OutputImg, diskSize, diskfs.Raw, diskfs.SectorSizeDefault)
  130. table := &mbr.Table{
  131. Partitions: []*mbr.Partition{
  132. {
  133. Bootable: false,
  134. Type: mbr.Fat32LBA,
  135. Start: p1start,
  136. Size: p1size,
  137. },
  138. {
  139. Bootable: false,
  140. Type: mbr.Linux,
  141. Start: p2start,
  142. Size: p2size,
  143. },
  144. {
  145. Bootable: false,
  146. Type: mbr.LinuxSwap,
  147. Start: p3start,
  148. Size: p3size,
  149. },
  150. {
  151. Bootable: false,
  152. Type: mbr.Linux,
  153. Start: p4start,
  154. Size: p4size,
  155. },
  156. },
  157. LogicalSectorSize: BLOCK_SIZE,
  158. PhysicalSectorSize: BLOCK_SIZE,
  159. }
  160. err = idisk.Partition(table)
  161. if err != nil {
  162. panic(fmt.Sprint("failed create disk partition: ", err))
  163. }
  164. fmt.Println("processing Partition 1: boot...")
  165. CreateFat32Fs(idisk, 1, Cfg.BootDir)
  166. fmt.Println("processing Partition 2: root...")
  167. CopyImgToPartition(idisk, 2, Cfg.RootImg)
  168. fmt.Println("processing Partition 3: swap...")
  169. FillPartition(idisk, 3, int(swapActualSize))
  170. fmt.Println("processing Partition 4: data...")
  171. CopyImgToPartition(idisk, 4, Cfg.UpperImg)
  172. fmt.Println("writing bootloader...")
  173. WriteUboot()
  174. fmt.Println("==== Done ====")
  175. }
  176. func CheckExists() bool {
  177. stat, err := os.Stat(OutputImg)
  178. if err != nil {
  179. return true
  180. }
  181. if !QuietMode {
  182. if !ack.Acknowledge(fmt.Sprintf("'%s' already exists, override?", OutputImg), true) {
  183. fmt.Println("User canceled.")
  184. return false
  185. }
  186. }
  187. if stat.IsDir() {
  188. panic(fmt.Sprintf("'%s' is a existed dir", OutputImg))
  189. }
  190. err = os.Remove(OutputImg)
  191. if err != nil {
  192. panic(fmt.Sprintf("'%s' is a existed file, but can not remove: %v", OutputImg, err))
  193. }
  194. return true
  195. }
  196. func AlignToBlockSize(size int64) uint32 {
  197. remain := size % BLOCK_SIZE
  198. m := size - remain
  199. mr := m / BLOCK_SIZE
  200. if remain > 0 {
  201. mr = mr + 1
  202. }
  203. return uint32(mr)
  204. }
  205. func CheckSize(dname, fname string) (int64, error) {
  206. fstat, err := os.Stat(fname)
  207. if err != nil {
  208. return 0, fmt.Errorf("can not stat %s image '%s': %v", dname, fname, err)
  209. }
  210. l := fstat.Size()
  211. if l <= 0 {
  212. return 0, fmt.Errorf("%s image is empty", dname)
  213. }
  214. return l, nil
  215. }
  216. func CheckDir(dname string) {
  217. fstat, err := os.Stat(dname)
  218. if err != nil {
  219. panic(fmt.Sprintf("can not stat dir '%s': %v", dname, err))
  220. }
  221. if !fstat.IsDir() {
  222. panic(fmt.Sprintf("'%s' is not a dir", dname))
  223. }
  224. }
  225. type PTBrief struct {
  226. ShowName string
  227. StartSector int
  228. SizeSector int
  229. }
  230. func PrintPartitionTableBrief(brief []PTBrief) {
  231. tb, err := gotable.Create("#", "Used For", "Offset(Sectors)", "Offset(Bytes)", "Size(Sectors)", "Size(Bytes)")
  232. if err != nil {
  233. panic(err)
  234. }
  235. for i, v := range brief {
  236. err = tb.AddRow(map[string]string{
  237. "#": strconv.Itoa(i + 1),
  238. "Used For": v.ShowName,
  239. "Offset(Sectors)": strconv.Itoa(v.StartSector),
  240. "Offset(Bytes)": humanize.IBytes(uint64(v.StartSector) * BLOCK_SIZE),
  241. "Size(Sectors)": strconv.Itoa(v.SizeSector),
  242. "Size(Bytes)": humanize.IBytes(uint64(v.SizeSector) * BLOCK_SIZE),
  243. })
  244. if err != nil {
  245. panic(err)
  246. }
  247. }
  248. fmt.Println(tb.String())
  249. }
  250. func CreateFat32Fs(d *disk.Disk, ptn int, from string) {
  251. spec := disk.FilesystemSpec{Partition: ptn, FSType: filesystem.TypeFat32, VolumeLabel: "boot"}
  252. ifs, err := d.CreateFilesystem(spec)
  253. if err != nil {
  254. panic(fmt.Sprintf("can not create filesystem for partition %d: %v", ptn, err))
  255. }
  256. err = filepath.Walk(from, func(path string, info fs.FileInfo, err error) error {
  257. if err != nil {
  258. fmt.Println("[Warn] error in '", path, "': ", err)
  259. return nil
  260. }
  261. relPath, err := filepath.Rel(from, path)
  262. if err != nil {
  263. fmt.Println("[Warn] error in '", path, "': can not get rel path: ", err)
  264. return nil
  265. }
  266. relPath = "/" + strings.TrimPrefix(relPath, ".")
  267. if info.IsDir() {
  268. err = ifs.Mkdir(relPath)
  269. if err != nil {
  270. fmt.Printf("[Warn] error in 'PART%d://%s': can not mkdir: %v\n", ptn, relPath, err)
  271. return nil
  272. }
  273. return nil
  274. } else {
  275. rw, xerr := ifs.OpenFile(relPath, os.O_CREATE|os.O_RDWR)
  276. if xerr != nil {
  277. fmt.Printf("[Warn] error in 'PART%d://%s': can not open file: %v\n", ptn, relPath, xerr)
  278. return nil
  279. }
  280. defer rw.Close()
  281. in, xerr := os.Open(path)
  282. if xerr != nil {
  283. fmt.Println("[Warn] error in '", path, "': can not open file: ", xerr)
  284. return nil
  285. }
  286. defer in.Close()
  287. _, xerr = io.Copy(rw, in)
  288. if xerr != nil {
  289. fmt.Printf("[Warn] io error in copy '%s' to 'PART%d://%s': %v\n", path, ptn, relPath, xerr)
  290. return nil
  291. }
  292. }
  293. return nil
  294. })
  295. if err != nil {
  296. panic(fmt.Sprint("error in copy files to partition 1: ", err))
  297. }
  298. _, ok := ifs.(*fat32.FileSystem)
  299. if !ok {
  300. panic(fmt.Errorf("create filesystem for partition %d failed: finally it is not a fat32 filesystem"))
  301. }
  302. }
  303. func CopyImgToPartition(d *disk.Disk, ptn int, imgfile string) {
  304. in, err := os.Open(imgfile)
  305. if err != nil {
  306. panic(fmt.Sprint("error in open img file '", imgfile, "': ", err))
  307. }
  308. defer in.Close()
  309. n, err := d.WritePartitionContents(ptn, in)
  310. if err != nil {
  311. panic(fmt.Sprintf("error in writing img '%s' to partition %d: %v '", imgfile, ptn, err))
  312. }
  313. fmt.Printf("write %s into partition %d.\n", humanize.IBytes(uint64(n)), ptn)
  314. }
  315. func FillPartition(d *disk.Disk, ptn int, size int) {
  316. in := NewSizableZeroReader(size)
  317. n, err := d.WritePartitionContents(ptn, in)
  318. if err != nil {
  319. panic(fmt.Sprintf("error in filling partition %d: %v '", ptn, err))
  320. }
  321. fmt.Printf("fill %s into partition %d.\n", humanize.IBytes(uint64(n)), ptn)
  322. }
  323. func WriteUboot() {
  324. fUboot, err := os.OpenFile(Cfg.UBootImg, os.O_RDONLY|os.O_EXCL, 0666)
  325. if err != nil {
  326. panic(fmt.Sprintf("error in open u-boot img: %v '", err))
  327. }
  328. defer fUboot.Close()
  329. fDiskImg, err := os.OpenFile(OutputImg, os.O_EXCL|os.O_RDWR, 0666)
  330. if err != nil {
  331. panic(fmt.Sprintf("error in open disk img for writting bootloader: %v '", err))
  332. }
  333. defer fUboot.Close()
  334. n, err := fDiskImg.Seek(UBOOT_OFFSET, 0)
  335. if err != nil {
  336. panic(fmt.Sprintf("error in seeking img for writting bootloader: %v '", err))
  337. }
  338. if n != UBOOT_OFFSET {
  339. panic(fmt.Sprintf("error in seeking img for writting bootloader: current pos %d != UBOOT_OFFSET(%d) '", n, UBOOT_OFFSET))
  340. }
  341. n, err = io.Copy(fDiskImg, fUboot)
  342. if err != nil {
  343. panic(fmt.Sprintf("io error in writting bootloader: %v '", err))
  344. }
  345. fmt.Printf("%d bytes written.\n", n)
  346. }