284 lines
8.6 KiB
Python
Executable File
284 lines
8.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
# Copyright (C) 2020 ArtInChip
|
|
#
|
|
# Creates binary images from ArtInChip partition table for SDCard boot.
|
|
#
|
|
|
|
import os
|
|
import sys
|
|
import argparse
|
|
import subprocess
|
|
|
|
class PartitionEntry:
|
|
def __init__(self):
|
|
self.part = '';
|
|
self.name = '';
|
|
self.offset = 0;
|
|
self.storage = '';
|
|
self.type = '';
|
|
self.file = '';
|
|
self.size = 0;
|
|
|
|
def Trans2PartEntry(line):
|
|
""" Translate line content to PartitionEntry
|
|
Args:
|
|
line: Partition table line content
|
|
"""
|
|
ent = PartitionEntry()
|
|
params = line.split(' ')
|
|
i = len(params)
|
|
|
|
while i > 0:
|
|
if params[i - 1] == '':
|
|
del params[i - 1]
|
|
i -= 1
|
|
if len(params) != 6:
|
|
print('Error, Partition entry parameter is not enough.')
|
|
return None
|
|
ent.part = params[0].upper()
|
|
ent.name = params[1]
|
|
ent.offset = int(params[2], 16)
|
|
ent.storage = params[3].upper()
|
|
ent.type = params[4].upper()
|
|
ent.file = params[5]
|
|
|
|
return ent
|
|
|
|
def ParsePartitionTable(ptable):
|
|
""" Parse partition table, and return PartitionEntry list
|
|
Args:
|
|
ptable: Partition talbe file name
|
|
"""
|
|
plist = []
|
|
with open(ptable, 'r') as f:
|
|
lines = f.readlines()
|
|
for ln in lines:
|
|
ln = ln.expandtabs().strip().replace('\n', '').replace('\r', '')
|
|
if ln.startswith('#') or len(ln) == 0:
|
|
continue
|
|
ent = Trans2PartEntry(ln)
|
|
plist.append(ent)
|
|
return plist
|
|
|
|
def GetSizeWithUnit(siz):
|
|
"""Translate size in appropriate unit:B/K/M/G
|
|
Args:
|
|
siz: integer size value
|
|
Return:
|
|
String of size with unit.
|
|
"""
|
|
K = 1024
|
|
M = 1024 * K
|
|
G = 1024 * M
|
|
ret = ''
|
|
if siz >= G:
|
|
ret = '{:.2f} GB'.format(float(siz)/G)
|
|
elif siz >= M:
|
|
ret = '{:.2f} MB'.format(float(siz)/M)
|
|
elif siz >= K:
|
|
ret = '{:.2f} KB'.format(float(siz)/K)
|
|
else:
|
|
ret = '{} B'.format(siz)
|
|
return ret
|
|
|
|
def GetTotalSize(args):
|
|
"""Args:
|
|
args: arguments Namespace object
|
|
"""
|
|
totalsiz = 0
|
|
if args.size.isdigit():
|
|
totalsiz = int(args.size)
|
|
else:
|
|
strsiz = args.size.upper()
|
|
if strsiz[-1] == 'B':
|
|
totalsiz = int(strsiz[0:-1])
|
|
elif strsiz[-1] == 'K':
|
|
totalsiz = int(strsiz[0:-1]) * 1024
|
|
elif strsiz[-1] == 'M':
|
|
totalsiz = int(strsiz[0:-1]) * 1024 * 1024
|
|
elif strsiz[-1] == 'G':
|
|
totalsiz = int(strsiz[0:-1]) * 1024 * 1024 * 1024
|
|
else:
|
|
print('Error, option --size {} is invalid.'.format(args.size))
|
|
return -1
|
|
return totalsiz
|
|
|
|
def CheckAndSetPartitionSize(total, plist, v=False):
|
|
""" Check Partition size valid or not.
|
|
Args:
|
|
total: Total image size
|
|
plist: Partition list
|
|
v: Verbose
|
|
Return:
|
|
True or False
|
|
"""
|
|
ssiz = 512
|
|
psiz = 0
|
|
pcnt = len(plist)
|
|
for i in range(0, pcnt):
|
|
if (i + 1) != pcnt:
|
|
psiz = plist[i+1].offset - plist[i].offset
|
|
if psiz <= 0:
|
|
print('Size of {} is {} invalid.'.format(plist[i].name, psiz))
|
|
return False
|
|
if (psiz % ssiz) != 0:
|
|
print('Size of {} is {} invalid, should be 512 bytes alignment.'.format(plist[i].name, psiz))
|
|
return False
|
|
plist[i].size = psiz
|
|
if v:
|
|
print('Partition {}, name {}, size {}'.format(i, plist[i].name, psiz))
|
|
else:
|
|
psiz = total - plist[i].offset
|
|
if psiz <= (34 * ssiz):
|
|
# Reserve 34 blocks for GPT header at the end of the image.
|
|
print('Size of {} is {}, invalid.'.format(plist[i].name, psiz))
|
|
return False
|
|
if (psiz % ssiz) != 0:
|
|
print('Size of {} is {} invalid, should be 512 bytes alignment.'.format(plist[i].name, psiz))
|
|
return False
|
|
plist[i].size = psiz - (33 * ssiz)
|
|
if v:
|
|
print('Partition {}, name {}, size {}'.format(i, plist[i].name, psiz))
|
|
return True
|
|
|
|
def ValidatePartitionForSD(plist):
|
|
""" Validate Partition setting for sd card is valid or not.
|
|
Args:
|
|
plist: Partition list
|
|
Return:
|
|
True or False
|
|
"""
|
|
for ent in plist:
|
|
if ent.part != 'GPT':
|
|
return False
|
|
elif ent.storage != 'SDCARD':
|
|
return False
|
|
elif ent.type not in ['RAW', 'FAT32', 'EXT4']:
|
|
return False
|
|
return True
|
|
|
|
def RunCommand(cmd, dumpmsg=False, verbose=False):
|
|
"""Execute command
|
|
Args:
|
|
cmd: cmd line content
|
|
dumpmsg: display message during cmd execute
|
|
Return:
|
|
0 if success, -1 if failure.
|
|
"""
|
|
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=-1)
|
|
ret = p.wait()
|
|
if ret != 0:
|
|
print('Run commmand error:')
|
|
print(' ' + cmd)
|
|
print('stdout: {}\nstderr: {}'.format(p.stdout.read(), p.stderr.read()))
|
|
return -1
|
|
if verbose:
|
|
print(cmd)
|
|
if dumpmsg or verbose:
|
|
print(p.stdout.read())
|
|
if verbose:
|
|
print(p.stderr.read())
|
|
|
|
return 0
|
|
|
|
def GenSdcardImage(args):
|
|
"""Generate Image for SDcard boot
|
|
Args:
|
|
args: arguments Namespace object
|
|
"""
|
|
|
|
# Sector size 512 bytes
|
|
ssiz = 512
|
|
totalsiz = GetTotalSize(args)
|
|
if totalsiz < 0:
|
|
return -1
|
|
if totalsiz % ssiz != 0:
|
|
print('Error, Image size should be 512 bytes alignment')
|
|
return -1
|
|
|
|
plist = ParsePartitionTable(args.table)
|
|
if CheckAndSetPartitionSize(totalsiz, plist, args.verbose) != True:
|
|
print('Partition size validate failed.')
|
|
return -1
|
|
if ValidatePartitionForSD(plist) != True:
|
|
print('Partition validate for SD Card failed.')
|
|
return -1
|
|
|
|
# Create empty image
|
|
print('Creating image file...')
|
|
cmd = 'truncate -s {} {}'.format(totalsiz, args.output)
|
|
ret = RunCommand(cmd, verbose=args.verbose)
|
|
if ret != 0:
|
|
return ret
|
|
|
|
# Make GPT format header
|
|
print('Creating GPT...')
|
|
cmd = 'sgdisk -a 1 -og {}'.format(args.output)
|
|
ret = RunCommand(cmd, verbose=args.verbose)
|
|
if ret != 0:
|
|
return ret
|
|
|
|
part_num = 1
|
|
for ent in plist:
|
|
print(' making partition {} ...'.format(ent.name))
|
|
scnt = ent.size/ssiz
|
|
start_sector = ent.offset/ssiz
|
|
end_sector = start_sector + scnt - 1
|
|
if ent.type == 'FAT32':
|
|
type_code = '0700' # Microsoft basic data
|
|
elif ent.type == 'EXT4':
|
|
type_code = '8300' # Linux filesystem
|
|
else:
|
|
type_code = '8301' # Linux reserved
|
|
# Create new partition
|
|
cmd = 'sgdisk -a 1 -n {}:{}:{} -c {}:{} -t {}:{} {}'.format(part_num,
|
|
start_sector, end_sector, part_num, ent.name, part_num, type_code, args.output)
|
|
ret = RunCommand(cmd, verbose=args.verbose)
|
|
if ret != 0:
|
|
return ret
|
|
part_num += 1
|
|
# Copy binary file to partition, skip this if it is set to none
|
|
dfile = ent.file
|
|
if dfile.upper() == 'NONE':
|
|
continue
|
|
cmd = 'dd if={} of={} bs={} seek={} conv=notrunc'.format(args.datadir + ent.file,
|
|
args.output, ssiz, start_sector)
|
|
ret = RunCommand(cmd, verbose=args.verbose)
|
|
if ret != 0:
|
|
return ret
|
|
|
|
print('Image created.\n')
|
|
cmd = 'sgdisk -p {}'.format(args.output)
|
|
RunCommand(cmd, dumpmsg=True, verbose=args.verbose)
|
|
|
|
return 0
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("-t", "--table", type=str, help="partition table file name")
|
|
parser.add_argument("-s", "--size", type=str,
|
|
help="whole image size, if end withcharacter B/K/M/G,"
|
|
"means the uint is Byte/Kilobytes/Megabytes/Gigabytes, default is B.")
|
|
parser.add_argument("-d", "--datadir", type=str, help="input image data directory")
|
|
parser.add_argument("-o", "--output", type=str, help="output image file name")
|
|
parser.add_argument("-v", "--verbose", action='store_true', help="show detail information")
|
|
args = parser.parse_args()
|
|
if args.size == None:
|
|
print('Error, option --size is required.')
|
|
sys.exit(1)
|
|
if args.table == None:
|
|
print('Error, option --table is required.')
|
|
sys.exit(1)
|
|
if args.output == None:
|
|
print('Error, option --output is required.')
|
|
sys.exit(1)
|
|
# If user not specified data directory, use current directory as default
|
|
if args.datadir == None:
|
|
args.datadir = './'
|
|
if args.datadir.endswith('/') == False:
|
|
args.datadir = args.datadir + '/'
|
|
|
|
GenSdcardImage(args)
|