Skip to content

Commit 89fdb60

Browse files
committed
Added host-only mode to toggle host mount verifications
Signed-off-by: Zen <[email protected]>
1 parent 7f1566a commit 89fdb60

File tree

4 files changed

+84
-33
lines changed

4 files changed

+84
-33
lines changed

readme.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ Modules write to a shared config dict that is accessible by other modules.
7070
* `build_dir` (/tmp/initramfs) Defines where the build will take place.
7171
* `out_dir` (/tmp/initramfs_out) Defines where packed files will be placed.
7272
* `clean` (true) forces the build dir to be cleaned on each run.
73+
* `hostonly` (true) adds additional checks to verify the initramfs will work on the build host.
7374
* `old_count` (1) Sets the number of old file to keep when running the `_rotate_old` function.
7475
* `file_owner` (portage) sets the owner for items pulled into the initramfs on the build system
7576
* `binaries` is a list used to define programs to be pulled into the initrams. `which` is used to find the path of added entries, and `lddtree` is used to resolve dependendies.

ugrd/base/core.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ file_owner = "portage"
22
build_dir = "/tmp/initramfs"
33
out_dir = "/tmp/initramfs_out"
44
clean = true
5+
hostonly = true
56
library_paths = [ "/lib64" ]
67
old_count = 1
78

@@ -38,6 +39,7 @@ build_dir = "Path" # The directory where the initramfs is built
3839
out_dir = "Path" # The directory where the initramfs is packed/output. If no packer is used, this is the final output directory.
3940
old_count = "int" # The number of times to cycle old files before deleting
4041
clean = "bool" # Add the clean property, used to define if the mounts should be cleaned up after boot
42+
hostonly = "bool" # If true, the initramfs will be designed for the host creating it
4143
file_owner = "str" # Add the file_owner property, used to define who should own the copied initramfs files
4244
_file_owner_uid = "int" # Add the _file_owner_uid property, used to store the uid of the file owner
4345
_custom_init_file = "str" # Add the _custom_init_file propety, used to set where the custom init file is located

ugrd/fs/mounts.py

Lines changed: 81 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
__author__ = 'desultory'
2-
__version__ = '1.1.1'
2+
__version__ = '1.1.2'
33

44
from pathlib import Path
55

66

77
MOUNT_PARAMETERS = ['destination', 'source', 'type', 'options', 'base_mount', 'skip_unmount', 'remake_mountpoint']
8+
SOURCE_TYPES = ['uuid', 'partuuid', 'label']
89

910

1011
def _process_mounts_multi(self, mount_name, mount_config):
1112
"""
1213
Processes the passed mounts into fstab mount objects
1314
under 'mounts'
1415
"""
16+
# If the mount already exists, merge the options and update it
1517
if mount_name in self['mounts']:
1618
self.logger.info("Updating mount: %s" % mount_name)
1719
self.logger.debug("[%s] Updating mount with: %s" % (mount_name, mount_config))
@@ -21,14 +23,36 @@ def _process_mounts_multi(self, mount_name, mount_config):
2123
mount_config.pop('options')
2224
mount_config = dict(self['mounts'][mount_name], **mount_config)
2325

24-
for parameter in mount_config:
25-
if parameter not in MOUNT_PARAMETERS:
26+
# Validate the mount config
27+
for parameter, value in mount_config.items():
28+
if parameter == 'source' and isinstance(value, dict):
29+
for source_type in SOURCE_TYPES:
30+
if source_type in value:
31+
break
32+
else:
33+
self.logger.info("Valid source types: %s" % SOURCE_TYPES)
34+
raise ValueError("Invalid source type in mount: %s" % value)
35+
elif parameter not in MOUNT_PARAMETERS:
2636
raise ValueError("Invalid parameter in mount: %s" % parameter)
2737

38+
# Set defaults
2839
mount_config['destination'] = Path(mount_config.get('destination', mount_name))
2940
mount_config['base_mount'] = mount_config.get('base_mount', False)
3041
mount_config['options'] = set(mount_config.get('options', ''))
3142

43+
# Check if the mount exists on the host if it's not a base mount
44+
if not mount_config['base_mount']:
45+
# Only check the root mount after the source has been defined
46+
if mount_name == 'root':
47+
if 'source' in mount_config:
48+
_validate_host_mount(self, mount_config, '/')
49+
# The source must be defined for non-root mounts
50+
elif 'source' not in mount_config:
51+
raise ValueError("[%s] No source specified in mount: %s" % (mount_name, mount_config))
52+
else:
53+
_validate_host_mount(self, mount_config)
54+
55+
# Add imports based on the mount type
3256
if mount_type := mount_config.get('type'):
3357
if mount_type == 'vfat':
3458
self['_kmod_depend'] = 'vfat'
@@ -54,14 +78,11 @@ def _get_mount_source(self, mount, pad=False):
5478

5579
out_str = ''
5680
if isinstance(source, dict):
57-
if 'uuid' in source:
58-
out_str = f"UUID={source['uuid']}"
59-
elif 'partuuid' in source:
60-
out_str = f"PARTUUID={source['partuuid']}"
61-
elif 'label' in source:
62-
out_str = f"LABEL={source['label']}"
63-
else:
64-
raise ValueError("Unable to process source entry: %s" % repr(source))
81+
# Create the source string from the dict
82+
for source_type in SOURCE_TYPES:
83+
if source_type in source:
84+
out_str = f"{source_type.upper()}={source[source_type]}"
85+
break
6586
else:
6687
out_str = source
6788

@@ -157,6 +178,11 @@ def _get_mounts_source(self, mount):
157178
"""
158179
Returns the source device of a mountpoint on /proc/mounts
159180
"""
181+
# Make the mount a string and ensure it starts with a /
182+
mount = str(mount)
183+
if not mount.startswith('/') and not mount.startswith(' /'):
184+
mount = '/' + mount
185+
160186
self.logger.debug("Getting mount source for: %s" % mount)
161187
# Add space padding to the mount name
162188
mount = mount if mount.startswith(' ') else ' ' + mount
@@ -177,9 +203,16 @@ def _get_blkid_info(self, device):
177203
"""
178204
Gets the blkid info for a device
179205
"""
206+
from subprocess import run
180207
self.logger.debug("Getting blkid info for: %s" % device)
181208

182-
mount_info = self._run(['blkid', str(device)]).stdout.decode().strip()
209+
cmd = run(['blkid', str(device)], capture_output=True)
210+
if cmd.returncode != 0:
211+
self.logger.warning("Unable to find blkid info for: %s" % device)
212+
return None
213+
214+
mount_info = cmd.stdout.decode().strip()
215+
183216
if not mount_info:
184217
self.logger.warning("Unable to find blkid info for: %s" % device)
185218
return None
@@ -188,33 +221,50 @@ def _get_blkid_info(self, device):
188221
return mount_info
189222

190223

191-
def mount_root(self):
224+
def _validate_host_mount(self, mount, destination_path=None):
192225
"""
193-
Mounts the root partition.
194-
Warns if the root partition isn't found on the current system.
226+
Checks if a defined mount exists on the host
195227
"""
196-
root_source = self.config_dict['mounts']['root']['source']
197-
host_root_dev = _get_mounts_source(self, '/')
228+
if not self['hostonly']:
229+
self.logger.debug("Skipping host mount check as hostonly is not set")
230+
return
231+
232+
source = mount['source']
233+
destination_path = mount['destination'] if destination_path is None else destination_path
198234

199-
# If the root device is a string, check that it's the same path as the host root mount
200-
if isinstance(root_source, str):
201-
if root_source != host_root_dev:
202-
self.logger.warning("Root device mismatch. Expected: %s, Found: %s" % (root_source, host_root_dev))
203-
elif isinstance(root_source, dict):
204-
# If the root device is a dict, check that the uuid, partuuid, or label matches the host root mount
205-
if blkid_info := _get_blkid_info(self, host_root_dev):
235+
host_source_dev = _get_mounts_source(self, destination_path)
236+
if not host_source_dev:
237+
self.logger.error("Unable to find mount on host system: %s" % destination_path)
238+
return
239+
else:
240+
self.logger.debug("Checking host volume: %s" % host_source_dev)
241+
242+
if isinstance(source, str):
243+
if source != host_source_dev:
244+
self.logger.warning("Host device mismatch. Expected: %s, Found: %s" % (source, host_source_dev))
245+
elif isinstance(source, dict):
246+
# If the source is a dict, check that the uuid, partuuid, or label matches the host mount
247+
if blkid_info := _get_blkid_info(self, host_source_dev):
206248
# Unholy for-else, breaks if the uuid, partuuid, or label matches, otherwise warns
207-
for key, value in root_source.items():
208-
search_str = f"{key.upper()}=\"{value}\""
249+
for key, value in source.items():
250+
search_str = f'{key.upper()}="{value}"'
209251
if value in blkid_info:
210-
self.logger.debug("Found root device match: %s" % search_str)
211-
break
252+
self.logger.debug("Fount host device match: %s" % search_str)
253+
return True
212254
else:
213-
self.logger.warning("Configuration root device not found on host system. Expected: %s" % root_source)
214-
self.logger.warning("Host system root device info: %s" % blkid_info)
255+
self.logger.error("Mount device not found on host system. Expected: %s" % source)
256+
self.logger.error("Host device info: %s" % blkid_info)
215257
else:
216-
self.logger.warning("Unable to find blkid info for: %s" % root_source)
258+
self.logger.warning("Unable to find blkid info for: %s" % host_source_dev)
259+
260+
raise ValueError("Unable to validate host mount: %s" % mount)
217261

262+
263+
def mount_root(self):
264+
"""
265+
Mounts the root partition.
266+
Warns if the root partition isn't found on the current system.
267+
"""
218268
root_path = self.config_dict['mounts']['root']['destination']
219269

220270
return [f"mount {root_path} || (echo 'Failed to mount root partition' && bash)"]

ugrd/fs/mounts.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ binaries = [
77
"umount",
88
]
99

10-
paths = [ "mnt/root" ]
11-
1210
mount_wait = false
1311

1412
[imports.config_processing]

0 commit comments

Comments
 (0)