flatcar-scripts/build_library/cgpt.py
Liam McLoughlin 5b37c5443a Simplify and add flexibility to image creation process
This change adds support for building the disk layout from a
configuration file. It also cleans up much of the image creation
code.

install_gpt no longer exists, and has been replaced by cgpt.py's
write action. This spits out a file that has two functions that can
be called to write a partition layout to a disk/file. This gets rid
of the gigantic nest of calculations that built the layout previously.

All instances of partition/filesystem sizes in build scripts should now
be gone in favour of calls to the cgpt.py tool.

create_boot_desc has moved inside the base image creation, in an effort
to simplify build_image.

load_kernel_test is gone since it's apparently not supposed to be called
here anyway (asked wfrichar/rspangler about this one).

Base image creation now uses files rather than loop devices when
building an image. This means we can simply umount them once we're
done and not worry about cleaning up the loop device, since it's
been done for us.

Hash pad calculation has been removed. This is now set manually inside
the partition config file.

Hybrid MBR creation is gone, since it's now possible to do that in a board
specific hook (see overlay-beaglebone/scripts/board_specific_setup.sh).

OEM partition now has a filesystem, which is mounted at /usr/share/oem
during emerge so that packages can stash files here.

root_fs_dir and friends are still globals, but the long-term idea
is to make this not the case.

BUG=chromium-os:33817
TEST=All types of images and their respective flows
  (VM, recovery, test, factory etc)

Change-Id: I8a596728a4d1845c930e837bea627f5b6a11c098
Reviewed-on: https://gerrit.chromium.org/gerrit/29931
Commit-Ready: Liam McLoughlin <lmcloughlin@chromium.org>
Reviewed-by: Liam McLoughlin <lmcloughlin@chromium.org>
Tested-by: Liam McLoughlin <lmcloughlin@chromium.org>
2012-09-23 10:05:12 -07:00

408 lines
12 KiB
Python
Executable File

#!/usr/bin/python
# Copyright (c) 2012 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.
import copy
import json
import os
import sys
# First sector we can use.
START_SECTOR = 64
class ConfigNotFound(Exception):
pass
class PartitionNotFound(Exception):
pass
class InvalidLayout(Exception):
pass
def LoadPartitionConfig(filename):
"""Loads a partition tables configuration file into a Python object.
Args:
filename: Filename to load into object
Returns:
Object containing disk layout configuration
"""
if not os.path.exists(filename):
raise ConfigNotFound("Partition config %s was not found!" % filename)
with open(filename) as f:
config = json.load(f)
f.close()
metadata = config["metadata"]
metadata["block_size"] = int(metadata["block_size"])
for layout_name, layout in config["layouts"].items():
for part in layout:
part["blocks"] = int(part["blocks"])
part["bytes"] = part["blocks"] * metadata["block_size"]
if "fs_blocks" in part:
part["fs_blocks"] = int(part["fs_blocks"])
part["fs_bytes"] = part["fs_blocks"] * metadata["fs_block_size"]
if part["fs_bytes"] > part["bytes"]:
raise InvalidLayout("Filesystem may not be larger than partition")
return config
def GetTableTotals(config, partitions):
"""Calculates total sizes/counts for a partition table.
Args:
config: Partition configuration file object
partitions: List of partitions to process
Returns:
Dict containing totals data
"""
ret = {
"expand_count": 0,
"expand_min": 0,
"block_count": START_SECTOR * config["metadata"]["block_size"]
}
# Total up the size of all non-expanding partitions to get the minimum
# required disk size.
for partition in partitions:
if "features" in partition and "expand" in partition["features"]:
ret["expand_count"] += 1
ret["expand_min"] += partition["blocks"]
else:
ret["block_count"] += partition["blocks"]
# At present, only one expanding partition is permitted.
# Whilst it'd be possible to have two, we don't need this yet
# and it complicates things, so it's been left out for now.
if ret["expand_count"] > 1:
raise InvalidLayout("1 expand partition allowed, %d requested"
% ret["expand_count"])
ret["min_disk_size"] = ret["block_count"] + ret["expand_min"]
return ret
def GetPartitionTable(config, image_type):
"""Generates requested image_type layout from a layout configuration.
This loads the base table and then overlays the requested layout over
the base layout.
Args:
config: Partition configuration file object
image_type: Type of image eg base/test/dev/factory_install
Returns:
Object representing a selected partition table
"""
partitions = config["layouts"]["base"]
if image_type != "base":
for partition_t in config["layouts"][image_type]:
for partition in partitions:
if partition["type"] == "blank" or partition_t["type"] == "blank":
continue
if partition_t["num"] == partition["num"]:
for k, v in partition_t.items():
partition[k] = v
return partitions
def GetScriptShell():
"""Loads and returns the skeleton script for our output script.
Returns:
A string containg the skeleton script
"""
script_shell_path = os.path.join(os.path.dirname(__file__), "cgpt_shell.sh")
with open(script_shell_path, "r") as f:
script_shell = "".join(f.readlines())
f.close()
# Before we return, insert the path to this tool so somebody reading the
# script later can tell where it was generated.
script_shell = script_shell.replace("@SCRIPT_GENERATOR@", script_shell_path)
return script_shell
def WriteLayoutFunction(sfile, func_name, image_type, config):
"""Writes a shell script function to write out a given partition table.
Args:
sfile: File handle we're writing to
func_name: Function name to write out for specified layout
image_type: Type of image eg base/test/dev/factory_install
config: Partition configuration file object
"""
partitions = GetPartitionTable(config, image_type)
partition_totals = GetTableTotals(config, partitions)
sfile.write("%s() {\ncreate_image $1 %d %s\n" % (
func_name, partition_totals["min_disk_size"],
config["metadata"]["block_size"]))
sfile.write("CURR=%d\n" % START_SECTOR)
sfile.write("$GPT create $1\n")
# Pass 1: Set up the expanding partition size.
for partition in partitions:
if partition["type"] != "blank":
partition["var"] = partition["blocks"]
if partition["num"] == 1:
if "features" in partition and "expand" in partition["features"]:
sfile.write("if [ -b $1 ]; then\n")
sfile.write("STATEFUL_SIZE=$(( $(numsectors $1) - %d))\n" %
partition_totals["block_count"])
sfile.write("else\n")
sfile.write("STATEFUL_SIZE=%s\n" % partition["blocks"])
sfile.write("fi\n")
partition["var"] = "$STATEFUL_SIZE"
# Pass 2: Write out all the cgpt add commands.
for partition in partitions:
if partition["type"] != "blank":
sfile.write("$GPT add -i %d -b $CURR -s %s -t %s -l %s $1 && " % (
partition["num"], str(partition["var"]), partition["type"],
partition["label"]))
# Increment the CURR counter ready for the next partition.
sfile.write("CURR=$(( $CURR + %s ))\n" % partition["blocks"])
# Set default priorities on kernel partitions
sfile.write("$GPT add -i 2 -S 0 -T 15 -P 15 $1\n")
sfile.write("$GPT add -i 4 -S 0 -T 15 -P 0 $1\n")
sfile.write("$GPT add -i 6 -S 0 -T 15 -P 0 $1\n")
sfile.write("$GPT boot -p -b $2 -i 12 $1\n")
sfile.write("$GPT show $1\n")
sfile.write("}\n")
def GetPartitionByNumber(partitions, num):
"""Given a partition table and number returns the partition object.
Args:
partitions: List of partitions to search in
num: Number of partition to find
Returns:
An object for the selected partition
"""
for partition in partitions:
if partition["type"] == "blank":
continue
if partition["num"] == int(num):
return partition
raise PartitionNotFound("Partition not found")
def WritePartitionScript(image_type, layout_filename, sfilename):
"""Writes a shell script with functions for the base and requested layouts.
Args:
image_type: Type of image eg base/test/dev/factory_install
layout_filename: Path to partition configuration file
sfilename: Filename to write the finished script to
"""
config = LoadPartitionConfig(layout_filename)
sfile = open(sfilename, "w")
script_shell = GetScriptShell()
sfile.write(script_shell)
WriteLayoutFunction(sfile, "write_base_table", "base", config)
WriteLayoutFunction(sfile, "write_partition_table", image_type, config)
sfile.close()
def GetBlockSize(layout_filename):
"""Returns the partition table block size.
Args:
layout_filename: Path to partition configuration file
Returns:
Block size of all partitions in the layout
"""
config = LoadPartitionConfig(layout_filename)
return config["metadata"]["block_size"]
def GetFilesystemBlockSize(layout_filename):
"""Returns the filesystem block size.
This is used for all partitions in the table that have filesystems.
Args:
layout_filename: Path to partition configuration file
Returns:
Block size of all filesystems in the layout
"""
config = LoadPartitionConfig(layout_filename)
return config["metadata"]["fs_block_size"]
def GetPartitionSize(image_type, layout_filename, num):
"""Returns the partition size of a given partition for a given layout type.
Args:
image_type: Type of image eg base/test/dev/factory_install
layout_filename: Path to partition configuration file
num: Number of the partition you want to read from
Returns:
Size of selected partition in bytes
"""
config = LoadPartitionConfig(layout_filename)
partitions = GetPartitionTable(config, image_type)
partition = GetPartitionByNumber(partitions, num)
return partition["bytes"]
def GetFilesystemSize(image_type, layout_filename, num):
"""Returns the filesystem size of a given partition for a given layout type.
If no filesystem size is specified, returns the partition size.
Args:
image_type: Type of image eg base/test/dev/factory_install
layout_filename: Path to partition configuration file
num: Number of the partition you want to read from
Returns:
Size of selected partition filesystem in bytes
"""
config = LoadPartitionConfig(layout_filename)
partitions = GetPartitionTable(config, image_type)
partition = GetPartitionByNumber(partitions, num)
if "fs_bytes" in partition:
return partition["fs_bytes"]
else:
return partition["bytes"]
def GetLabel(image_type, layout_filename, num):
"""Returns the label for a given partition.
Args:
image_type: Type of image eg base/test/dev/factory_install
layout_filename: Path to partition configuration file
num: Number of the partition you want to read from
Returns:
Label of selected partition, or "UNTITLED" if none specified
"""
config = LoadPartitionConfig(layout_filename)
partitions = GetPartitionTable(config, image_type)
partition = GetPartitionByNumber(partitions, num)
if "label" in partition:
return partition["label"]
else:
return "UNTITLED"
def DoDebugOutput(image_type, layout_filename):
"""Prints out a human readable disk layout in on-disk order.
This will round values larger than 1MB, it's exists to quickly
visually verify a layout looks correct.
Args:
image_type: Type of image eg base/test/dev/factory_install
layout_filename: Path to partition configuration file
"""
config = LoadPartitionConfig(layout_filename)
partitions = GetPartitionTable(config, image_type)
for partition in partitions:
if partition["bytes"] < 1024 * 1024:
size = "%d bytes" % partition["bytes"]
else:
size = "%d MB" % (partition["bytes"] / 1024 / 1024)
if "label" in partition:
if "fs_bytes" in partition:
if partition["fs_bytes"] < 1024 * 1024:
fs_size = "%d bytes" % partition["fs_bytes"]
else:
fs_size = "%d MB" % (partition["fs_bytes"] / 1024 / 1024)
print "%s - %s/%s" % (partition["label"], fs_size, size)
else:
print "%s - %s" % (partition["label"], size)
else:
print "blank - %s" % size
def main(argv):
action_map = {
"write": {
"argc": 4,
"usage": "<image_type> <partition_config_file> <script_file>",
"func": WritePartitionScript
},
"readblocksize": {
"argc": 2,
"usage": "<partition_config_file>",
"func": GetBlockSize
},
"readfsblocksize": {
"argc": 2,
"usage": "<partition_config_file>",
"func": GetFilesystemBlockSize
},
"readpartsize": {
"argc": 4,
"usage": "<image_type> <partition_config_file> <partition_num>",
"func": GetPartitionSize
},
"readfssize": {
"argc": 4,
"usage": "<image_type> <partition_config_file> <partition_num>",
"func": GetFilesystemSize
},
"readlabel": {
"argc": 4,
"usage": "<image_type> <partition_config_file> <partition_num>",
"func": GetLabel
},
"debug": {
"argc": 3,
"usage": "<image_type> <partition_config_file>",
"func": DoDebugOutput
}
}
if len(sys.argv) < 2 or sys.argv[1] not in action_map:
print "Usage: %s <action>\n" % sys.argv[0]
print "Valid actions are:"
for action in action_map:
print " %s %s" % (action, action_map[action]["usage"])
sys.exit(1)
else:
action_name = sys.argv[1]
action = action_map[action_name]
if action["argc"] == len(sys.argv) - 1:
print action["func"](*sys.argv[2:])
else:
sys.exit("Usage: %s %s %s" % (sys.argv[0], sys.argv[1], action["usage"]))
if __name__ == "__main__":
main(sys.argv)