#!/usr/bin/env python # Copyright (c) 2009 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Script to generate ARM beagleboard SD card image from kernel, root fs This script must be passed a uImage file and a tarred up root filesystem. It also needs EITHER an output device or a file + size. If you use a real device, the entire device will be used. if you specify a file, the file will be truncated to the given length and be formatted as a disk image. To copy a disk image to a device (e.g. /dev/sdb): # dd if=disk_image.img of=/dev/sdb bs=4M """ from optparse import OptionParser import math import os import re import shutil import subprocess import sys def DieWithUsage(exec_path): print 'usage:', exec_path, ' [-f file] [-s filesize] [-d device] ', \ 'path/to/uImage path/to/armel-rootfs.tgz' print 'You must pass either -d or both -f and -s' print 'size may end in k, m, or g for kibibyte, mebibytes, gibibytes.' print 'This will erase all data on the device or in the file passed.' print 'This script must be run as root.' sys.exit(1) def ParseFilesize(size): if size == '': return -1 multiplier = 1 number_part = size[:-1] last_char = size[-1] if (last_char == 'k') or (last_char == 'K'): multiplier = 1024 elif (last_char == 'm') or (last_char == 'M'): multiplier = 1024 * 1024 elif (last_char == 'g') or (last_char == 'G'): multiplier = 1024 * 1024 * 1024 else: number_part = size return long(number_part) * multiplier def ParseArgs(argv): use_file = False file_size = 0 device_path = '' uimage_path = '' rootfs_path = '' parser = OptionParser() parser.add_option('-f', action='store', type='string', dest='filename') parser.add_option('-s', action='store', type='string', dest='filesize') parser.add_option('-d', action='store', type='string', dest='devname') (options, args) = parser.parse_args() # check for valid arg presence if len(args) != 2: DieWithUsage(argv[0]) if (options.filename != None) != (options.filesize != None): DieWithUsage(argv[0]) if not (bool((options.filename != None) and (options.filesize != None)) ^ bool(options.devname != None)): DieWithUsage(argv[0]) # check the device isn't a partition if options.devname != None: if (options.devname[-1] >= '0') and (options.devname[-1] <= '9'): print 'Looks like you specified a partition device, rather than the ' \ 'entire device. try using -d',options.devname[:-1] DieWithUsage(argv[0]) # if size passed, parse size if options.filesize != None: file_size = ParseFilesize(options.filesize) if file_size < 0: DieWithUsage(argv[0]) if options.devname != None: device_path = options.devname if options.filename != None: use_file = True device_path = options.filename uimage_path = args[0] rootfs_path = args[1] # print args if use_file: print "file size:", file_size print "dev path:", device_path print "uimage:", uimage_path print 'rootfs:', rootfs_path return use_file, file_size, device_path, uimage_path, rootfs_path def CreateSparseFile(path, size): fd = os.open(path, os.O_CREAT | os.O_WRONLY | os.O_TRUNC, 0644) if (fd < 0): print 'os.open() failed' exit(1) os.ftruncate(fd, size) os.close(fd) # creates the partion table with the first partition having enough # space for the uimage, the second partition takingn the rest of the space def CreatePartitions(uimage_path, device_path): # get size of first partition in mebibytes statinfo = os.stat(uimage_path) first_part_size = int(math.ceil(statinfo.st_size / (1024.0 * 1024.0)) + 1) System('echo -e ",' + str(first_part_size) \ + ',c,*\\n,,83,-" | sfdisk -uM \'' + device_path + '\'') # uses losetup to set up two loopback devices for the two partitions # returns the two loopback device paths def SetupLoopbackDevices(device_path): sector_size = 512 # bytes # get size of partitons output = subprocess.Popen(['sfdisk', '-d', device_path], stdout=subprocess.PIPE).communicate()[0] m = re.search('start=\\s+(\\d+), size=\\s+(\\d+),.*?start=\\s+(\\d+), size=\\s+(\\d+),', output, re.DOTALL) part1_start = long(m.group(1)) * sector_size part1_size = long(m.group(2)) * sector_size part2_start = long(m.group(3)) * sector_size part2_size = long(m.group(4)) * sector_size if part1_start < 1 or part1_size < 1 or part2_start < 1 or part2_size < 1: print 'failed to read partition table' sys.exit(1) return SetupLoopbackDevice(device_path, part1_start, part1_size), \ SetupLoopbackDevice(device_path, part2_start, part2_size) # returns loopback device path def SetupLoopbackDevice(path, start, size): # get a device device = subprocess.Popen(['losetup', '-f'], stdout=subprocess.PIPE).communicate()[0].rstrip() if device == '': print 'can\'t get device' sys.exit(1) System('losetup -o ' + str(start) + ' --sizelimit ' + str(size) + ' ' + device + ' ' + path) return device def DeleteLoopbackDevice(dev): System('losetup -d ' + dev) def FormatDevices(first, second): System('mkfs.msdos -F 32 ' + first) System('mkfs.ext3 ' + second) # returns mounted paths def MountFilesystems(paths): i = 0 ret = [] for path in paths: i = i + 1 mntpoint = 'mnt' + str(i) System('mkdir ' + mntpoint) System('mount ' + path + ' ' + mntpoint) ret.append(mntpoint) return ret def UnmountFilesystems(mntpoints): for mntpoint in mntpoints: System('umount ' + mntpoint) os.rmdir(mntpoint) def System(cmd): print 'system(' + cmd + ')' p = subprocess.Popen(cmd, shell=True) return os.waitpid(p.pid, 0) def main(argv): (use_file, file_size, device_path, uimage_path, rootfs_path) = ParseArgs(argv) if use_file: CreateSparseFile(device_path, file_size) CreatePartitions(uimage_path, device_path) if use_file: (dev1, dev2) = SetupLoopbackDevices(device_path) else: dev1 = device_path + '1' dev2 = device_path + '2' FormatDevices(dev1, dev2) (mnt1, mnt2) = MountFilesystems([dev1, dev2]) # copy data in shutil.copy(uimage_path, mnt1 + '/uImage') System('tar xzpf ' + rootfs_path + ' -C ' + mnt2) UnmountFilesystems([mnt1, mnt2]) if use_file: DeleteLoopbackDevice(dev1) DeleteLoopbackDevice(dev2) print 'all done!' if use_file: print 'you may want to run dd if=' + device_path + ' of=/some/device bs=4M' if __name__ == '__main__': main(sys.argv)