# SPDX-FileCopyrightText: Huawei Inc. # # SPDX-License-Identifier: Apache-2.0 # This class provides support for defining, at the level of the recipe, the # required writable paths. It provides support for maintaining persistent or # volatile state at runtime for read-only filesystems. # # To take advantage of the provided functionality, one would define a set of # writable as part of WRITABLES: # WRITABLES = "logs database misc" # # Each writable's associated path is defined using a VarFlag: # # WRITABLE_PATH[logs] = "/foo/bar/logs" # WRITABLE_PATH[database] = "/foo/bar/db" # WRITABLE_PATH[misc] = "/foo/bar/misc" # # Each WRITABLE_PATH is a directory. # # By default, each writable is of type persistent (it will use a bind mount # from the state partition to the writable's path). This can be switched to # volatile by defining: # # WRITABLE_TYPE[logs] = "volatile" # # The supported types are "volatile" and "persistent". # # This will use a tmpfs for the provided writable path. inherit systemd # The read-write area needs to be provided by the OS in the form of a rw # mountpoint handled with systemd mount unit. In this way the state/writable # systemd mount units this class generates, will have the correct dependency on # having the read-write partition mounted. SYSTEM_STATE_RUNTIME_MOUNTPOINT ??= "/var/run/mount/sysdata" SYSTEM_STATE_MOUNT_UNIT ??= "run-mount-sysdata.mount" # The mount units depend on having the system state partition mounted at a # known location as described above. The respective system partition mount # units are part of the oniro-mounts package. This provides the # SYSTEM_STATE_MOUNT_UNIT systemd mount unit. RDEPENDS_${PN} += "oniro-mounts" # This is the root filesystem hierarchy used as part of the bind mount units to # provide read-write locations. SYSDATA_OVERLAY ??= "rootfs-overlay" def escapeSystemdUnitName(path): escapeMap = { '/': '-', '-': "\\x2d", '\\': "\\x5d" } return "".join([escapeMap.get(c, c) for c in path.strip('/')]) def mountUnitName(unit): return escapeSystemdUnitName(unit) + '.mount' def write_mount_unit(writable, what, dst): """Writes a mount unit to destination - argument `dst`. The mount unit is described as a dictionary (argument `writable`) for most of the mount unit parts with the exception of `What` which is provided by the argument 'what'.""" MountUnit = "[Unit]\n" \ "DefaultDependencies=no\n" \ "Conflicts=umount.target\n" \ "Before=local-fs.target umount.target\n" if writable["after"]: MountUnit += "After=%s\n" % ' '.join(writable["after"]) if writable["requires"]: MountUnit += "Requires=%s\n" % ' '.join(writable["after"]) MountUnit += "\n"\ "[Mount]\n" if what: MountUnit += "What=%s\n" % what if writable["where"]: MountUnit += "Where=%s\n" % writable["where"] if writable["type"]: MountUnit += "Type=%s\n" % writable["type"] if writable["options"]: MountUnit += "Options=%s\n" % ','.join(writable["options"]) MountUnit += "\n"\ "[Install]\n" \ "WantedBy=local-fs.target\n" with open((dst), 'w') as f: f.write(MountUnit) def get_writable_data(d): wdata = [] writables = d.getVar('WRITABLES') if not writables: bb.fatal("No writable paths defined") for writable in writables.split(): where = d.getVarFlag('WRITABLE_PATH', writable) if not where: bb.fatal("No writable path defined for %s" % writable) options = [] after = [] requires = [] type = d.getVarFlag('WRITABLE_TYPE', writable) or 'persistent' if type == 'persistent': state_mount_unit = d.getVar('SYSTEM_STATE_MOUNT_UNIT') if not state_mount_unit: bb.fatal("SYSTEM_STATE_MOUNT_UNIT not defined") type = 'none' options.append('bind') after.append(state_mount_unit) requires.append(state_mount_unit) elif type == 'volatile': type = 'tmpfs' else: bbfatal("%s mount type not implemented" % type) writable_data = { 'after': after, 'requires': requires, 'id': writable, 'where': where, 'type': type, 'options': options, } wdata.append(writable_data) return wdata python() { import os systemd_system_unitdir = d.getVar('systemd_system_unitdir') for writable in get_writable_data(d): d.appendVar('FILES_' + d.getVar('PN'), ' ' + writable['where']) d.appendVar('FILES_' + d.getVar('PN'), ' ' + \ os.path.join(systemd_system_unitdir, mountUnitName(writable['where']))) d.appendVar('SYSTEMD_SERVICE_' + d.getVar('PN'), ' ' + \ mountUnitName(writable['where'])) } python install_mount_units() { import os # Validate and define the path where the persistent state is kept state_mountpoint = d.getVar('SYSTEM_STATE_RUNTIME_MOUNTPOINT') if not state_mountpoint: bb.fatal("SYSTEM_STATE_RUNTIME_MOUNTPOINT not defined.") sys_data_overlay = d.getVar('SYSDATA_OVERLAY') if not sys_data_overlay: bb.fatal("SYSDATA_OVERLAY not defined.") persistent_path = os.path.join(state_mountpoint, sys_data_overlay) dee = d.getVar('D') systemd_system_unitdir = d.getVar('systemd_system_unitdir').strip('/') d_systemd_system_unitdir = os.path.join(dee, systemd_system_unitdir) os.makedirs(d_systemd_system_unitdir, exist_ok=True) for writable in get_writable_data(d): where = writable['where'].strip('/') # Where usually is created by systemd when not in place but on a # read-only filesystem that won't work so we make sure we have the # right mountpoints on the filesystem. where_installpath = os.path.join(dee, where) if os.path.exists(where_installpath): if os.path.isdir(where_installpath) and os.listdir(where_installpath): bb.fatal("The path for %s writable (%s) already exists and contains entries that will be shadowed at runtime. Please fix." % (writable, where_installpath)) elif os.path.isdir(where_installpath) and not os.listdir(where_installpath): pass else: bb.fatal("The path for %s writable (%s) already exists and it is not an empty directory. Please fix." % (writable, where_installpath)) else: os.makedirs(os.path.join(dee, where)) if writable['type'] == 'tmpfs': what = 'tmpfs' else: what = os.path.join(persistent_path, where) dst = os.path.join(d_systemd_system_unitdir, mountUnitName(where)) write_mount_unit(writable, what, dst) } do_install[vardeps] += "WRITABLES WRITABLE_PATH WRITABLE_TYPE" do_install[postfuncs] += "install_mount_units"