uhd/mpm/python/usrp_update_fs
Martin Braun d610a16cc4 mpm: Update usrp_update_fs to support X410
With this patch, we can, for example, run

    usrp_update_fs -t master

to download the currently most up-to-date filesystem and apply to the
device.
2021-07-08 00:05:43 -07:00

199 lines
6.5 KiB
Python
Executable file

#!/usr/bin/env python3
#
# Copyright 2018 Ettus Research, a National Instruments Company
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
"""
Embedded USRP filesystem update utility
"""
import sys
import os
import time
import subprocess
import argparse
import tempfile
import requests
MPM_DEVICE = None # This is the default value
try:
from usrp_mpm import __mpm_device__ as MPM_DEVICE
except ImportError:
print("ERROR: Could not import MPM.")
DEFAULT_IMAGES_INSTALL_LOCATION = '/usr/share/uhd/images/'
DEFAULT_REMOTE_MANIFEST_URL =\
'https://raw.githubusercontent.com/EttusResearch/uhd/{tag}/images/manifest.txt'
DEFAULT_FS_IMAGE = {
'n3xx': 'usrp_n3xx_fs.mender',
'e320': 'usrp_e320_fs.mender',
'x4xx': 'usrp_x4xx_fs.mender',
}
DEFAULT_MENDER_TARGET = {
'n3xx': 'n3xx_...._mender_default',
'e320': 'e320_...._mender_default',
'x4xx': 'x4xx_common_mender_default',
}
def parse_args():
""" Parse args """
parser = argparse.ArgumentParser(
description="""USRP filesystem update
If run without any options, it will reset the filesystem to the state
it was originally in, but with the same version ("factory reset").
By using -m or -t, the precise version of the new filesystem can be
selected. -t master will update to the very latest filesystem image.
Most options require access to the internet to download the latest
manifest and/or filesystem image.
""",
)
parser.add_argument(
'--image',
help="Binary image of the filesystem update. If this is given, all "
"other options are ignored. Can be a file or a URL",
)
parser.add_argument(
'-y', '--yes', action="store_true",
help="Answer questions with yes",
)
parser.add_argument(
'-m', '--manifest',
help="Manifest source. Can be a file or a URL. Overwrites -t",
)
parser.add_argument(
'-d', '--device-type', default=MPM_DEVICE, required=MPM_DEVICE is None,
help="Specify/overwrite device type " \
"(DANGER! May brick device if used incorrectly!)",
)
parser.add_argument(
'-t', '--git-tag',
help="Will try and locate the given git tag or branch and get the "
"corresponding manifest. Ignored when -m is given. Using this "
"requires internet access.",
)
return parser.parse_args()
def download_image(device_type, manifest_path=None):
"""
Run uhd_images_downloader to fetch the mender image
"""
downloader_command = [
'python3',
'/usr/bin/uhd_images_downloader',
'-t', DEFAULT_MENDER_TARGET[device_type],
'-i', DEFAULT_IMAGES_INSTALL_LOCATION,
]
if manifest_path is not None:
downloader_command = downloader_command + ['-m', manifest_path]
subprocess.check_call(downloader_command)
def prepare_manifest(manifest):
"""Create a valid local path for a manifest"""
manifest = manifest.strip()
if os.path.isfile(manifest):
return manifest
if manifest.strip().startswith('http'):
manifest = manifest.strip()
temp_dir = tempfile.mkdtemp()
print("Downloading manifest file from {}...".format(
manifest.strip()))
req_obj = requests.get(manifest)
manifest_path = os.path.join(temp_dir, 'manifest.txt')
open(manifest_path, 'wb').write(req_obj.content)
# This will leave a stray file in /tmp but that's OK... we're blowing
# away the FS anyway
return manifest_path
raise RuntimeError("Unknown manifest location type: " + manifest)
def get_manifest_from_tag(git_tag):
"""Turn a git tag or branch into a URL to a valid manifest"""
manifest_url = DEFAULT_REMOTE_MANIFEST_URL.format(tag=git_tag)
return prepare_manifest(manifest_url)
def prepare_image(device_type, args):
"""Figure out which mender image to use, and make sure it's available.
Returns the path to a valid Mender image.
"""
if args.image is not None:
print("Using image for upgrade: {}".format(args.image))
# This is the only case where we don't invoke the images downloader
return args.image
manifest_path = None
if args.manifest is not None:
print("Using manifest to download image: {}".format(args.manifest))
manifest_path = prepare_manifest(args.manifest)
elif args.git_tag:
manifest_path = get_manifest_from_tag(args.git_tag)
else:
print("Using default manifest.")
# Note: The image downloader may choose to do nothing if the requested
# image is already downloaded. It's safe to call anyway.
download_image(device_type, manifest_path)
return os.path.join(
DEFAULT_IMAGES_INSTALL_LOCATION,
DEFAULT_FS_IMAGE[device_type]
)
def apply_image(mender_image):
"""Actually run mender to inject the mender_image"""
subprocess.check_call(['mender', 'install', mender_image])
def reboot():
"""
Reboot
"""
print("Will reboot now. Hit Ctrl-C before the countdown expires to cancel.")
print("Rebooting in 3...")
time.sleep(1)
print("2...")
time.sleep(1)
print("1...")
time.sleep(1)
os.system('reboot')
def run():
"""Main execution"""
args = parse_args()
mender_image = prepare_image(args.device_type, args)
if mender_image is None:
print("Error: Unable to identify filesystem image!")
return False
apply_image(mender_image)
print("Applied image. After reboot, check if everything works, and then "
"run the command '$ mender -commit' to confirm (otherwise, this "
"update will be undone).")
print("Note: Any data stored in this partition will be not accessible "
"after reboot.")
if args.yes:
reboot_now = 'y'
else:
reboot_now = input("Reboot now? [Yn] ")
if reboot_now == "" or reboot_now.lower() == 'y':
try:
reboot() # This will implicitly terminate the program, but it looks
# neater if we have the return statement here
return True
except KeyboardInterrupt:
pass
print("Skipping reboot. Your device will load the updated file system "
"on the next boot.")
return True
def main():
""" Go, go, go! """
try:
return run()
except SystemExit: # for argparse's --help
return True
except BaseException as ex:
print("Error: Unexpected exception caught!")
print(str(ex))
return False
if __name__ == '__main__':
sys.exit(not main())