Skip to content

Commit 2ad1168

Browse files
authored
Fix unsupported fs.squashfs extraction in sonic-installer (sonic-net#1366)
Abstracted away the access to the path of the rootfs via a contextmanager. In Arista secureboot, the rootfs is never extracted on the flash. Instead it's mounted directly from within the signed SWI. The update_sonic_environment function however always assume that the rootfs to be at the same place. - How I did it To alleviate this restriction, a new context manager to obtain the rootfs is introduced. The choice of a context manager rather than a function is entirely based on error management and cleanup. Mounting a squashfs from a swi file requires the use of losetup which makes the rootfs available under /dev/loopX Once done or on error, we need to free this resource which becomes free when using a contextmanager.
1 parent 7578f08 commit 2ad1168

4 files changed

Lines changed: 64 additions & 33 deletions

File tree

sonic_installer/bootloader/aboot.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import subprocess
1010
import sys
1111
import zipfile
12+
from contextlib import contextmanager
1213

1314
import click
1415

@@ -18,7 +19,9 @@
1819
HOST_PATH,
1920
IMAGE_DIR_PREFIX,
2021
IMAGE_PREFIX,
22+
ROOTFS_NAME,
2123
run_command,
24+
run_command_or_raise,
2225
)
2326
from .bootloader import Bootloader
2427

@@ -34,7 +37,7 @@ def isSecureboot():
3437
global _secureboot
3538
if _secureboot is None:
3639
with open('/proc/cmdline') as f:
37-
m = re.search(r"secure_boot_enable=[y1]", f.read())
40+
m = re.search(r"secure_boot_enable=[y1]", f.read())
3841
_secureboot = bool(m)
3942
return _secureboot
4043

@@ -179,3 +182,25 @@ def base64Decode(cls, text):
179182
def detect(cls):
180183
with open('/proc/cmdline') as f:
181184
return 'Aboot=' in f.read()
185+
186+
def _get_swi_file_offset(self, swipath, filename):
187+
with zipfile.ZipFile(swipath) as swi:
188+
with swi.open(filename) as f:
189+
return f._fileobj.tell() # pylint: disable=protected-access
190+
191+
@contextmanager
192+
def get_rootfs_path(self, image_path):
193+
rootfs_path = os.path.join(image_path, ROOTFS_NAME)
194+
if os.path.exists(rootfs_path) and not isSecureboot():
195+
yield rootfs_path
196+
return
197+
198+
swipath = os.path.join(image_path, DEFAULT_SWI_IMAGE)
199+
offset = self._get_swi_file_offset(swipath, ROOTFS_NAME)
200+
loopdev = subprocess.check_output(['losetup', '-f']).rstrip()
201+
202+
try:
203+
run_command_or_raise(['losetup', '-o', str(offset), loopdev, swipath])
204+
yield loopdev
205+
finally:
206+
run_command_or_raise(['losetup', '-d', loopdev])

sonic_installer/bootloader/bootloader.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22
Abstract Bootloader class
33
"""
44

5+
from contextlib import contextmanager
56
from os import path
67

78
from ..common import (
89
HOST_PATH,
910
IMAGE_DIR_PREFIX,
1011
IMAGE_PREFIX,
12+
ROOTFS_NAME,
1113
)
1214

1315
class Bootloader(object):
@@ -68,3 +70,7 @@ def get_image_path(cls, image):
6870
prefix = path.join(HOST_PATH, IMAGE_DIR_PREFIX)
6971
return image.replace(IMAGE_PREFIX, prefix)
7072

73+
@contextmanager
74+
def get_rootfs_path(self, image_path):
75+
"""returns the path to the squashfs"""
76+
yield path.join(image_path, ROOTFS_NAME)

sonic_installer/common.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
HOST_PATH = '/host'
1414
IMAGE_PREFIX = 'SONiC-OS-'
1515
IMAGE_DIR_PREFIX = 'image-'
16+
ROOTFS_NAME = 'fs.squashfs'
1617

1718
# Run bash command and print output to stdout
1819
def run_command(command):

sonic_installer/main.py

Lines changed: 31 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from swsscommon.swsscommon import SonicV2Connector
1212

1313
from .bootloader import get_bootloader
14-
from .common import run_command, run_command_or_raise
14+
from .common import run_command, run_command_or_raise, IMAGE_PREFIX
1515
from .exception import SonicRuntimeException
1616

1717
SYSLOG_IDENTIFIER = "sonic-installer"
@@ -218,8 +218,7 @@ def print_deprecation_warning(deprecated_cmd_or_subcmd, new_cmd_or_subcmd):
218218
fg="red", err=True)
219219
click.secho("Please use '{}' instead".format(new_cmd_or_subcmd), fg="red", err=True)
220220

221-
222-
def update_sonic_environment(click, binary_image_version):
221+
def update_sonic_environment(click, bootloader, binary_image_version):
223222
"""Prepare sonic environment variable using incoming image template file. If incoming image template does not exist
224223
use current image template file.
225224
"""
@@ -234,38 +233,38 @@ def umount_next_image_fs(mount_point):
234233
SONIC_ENV_TEMPLATE_FILE = os.path.join("usr", "share", "sonic", "templates", "sonic-environment.j2")
235234
SONIC_VERSION_YML_FILE = os.path.join("etc", "sonic", "sonic_version.yml")
236235

237-
sonic_version = re.sub("SONiC-OS-", '', binary_image_version)
238-
new_image_dir = os.path.join('/', "host", "image-{0}".format(sonic_version))
239-
new_image_squashfs_path = os.path.join(new_image_dir, "fs.squashfs")
236+
sonic_version = re.sub(IMAGE_PREFIX, '', binary_image_version)
237+
new_image_dir = bootloader.get_image_path(binary_image_version)
240238
new_image_mount = os.path.join('/', "tmp", "image-{0}-fs".format(sonic_version))
241239
env_dir = os.path.join(new_image_dir, "sonic-config")
242240
env_file = os.path.join(env_dir, "sonic-environment")
243241

244-
try:
245-
mount_next_image_fs(new_image_squashfs_path, new_image_mount)
246-
247-
next_sonic_env_template_file = os.path.join(new_image_mount, SONIC_ENV_TEMPLATE_FILE)
248-
next_sonic_version_yml_file = os.path.join(new_image_mount, SONIC_VERSION_YML_FILE)
249-
250-
sonic_env = run_command_or_raise([
251-
"sonic-cfggen",
252-
"-d",
253-
"-y",
254-
next_sonic_version_yml_file,
255-
"-t",
256-
next_sonic_env_template_file,
257-
])
258-
os.mkdir(env_dir, 0o755)
259-
with open(env_file, "w+") as ef:
260-
print(sonic_env, file=ef)
261-
os.chmod(env_file, 0o644)
262-
except SonicRuntimeException as ex:
263-
echo_and_log("Warning: SONiC environment variables are not supported for this image: {0}".format(str(ex)), LOG_ERR, fg="red")
264-
if os.path.exists(env_file):
265-
os.remove(env_file)
266-
os.rmdir(env_dir)
267-
finally:
268-
umount_next_image_fs(new_image_mount)
242+
with bootloader.get_rootfs_path(new_image_dir) as new_image_squashfs_path:
243+
try:
244+
mount_next_image_fs(new_image_squashfs_path, new_image_mount)
245+
246+
next_sonic_env_template_file = os.path.join(new_image_mount, SONIC_ENV_TEMPLATE_FILE)
247+
next_sonic_version_yml_file = os.path.join(new_image_mount, SONIC_VERSION_YML_FILE)
248+
249+
sonic_env = run_command_or_raise([
250+
"sonic-cfggen",
251+
"-d",
252+
"-y",
253+
next_sonic_version_yml_file,
254+
"-t",
255+
next_sonic_env_template_file,
256+
])
257+
os.mkdir(env_dir, 0o755)
258+
with open(env_file, "w+") as ef:
259+
print(sonic_env, file=ef)
260+
os.chmod(env_file, 0o644)
261+
except SonicRuntimeException as ex:
262+
echo_and_log("Warning: SONiC environment variables are not supported for this image: {0}".format(str(ex)), LOG_ERR, fg="red")
263+
if os.path.exists(env_file):
264+
os.remove(env_file)
265+
os.rmdir(env_dir)
266+
finally:
267+
umount_next_image_fs(new_image_mount)
269268

270269
# Main entrypoint
271270
@click.group(cls=AliasedGroup)
@@ -332,7 +331,7 @@ def install(url, force, skip_migration=False):
332331
else:
333332
run_command('config-setup backup')
334333

335-
update_sonic_environment(click, binary_image_version)
334+
update_sonic_environment(click, bootloader, binary_image_version)
336335

337336
# Finally, sync filesystem
338337
run_command("sync;sync;sync")

0 commit comments

Comments
 (0)