mirror of
https://github.com/saymrwulf/uhd.git
synced 2026-05-14 20:58:09 +00:00
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.
199 lines
6.5 KiB
Python
Executable file
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())
|