mirror of
https://github.com/flatcar/scripts.git
synced 2025-08-08 05:26:58 +02:00
Make it possible to run chromeos-install without root BUG=none TEST=Build image, chromeos-install, verify ROOT-B is full size Change-Id: Id506f1e5a6f8b8ee03ea1bdd621aaab1239bca2c Reviewed-on: https://gerrit.chromium.org/gerrit/34081 Reviewed-by: Don Garrett <dgarrett@chromium.org> Commit-Ready: Chris Masone <cmasone@chromium.org> Tested-by: Chris Masone <cmasone@chromium.org>
412 lines
12 KiB
Python
Executable File
412 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:
|
|
partition["var"] = partition["blocks"]
|
|
if partition["type"] != "blank":
|
|
|
|
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"
|
|
|
|
|
|
sfile.write("STATEFUL_SIZE=$((STATEFUL_SIZE-(STATEFUL_SIZE %% %d)))\n" %
|
|
config["metadata"]["fs_block_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["var"])
|
|
|
|
# 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)
|