178 строки
5.7 KiB
Python
178 строки
5.7 KiB
Python
# Copyright 2018 The Chromium Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
"""Functions used to provision Fuchsia boot images."""
|
|
|
|
import common
|
|
import logging
|
|
import os
|
|
import subprocess
|
|
import tempfile
|
|
import time
|
|
import uuid
|
|
|
|
_SSH_CONFIG_TEMPLATE = """
|
|
Host *
|
|
CheckHostIP no
|
|
StrictHostKeyChecking no
|
|
ForwardAgent no
|
|
ForwardX11 no
|
|
UserKnownHostsFile {known_hosts}
|
|
User fuchsia
|
|
IdentitiesOnly yes
|
|
IdentityFile {identity}
|
|
ServerAliveInterval 2
|
|
ServerAliveCountMax 5
|
|
ControlMaster auto
|
|
ControlPersist 1m
|
|
ControlPath /tmp/ssh-%r@%h:%p
|
|
ConnectTimeout 5
|
|
"""
|
|
|
|
FVM_TYPE_QCOW = 'qcow'
|
|
FVM_TYPE_SPARSE = 'sparse'
|
|
|
|
|
|
def _TargetCpuToSdkBinPath(target_arch):
|
|
"""Returns the path to the SDK 'target' file directory for |target_cpu|."""
|
|
|
|
return os.path.join(common.SDK_ROOT, 'target', target_arch)
|
|
|
|
|
|
def _ProvisionSSH(output_dir):
|
|
"""Provisions the key files used by the SSH daemon, and generates a
|
|
configuration file used by clients for connecting to SSH.
|
|
|
|
Returns a tuple with:
|
|
#0: the client configuration file
|
|
#1: a list of file path pairs: (<path in image>, <path on build filesystem>).
|
|
"""
|
|
|
|
host_key_path = output_dir + '/ssh_key'
|
|
host_pubkey_path = host_key_path + '.pub'
|
|
id_key_path = output_dir + '/id_ed25519'
|
|
id_pubkey_path = id_key_path + '.pub'
|
|
known_hosts_path = output_dir + '/known_hosts'
|
|
ssh_config_path = GetSSHConfigPath(output_dir)
|
|
|
|
logging.debug('Generating SSH credentials.')
|
|
if not os.path.isfile(host_key_path):
|
|
subprocess.check_call(['ssh-keygen', '-t', 'ed25519', '-h', '-f',
|
|
host_key_path, '-P', '', '-N', ''],
|
|
stdout=open(os.devnull))
|
|
if not os.path.isfile(id_key_path):
|
|
subprocess.check_call(['ssh-keygen', '-t', 'ed25519', '-f', id_key_path,
|
|
'-P', '', '-N', ''], stdout=open(os.devnull))
|
|
|
|
with open(ssh_config_path, "w") as ssh_config:
|
|
ssh_config.write(
|
|
_SSH_CONFIG_TEMPLATE.format(identity=id_key_path,
|
|
known_hosts=known_hosts_path))
|
|
|
|
if os.path.exists(known_hosts_path):
|
|
os.remove(known_hosts_path)
|
|
|
|
return (
|
|
ssh_config_path,
|
|
(('ssh/ssh_host_ed25519_key', host_key_path),
|
|
('ssh/ssh_host_ed25519_key.pub', host_pubkey_path),
|
|
('ssh/authorized_keys', id_pubkey_path))
|
|
)
|
|
|
|
|
|
def _MakeQcowDisk(output_dir, disk_path):
|
|
"""Creates a QEMU copy-on-write version of |disk_path| in the output
|
|
directory."""
|
|
|
|
qimg_path = os.path.join(common.SDK_ROOT, 'qemu', 'bin', 'qemu-img')
|
|
output_path = os.path.join(output_dir,
|
|
os.path.basename(disk_path) + '.qcow2')
|
|
subprocess.check_call([qimg_path, 'create', '-q', '-f', 'qcow2',
|
|
'-b', disk_path, output_path])
|
|
return output_path
|
|
|
|
|
|
def GetTargetFile(target_arch, filename):
|
|
"""Computes a path to |filename| in the Fuchsia target directory specific to
|
|
|target_arch|."""
|
|
|
|
return os.path.join(_TargetCpuToSdkBinPath(target_arch), filename)
|
|
|
|
|
|
def GetSSHConfigPath(output_dir):
|
|
return output_dir + '/ssh_config'
|
|
|
|
|
|
def ConfigureDataFVM(output_dir, output_type):
|
|
"""Builds the FVM image for the /data volume and prepopulates it
|
|
with SSH keys.
|
|
|
|
output_dir: Path to the output directory which will contain the FVM file.
|
|
output_type: If FVM_TYPE_QCOW, then returns a path to the qcow2 FVM file,
|
|
used for QEMU.
|
|
|
|
If FVM_TYPE_SPARSE, then returns a path to the
|
|
sparse/compressed FVM file."""
|
|
|
|
logging.debug('Building /data partition FVM file.')
|
|
# minfs expects absolute paths(bug:
|
|
# https://fuchsia.atlassian.net/browse/ZX-2397)
|
|
output_dir = os.path.abspath(output_dir)
|
|
with tempfile.NamedTemporaryFile() as data_file:
|
|
# Build up the minfs partition data and install keys into it.
|
|
ssh_config, ssh_data = _ProvisionSSH(output_dir)
|
|
with tempfile.NamedTemporaryFile() as manifest:
|
|
for dest, src in ssh_data:
|
|
manifest.write('%s=%s\n' % (dest, src))
|
|
manifest.flush()
|
|
minfs_path = os.path.join(common.SDK_ROOT, 'tools', 'minfs')
|
|
subprocess.check_call([minfs_path, '%s@1G' % data_file.name, 'create'])
|
|
subprocess.check_call([minfs_path, data_file.name, 'manifest',
|
|
manifest.name])
|
|
|
|
# Wrap the minfs partition in a FVM container.
|
|
fvm_path = os.path.join(common.SDK_ROOT, 'tools', 'fvm')
|
|
fvm_output_path = os.path.join(output_dir, 'fvm.data.blk')
|
|
if os.path.exists(fvm_output_path):
|
|
os.remove(fvm_output_path)
|
|
|
|
if output_type == FVM_TYPE_SPARSE:
|
|
cmd = [fvm_path, fvm_output_path, 'sparse', '--compress', 'lz4',
|
|
'--data', data_file.name]
|
|
else:
|
|
cmd = [fvm_path, fvm_output_path, 'create', '--data', data_file.name]
|
|
|
|
logging.debug(' '.join(cmd))
|
|
subprocess.check_call(cmd)
|
|
|
|
if output_type == FVM_TYPE_SPARSE:
|
|
return fvm_output_path
|
|
elif output_type == FVM_TYPE_QCOW:
|
|
return _MakeQcowDisk(output_dir, fvm_output_path)
|
|
else:
|
|
raise Exception('Unknown output_type: %r' % output_type)
|
|
|
|
|
|
def GetNodeName(output_dir):
|
|
"""Returns the cached Zircon node name, or generates one if it doesn't
|
|
already exist. The node name is used by Discover to find the prior
|
|
deployment on the LAN."""
|
|
|
|
nodename_file = os.path.join(output_dir, 'nodename')
|
|
if not os.path.exists(nodename_file):
|
|
nodename = uuid.uuid4()
|
|
f = open(nodename_file, 'w')
|
|
f.write(str(nodename))
|
|
f.flush()
|
|
f.close()
|
|
return str(nodename)
|
|
else:
|
|
f = open(nodename_file, 'r')
|
|
return f.readline()
|
|
|
|
|
|
def GetKernelArgs(output_dir):
|
|
return ['devmgr.epoch=%d' % time.time(),
|
|
'zircon.nodename=' + GetNodeName(output_dir)]
|