From 1d16aea9c3f7376a5176b20283b7c2f3705c6bc2 Mon Sep 17 00:00:00 2001 From: Guillaume Chinal Date: Sun, 19 Jan 2025 11:18:50 +0100 Subject: [PATCH 01/18] custom-persist: ignore /rw/config bind-dirs if custom-persist enabled When the custom-persist feature is enabled, we no longer need to worry about the bind directories configured in /rw/config/qubes-bind-dirs.d. --- vm-systemd/bind-dirs.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/vm-systemd/bind-dirs.sh b/vm-systemd/bind-dirs.sh index c2ff07d6a..83014cd9c 100755 --- a/vm-systemd/bind-dirs.sh +++ b/vm-systemd/bind-dirs.sh @@ -118,7 +118,12 @@ main() { } binds=() -for source_folder in /usr/lib/qubes-bind-dirs.d /etc/qubes-bind-dirs.d /rw/config/qubes-bind-dirs.d ; do +sources=( "/usr/lib/qubes-bind-dirs.d" "/etc/qubes-bind-dirs.d" ) +if [ ! -f "/var/run/qubes-service/custom-persist" ]; then + sources+=( "/rw/config/qubes-bind-dirs.d" ) +fi + +for source_folder in "${sources[@]}"; do true "source_folder: $source_folder" if [ ! -d "$source_folder" ]; then continue From 6e7bed01d418859b621f2a00a7757f01b8999fa4 Mon Sep 17 00:00:00 2001 From: Guillaume Chinal Date: Thu, 23 Jan 2025 23:00:45 +0100 Subject: [PATCH 02/18] custom-persist: systemd mount units for /home and /usr/local and services start dependencies The custom-persist feature should disable /home and /usr/local mounts by default. To do this, we can use SystemD drop-ins which requires to remove fstab entries and convert them to regular SystemD units as drop-ins does not seem to work with units generated by systemd-fstab-generator. Mount command in mount_dirs.sh is not required anymore and need to be deleted as it causes issues. Instead, a we can use SystemD unit options to ensure /home and /usr/local are mounted before loading user bind dirs --- Makefile | 2 ++ debian/qubes-core-agent.install | 3 +++ filesystem/fstab | 2 -- rpm_spec/core-agent.spec.in | 3 +++ vm-systemd/75-qubes-vm.preset | 1 + vm-systemd/NetworkManager.service.d/30_qubes.conf | 2 +- vm-systemd/home.mount | 5 +++++ vm-systemd/mount-dirs.sh | 5 ----- vm-systemd/qubes-bind-dirs.service | 14 ++++++++++++++ vm-systemd/qubes-misc-post.service | 2 +- vm-systemd/qubes-mount-dirs.service | 2 +- vm-systemd/usr-local.mount | 5 +++++ 12 files changed, 36 insertions(+), 10 deletions(-) create mode 100644 vm-systemd/home.mount create mode 100644 vm-systemd/qubes-bind-dirs.service create mode 100644 vm-systemd/usr-local.mount diff --git a/Makefile b/Makefile index c42255395..359c52143 100644 --- a/Makefile +++ b/Makefile @@ -154,6 +154,8 @@ install-systemd: install-init install -m 0644 vm-systemd/xendriverdomain.service $(DESTDIR)/etc/systemd/system/ install -m 0644 vm-systemd/80-qubes-vif.link $(DESTDIR)$(SYSLIBDIR)/systemd/network/ install -m 0644 vm-systemd/30_resolved-no-mdns-or-llmnr.conf $(DESTDIR)$(SYSLIBDIR)/systemd/resolved.conf.d/ + install -m 0644 vm-systemd/home.mount $(DESTDIR)$(SYSLIBDIR)/systemd/system/ + install -m 0644 vm-systemd/usr-local.mount $(DESTDIR)$(SYSLIBDIR)/systemd/system/ .PHONY: install-sysvinit install-sysvinit: install-init diff --git a/debian/qubes-core-agent.install b/debian/qubes-core-agent.install index 3015aa9fd..783967d84 100644 --- a/debian/qubes-core-agent.install +++ b/debian/qubes-core-agent.install @@ -86,6 +86,7 @@ lib/systemd/system/org.cups.cupsd.path.d/30_qubes.conf lib/systemd/system/org.cups.cupsd.service.d/30_qubes.conf lib/systemd/system/org.cups.cupsd.socket.d/30_qubes.conf lib/systemd/system/dev-xvdc1-swap.service +lib/systemd/system/qubes-bind-dirs.service lib/systemd/system/qubes-early-vm-config.service lib/systemd/system/qubes-misc-post.service lib/systemd/system/qubes-mount-dirs.service @@ -107,6 +108,8 @@ lib/systemd/system/sysinit.target.d/30_qubes.conf lib/systemd/system/systemd-timesyncd.service.d/30_qubes.conf lib/systemd/system/systemd-logind.service.d/30_qubes.conf lib/systemd/resolved.conf.d/30_resolved-no-mdns-or-llmnr.conf +lib/systemd/system/home.mount +lib/systemd/system/usr-local.mount usr/lib/sysctl.d/20-qubes-core.conf usr/lib/systemd/user/tracker-extract-3.service.d/30_qubes.conf usr/lib/systemd/user/tracker-miner-fs-3.service.d/30_qubes.conf diff --git a/filesystem/fstab b/filesystem/fstab index 99b686e14..8f76176f6 100644 --- a/filesystem/fstab +++ b/filesystem/fstab @@ -6,8 +6,6 @@ /dev/mapper/dmroot / ext4 defaults,discard,noatime 1 1 /dev/xvdb /rw auto noauto,defaults,discard,nosuid,nodev 1 2 -/rw/home /home none noauto,bind,defaults,nosuid,nodev 0 0 -/rw/usrlocal /usr/local none noauto,bind,defaults 0 0 /dev/xvdc1 swap swap defaults 0 0 tmpfs /dev/shm tmpfs defaults,size=1G 0 0 devpts /dev/pts devpts gid=5,mode=620 0 0 diff --git a/rpm_spec/core-agent.spec.in b/rpm_spec/core-agent.spec.in index fbfd397f8..1546a7afc 100644 --- a/rpm_spec/core-agent.spec.in +++ b/rpm_spec/core-agent.spec.in @@ -1260,6 +1260,8 @@ The Qubes core startup configuration for SystemD init. %defattr(-,root,root,-) /etc/systemd/system/xendriverdomain.service %_unitdir/dev-xvdc1-swap.service +%_unitdir/home.mount +%_unitdir/qubes-bind-dirs.service %_unitdir/qubes-misc-post.service %_unitdir/qubes-mount-dirs.service %_unitdir/qubes-rootfs-resize.service @@ -1271,6 +1273,7 @@ The Qubes core startup configuration for SystemD init. %_unitdir/qubes-sync-time.timer %_unitdir/qubes-updates-proxy-forwarder@.service %_unitdir/qubes-updates-proxy-forwarder.socket +%_unitdir/usr-local.mount %{_unitdir}-preset/%qubes_preset_file %_modulesloaddir/qubes-core.conf %dir %_unitdir/boot.automount.d diff --git a/vm-systemd/75-qubes-vm.preset b/vm-systemd/75-qubes-vm.preset index 394c97df0..82704b187 100644 --- a/vm-systemd/75-qubes-vm.preset +++ b/vm-systemd/75-qubes-vm.preset @@ -96,6 +96,7 @@ enable qubes-network.service enable qubes-network-uplink.service enable qubes-qrexec-agent.service enable qubes-mount-dirs.service +enable qubes-bind-dirs.service enable qubes-rootfs-resize.service enable qubes-firewall.service enable qubes-meminfo-writer.service diff --git a/vm-systemd/NetworkManager.service.d/30_qubes.conf b/vm-systemd/NetworkManager.service.d/30_qubes.conf index 5f598a10a..883c54200 100644 --- a/vm-systemd/NetworkManager.service.d/30_qubes.conf +++ b/vm-systemd/NetworkManager.service.d/30_qubes.conf @@ -1,7 +1,7 @@ [Unit] ConditionPathExists=/var/run/qubes-service/network-manager # For /rw -After=qubes-mount-dirs.service +After=qubes-bind-dirs.service # For /var/run/qubes-service After=qubes-sysinit.service # For configuration of qubes-provided interfaces diff --git a/vm-systemd/home.mount b/vm-systemd/home.mount new file mode 100644 index 000000000..683b791a0 --- /dev/null +++ b/vm-systemd/home.mount @@ -0,0 +1,5 @@ +[Mount] +What=/rw/home +Where=/home +Type=none +Options=noauto,bind,defaults,nosuid,nodev diff --git a/vm-systemd/mount-dirs.sh b/vm-systemd/mount-dirs.sh index 1c3a9e626..46631bbea 100755 --- a/vm-systemd/mount-dirs.sh +++ b/vm-systemd/mount-dirs.sh @@ -11,8 +11,3 @@ if [ -e /dev/xvdb ] ; then mount /rw ; fi /usr/lib/qubes/init/setup-rw.sh initialize_home "/rw/home" ifneeded -echo "Mounting /rw/home onto /home" >&2 -mount /home -echo "Mounting /rw/usrlocal onto /usr/local" >&2 -mount /usr/local -/usr/lib/qubes/init/bind-dirs.sh diff --git a/vm-systemd/qubes-bind-dirs.service b/vm-systemd/qubes-bind-dirs.service new file mode 100644 index 000000000..7c9540777 --- /dev/null +++ b/vm-systemd/qubes-bind-dirs.service @@ -0,0 +1,14 @@ +[Unit] +Description=Mount configured bind files and directories +After=qubes-mount-dirs.service local-fs.target rw.mount home.mount usr-local.mount +Before=qubes-gui-agent.service +DefaultDependencies=no +Requires=qubes-mount-dirs.service home.mount usr-local.mount + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/usr/lib/qubes/init/bind-dirs.sh + +[Install] +WantedBy=multi-user.target diff --git a/vm-systemd/qubes-misc-post.service b/vm-systemd/qubes-misc-post.service index a0b259693..f24df2547 100644 --- a/vm-systemd/qubes-misc-post.service +++ b/vm-systemd/qubes-misc-post.service @@ -1,6 +1,6 @@ [Unit] Description=Qubes misc post-boot actions -After=network-pre.target qubes-mount-dirs.service qubes-network.service qubes-firewall.service +After=network-pre.target qubes-bind-dirs.service qubes-network.service qubes-firewall.service [Service] Type=oneshot diff --git a/vm-systemd/qubes-mount-dirs.service b/vm-systemd/qubes-mount-dirs.service index b6c92f158..98cf9e952 100644 --- a/vm-systemd/qubes-mount-dirs.service +++ b/vm-systemd/qubes-mount-dirs.service @@ -6,7 +6,7 @@ Description=Initialize and mount /rw and /home After=qubes-sysinit.service dev-xvdb.device After=systemd-fsck@dev-xvdb.service DefaultDependencies=no -Before=local-fs.target rw.mount home.mount qubes-gui-agent.service +Before=local-fs.target rw.mount usr-local.mount home.mount qubes-gui-agent.service [Service] Type=oneshot diff --git a/vm-systemd/usr-local.mount b/vm-systemd/usr-local.mount new file mode 100644 index 000000000..0a3481177 --- /dev/null +++ b/vm-systemd/usr-local.mount @@ -0,0 +1,5 @@ +[Mount] +What=/rw/usrlocal +Where=/usr/local +Type=none +Options=noauto,bind,defaults From 2ac91d7d0527755dff3fab8632151b38ff241feb Mon Sep 17 00:00:00 2001 From: Guillaume Chinal Date: Sun, 26 Jan 2025 16:16:12 +0100 Subject: [PATCH 03/18] custom-persist: mount binds configured in qubes-db Config is read from qubes database and every bind directory is mounted excepted /home and /usr/local which need to be handled differently --- init/functions | 4 ++++ vm-systemd/bind-dirs.sh | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/init/functions b/init/functions index 27f97e379..8b3f6bcb1 100644 --- a/init/functions +++ b/init/functions @@ -57,6 +57,10 @@ is_appvm() { [ "$(qubes_vm_type)" = "AppVM" ] } +is_custom_persist_enabled() { + [ -f "/var/run/qubes-service/custom-persist" ] +} + is_proxyvm() { [ "$(qubes_vm_type)" = "ProxyVM" ] } diff --git a/vm-systemd/bind-dirs.sh b/vm-systemd/bind-dirs.sh index 83014cd9c..266a5bf1f 100755 --- a/vm-systemd/bind-dirs.sh +++ b/vm-systemd/bind-dirs.sh @@ -135,6 +135,16 @@ for source_folder in "${sources[@]}"; do done done +# read binds in QubesDB if custom-persist feature is enabled +if is_custom_persist_enabled; then + while read -r qubes_persist_entry; do + [[ "$qubes_persist_entry" =~ =\ (.*)$ ]] || continue + target="${BASH_REMATCH[1]}" + [[ "$target" =~ ^(\/home|\/usr\/local)$ ]] && continue + binds+=( "$target" ) + done <<< "$(qubesdb-multiread /persist/)" +fi + main "$@" true "OK: END." From 91d312aef52c90dd6718294966a0d2708a49f2f6 Mon Sep 17 00:00:00 2001 From: Guillaume Chinal Date: Sun, 26 Jan 2025 16:27:20 +0100 Subject: [PATCH 04/18] custom-persist: disable /home and /usr/local mounts If not explicitly configured, /rw/home and /rw/usrlocal must not be bind mounted to /home and /usr/local. Instead, the original /home and /usr/local is mounted. SystemD drop-ins are used to override the resource to mount (What= option in unit) --- init/functions | 12 ++++++++++++ vm-systemd/mount-dirs.sh | 29 +++++++++++++++++++++++++++-- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/init/functions b/init/functions index 8b3f6bcb1..35b90d661 100644 --- a/init/functions +++ b/init/functions @@ -250,3 +250,15 @@ initialize_home() { for waitpid in $waitpids ; do wait "$waitpid" ; done ; waitpids= done } + +disable_persistent_home() { + echo "Disabling persistent /home" + mkdir /run/systemd/system/home.mount.d + echo -e '[Mount]\nWhat=/home' > /run/systemd/system/home.mount.d/30_qubes.conf +} + +disable_persistent_usrlocal() { + echo "Disabling persistent /usr/local" + mkdir /run/systemd/system/usr-local.mount.d + echo -e '[Mount]\nWhat=/usr/local' > /run/systemd/system/usr-local.mount.d/30_qubes.conf +} diff --git a/vm-systemd/mount-dirs.sh b/vm-systemd/mount-dirs.sh index 46631bbea..2fa3fa8e0 100755 --- a/vm-systemd/mount-dirs.sh +++ b/vm-systemd/mount-dirs.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # Source Qubes library. # shellcheck source=init/functions @@ -10,4 +10,29 @@ set -e if [ -e /dev/xvdb ] ; then mount /rw ; fi /usr/lib/qubes/init/setup-rw.sh -initialize_home "/rw/home" ifneeded +if is_custom_persist_enabled; then + mount_home=false + mount_usr_local=false + + while read -r qubes_persist_entry; do + [[ "$qubes_persist_entry" =~ \=\ /home$ ]] && mount_home=true + [[ "$qubes_persist_entry" =~ \=\ /usr/local$ ]] && mount_usr_local=true + done <<< "$(qubesdb-multiread /persist/)" +else + mount_home=true + mount_usr_local=true +fi + +if $mount_home; then + initialize_home "/rw/home" ifneeded +else + disable_persistent_home + initialize_home "/home" unconditionally +fi + +if ! $mount_usr_local; then + disable_persistent_usrlocal +fi + +systemctl daemon-reload + From ec868854c64c610b1494d48dfbbd0035c6ce7365 Mon Sep 17 00:00:00 2001 From: Guillaume Chinal Date: Sun, 26 Jan 2025 12:32:24 +0100 Subject: [PATCH 05/18] custom-persist: disable user firewall rules when custom persist is enabled --- qubesagent/firewall.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/qubesagent/firewall.py b/qubesagent/firewall.py index 7c3184532..d41e6078a 100755 --- a/qubesagent/firewall.py +++ b/qubesagent/firewall.py @@ -86,10 +86,15 @@ def get_connected_ips(self, family): return [] return ips.decode().split() + def is_custom_persist_enabled(self) -> bool: + """Check if the feature custom-persist is enabled on the current VM""" + return os.path.isfile('/var/run/qubes-service/custom-persist') + def run_firewall_dir(self): """Run scripts dir contents, before user script""" - script_dir_paths = ['/etc/qubes/qubes-firewall.d', - '/rw/config/qubes-firewall.d'] + script_dir_paths = ['/etc/qubes/qubes-firewall.d'] + if not self.is_custom_persist_enabled(): + script_dir_paths.append('/rw/config/qubes-firewall.d') for script_dir_path in script_dir_paths: if not os.path.isdir(script_dir_path): continue @@ -328,7 +333,8 @@ def main(self): self.terminate_requested = False self.init() self.run_firewall_dir() - self.run_user_script() + if not self.is_custom_persist_enabled(): + self.run_user_script() self.sd_notify('READY=1') self.qdb.watch('/qubes-firewall/') self.qdb.watch('/connected-ips') From 8042e29038f2b292e6371e6eae588b8b25656860 Mon Sep 17 00:00:00 2001 From: Guillaume Chinal Date: Sun, 26 Jan 2025 10:02:53 +0100 Subject: [PATCH 06/18] custom-persist: do not read user rc.local scripts when the feature is enabled --- vm-systemd/misc-post.sh | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/vm-systemd/misc-post.sh b/vm-systemd/misc-post.sh index 8439bb54c..60f7a7980 100755 --- a/vm-systemd/misc-post.sh +++ b/vm-systemd/misc-post.sh @@ -11,9 +11,11 @@ if [ -n "$(ls -A /usr/local/lib 2>/dev/null)" ] || \ ldconfig fi -for rc in /rw/config/rc.local.d/*.rc /rw/config/rc.local; do - [ -f "${rc}" ] || continue - [ -x "${rc}" ] || continue - "${rc}" -done +if ! is_custom_persist_enabled; then + for rc in /rw/config/rc.local.d/*.rc /rw/config/rc.local; do + [ -f "${rc}" ] || continue + [ -x "${rc}" ] || continue + "${rc}" + done +fi unset rc From e5209c88ed1651971d670366a1cdf9d74c59ef15 Mon Sep 17 00:00:00 2001 From: Guillaume Chinal Date: Sun, 26 Jan 2025 14:10:18 +0100 Subject: [PATCH 07/18] custom-persist: user suspend modules blacklist --- qubes-rpc/prepare-suspend | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qubes-rpc/prepare-suspend b/qubes-rpc/prepare-suspend index 949940ecf..9b4909b3f 100755 --- a/qubes-rpc/prepare-suspend +++ b/qubes-rpc/prepare-suspend @@ -16,7 +16,7 @@ MODULES_BLACKLIST="" if [ -r /etc/qubes-suspend-module-blacklist ]; then MODULES_BLACKLIST="$MODULES_BLACKLIST $(grep -v '^#' /etc/qubes-suspend-module-blacklist)" fi -if [ -r /rw/config/suspend-module-blacklist ]; then +if [ -r /rw/config/suspend-module-blacklist ] && ! is_custom_persist_enabled; then MODULES_BLACKLIST="$MODULES_BLACKLIST $(grep -v '^#' /rw/config/suspend-module-blacklist)" fi From e47e2853f21556d3cf9c418631b80933eb6882e8 Mon Sep 17 00:00:00 2001 From: Guillaume Chinal Date: Tue, 28 Jan 2025 20:24:17 +0100 Subject: [PATCH 08/18] custom-persist: init.d compatibility if the current VM is not under systemD we need to mount /home and /usr/local explicitly --- vm-systemd/mount-dirs.sh | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/vm-systemd/mount-dirs.sh b/vm-systemd/mount-dirs.sh index 2fa3fa8e0..04f9fafcf 100755 --- a/vm-systemd/mount-dirs.sh +++ b/vm-systemd/mount-dirs.sh @@ -25,14 +25,16 @@ fi if $mount_home; then initialize_home "/rw/home" ifneeded + under_systemd || mount -o noauto,bind,defaults,nosuid,nodev /rw/home /home else - disable_persistent_home + under_systemd && disable_persistent_home initialize_home "/home" unconditionally fi -if ! $mount_usr_local; then - disable_persistent_usrlocal +if $mount_usr_local; then + under_systemd || mount -o noauto,bind,defaults /rw/usrlocal /usr/local +else + under_systemd && disable_persistent_usrlocal fi -systemctl daemon-reload - +under_systemd && systemctl daemon-reload || exit 0 From bfe56a8223f65120ee5126be0e8fe0e1478c7994 Mon Sep 17 00:00:00 2001 From: Guillaume Chinal Date: Tue, 28 Jan 2025 22:34:29 +0100 Subject: [PATCH 09/18] fix under_systemd function on debian Read command name in /proc --- init/functions | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/init/functions b/init/functions index 35b90d661..ae9185a51 100644 --- a/init/functions +++ b/init/functions @@ -23,7 +23,9 @@ qsvc() { } under_systemd() { - pidof systemd >/dev/null 2>&1 + local init_command_name + read -r init_command_name < /proc/1/comm + [ "$init_command_name" = "systemd" ] } systemd_version_changed() { From e0003fc1bfb3ce435df3de02cd02175598980378 Mon Sep 17 00:00:00 2001 From: Guillaume Chinal Date: Wed, 19 Feb 2025 23:35:08 +0100 Subject: [PATCH 10/18] fix: bind-dirs should create files parent directories if they don't exist --- vm-systemd/bind-dirs.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/vm-systemd/bind-dirs.sh b/vm-systemd/bind-dirs.sh index 266a5bf1f..ad740bb9e 100755 --- a/vm-systemd/bind-dirs.sh +++ b/vm-systemd/bind-dirs.sh @@ -91,7 +91,11 @@ bind_dirs() { if [ ! -e "$fso_ro" ]; then ## Create empty file or directory if path exists in /rw to allow to bind mount none existing files/dirs. test -d "$fso_rw" && mkdir --parents "$fso_ro" - test -f "$fso_rw" && touch "$fso_ro" + if [ -f "$fso_rw" ]; then + parent_directory="$(dirname "$fso_ro")" + test -d "$parent_directory" || mkdir --parents "$parent_directory" + touch "$fso_ro" + fi fi else if [ -d "$fso_ro" ] || [ -f "$fso_ro" ]; then From c778254dc9d64225feabd04eb237b3c6e2306b11 Mon Sep 17 00:00:00 2001 From: Guillaume Chinal Date: Wed, 19 Feb 2025 23:40:46 +0100 Subject: [PATCH 11/18] custom-persist: files and directory auto-creation The support of metadata has been added to the custom-persist feature to allow automatic creation of files and directories declared through this feature. A type (file|dir), user, group and file mode must be specified before the path declaration. --- vm-systemd/bind-dirs.sh | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/vm-systemd/bind-dirs.sh b/vm-systemd/bind-dirs.sh index ad740bb9e..a6d094a7f 100755 --- a/vm-systemd/bind-dirs.sh +++ b/vm-systemd/bind-dirs.sh @@ -144,6 +144,39 @@ if is_custom_persist_enabled; then while read -r qubes_persist_entry; do [[ "$qubes_persist_entry" =~ =\ (.*)$ ]] || continue target="${BASH_REMATCH[1]}" + + # if the first char is not a slash, options should be extracted from + # the value + if [[ "$target" != /* ]]; then + resource_type="$(echo "$target" | cut -d':' -f1)" + owner="$(echo "$target" | cut -d':' -f2)" + group="$(echo "$target" | cut -d':' -f3)" + mode="$(echo "$target" | cut -d':' -f4)" + path="$(echo "$target" | cut -d':' -f5-)" + + if [ -z "$path" ] || [[ "$path" != /* ]]; then + echo "Skipping invalid custom-persist value '${target}'" >&2 + continue + fi + + # create resource if it does not exist + if ! [ -e "${path}" ] && ! [ -e "/rw/bind-dirs${path}" ]; then + if [ "$resource_type" = "file" ]; then + # for files, we need to create parent directories + parent_directory="$(dirname "$path")" + [ -d "$parent_directory" ] || mkdir -p "${parent_directory}" + touch "${path}" + elif [ "$resource_type" = "dir" ]; then + mkdir -p "${path}" + else + echo "Invalid entry ${target}, skipping" + continue + fi + chown "$owner":"$group" "${path}" + chmod "$mode" "${path}" + fi + target="$path" + fi [[ "$target" =~ ^(\/home|\/usr\/local)$ ]] && continue binds+=( "$target" ) done <<< "$(qubesdb-multiread /persist/)" From 4d12979628dd5ee2fa64168c5101c0a27c914a2f Mon Sep 17 00:00:00 2001 From: Guillaume Chinal Date: Sun, 23 Feb 2025 11:12:44 +0100 Subject: [PATCH 12/18] custom-persist: prefer objets pre-creation in /rw This commit changes the files and dirs pre-creation path. Instead of pre-create files and dirs directly on the RO file system and let bind_dirs() function populate /rw/bind-dirs, custom-persist creates objects in /rw/bind-dirs like a regular user would do. --- vm-systemd/bind-dirs.sh | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/vm-systemd/bind-dirs.sh b/vm-systemd/bind-dirs.sh index a6d094a7f..0b2bf89c2 100755 --- a/vm-systemd/bind-dirs.sh +++ b/vm-systemd/bind-dirs.sh @@ -159,21 +159,24 @@ if is_custom_persist_enabled; then continue fi + rw_path="/rw/bind-dirs${path}" # create resource if it does not exist - if ! [ -e "${path}" ] && ! [ -e "/rw/bind-dirs${path}" ]; then + if ! [ -e "${path}" ] && ! [ -e "$rw_path" ]; then if [ "$resource_type" = "file" ]; then # for files, we need to create parent directories - parent_directory="$(dirname "$path")" + parent_directory="$(dirname "$rw_path")" + echo "custom-persist: pre-creating file ${rw_path} with rights ${owner}:${group} ${mode}" [ -d "$parent_directory" ] || mkdir -p "${parent_directory}" - touch "${path}" + touch "${rw_path}" elif [ "$resource_type" = "dir" ]; then - mkdir -p "${path}" + echo "custom-persist: pre-creating directory ${rw_path} with rights ${owner}:${group} ${mode}" + mkdir -p "${rw_path}" else echo "Invalid entry ${target}, skipping" continue fi - chown "$owner":"$group" "${path}" - chmod "$mode" "${path}" + chown "$owner":"$group" "${rw_path}" + chmod "$mode" "${rw_path}" fi target="$path" fi From ff6742c2b5632d48b98cba3da8e1106d9292c2a1 Mon Sep 17 00:00:00 2001 From: Guillaume Chinal Date: Sun, 23 Feb 2025 11:27:07 +0100 Subject: [PATCH 13/18] custom-persist: handle mounts from /rw/home and /rw/usrlocal Custom persist disables /home and /usr/local persistence by default but a user may want to bind mount a file or a directory in one of those locations without mounting the whole directories. For example, we should be able to mount /home/user/.ssh/ but keep the rest of /home/user non-persistent. With this fix, bind dirs detects when an object is located under /home or /usr/local and will look in the associated /rw/home or /rw/usrlocal instead of /rw/bind-dirs. If needed, custom-persist will pre-create the objects in the same location. --- vm-systemd/bind-dirs.sh | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/vm-systemd/bind-dirs.sh b/vm-systemd/bind-dirs.sh index 0b2bf89c2..affa13780 100755 --- a/vm-systemd/bind-dirs.sh +++ b/vm-systemd/bind-dirs.sh @@ -29,6 +29,8 @@ shopt -s nullglob dotglob # shellcheck source=init/functions source /usr/lib/qubes/init/functions +readonly DEFAULT_RW_BIND_DIR="/rw/bind-dirs" + prerequisite() { if is_fully_persistent ; then echo "No TemplateBasedVM/DisposableVM detected. Exiting." @@ -37,7 +39,7 @@ prerequisite() { } init() { - [ -n "$rw_dest_dir" ] || rw_dest_dir="/rw/bind-dirs" + [ -n "$rw_dest_dir" ] || rw_dest_dir="$DEFAULT_RW_BIND_DIR" [ -n "$symlink_level_max" ] || symlink_level_max="10" mkdir --parents "$rw_dest_dir" } @@ -49,6 +51,21 @@ legacy() { true } +rw_from_ro() { + ro="$1" + # special cases for files/dirs in /home or /usr/local + if [[ "$ro" =~ ^/home/ ]]; then + # use /rw/home for /home/... binds + rw="/rw${ro}" + elif [[ "$ro" =~ ^/usr/local/ ]]; then + # use /rw/usrlocal for /usr/local/... binds + rw="/rw/usrlocal/$(echo "$ro" | cut -d/ -f4-)" + else + [ -z "$rw_dest_dir" ] && rw="${DEFAULT_RW_BIND_DIR}${ro}" || rw="${rw_dest_dir}${ro}" + fi + echo "$rw" +} + bind_dirs() { ## legend ## fso: file system object @@ -77,7 +94,7 @@ bind_dirs() { done true "fso_ro: $fso_ro" - fso_rw="${rw_dest_dir}${fso_ro}" + fso_rw="$(rw_from_ro "$fso_ro")" # Make sure fso_ro is not mounted. umount "$fso_ro" 2> /dev/null || true @@ -159,7 +176,7 @@ if is_custom_persist_enabled; then continue fi - rw_path="/rw/bind-dirs${path}" + rw_path="$(rw_from_ro "${path}")" # create resource if it does not exist if ! [ -e "${path}" ] && ! [ -e "$rw_path" ]; then if [ "$resource_type" = "file" ]; then From 385f3fecd8c1657cf175f714babdd27a06aedad8 Mon Sep 17 00:00:00 2001 From: Guillaume Chinal Date: Sun, 23 Feb 2025 16:38:42 +0100 Subject: [PATCH 14/18] bind-dirs: fix /rw/home and /rw/usrlocal initialization from template files --- vm-systemd/bind-dirs.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/vm-systemd/bind-dirs.sh b/vm-systemd/bind-dirs.sh index affa13780..462499e74 100755 --- a/vm-systemd/bind-dirs.sh +++ b/vm-systemd/bind-dirs.sh @@ -117,8 +117,10 @@ bind_dirs() { else if [ -d "$fso_ro" ] || [ -f "$fso_ro" ]; then ## Initially copy over data directories to /rw if rw directory does not exist. - echo "Initializing $rw_dest_dir with files from $fso_ro" >&2 - cp --archive --recursive --parents "$fso_ro" "$rw_dest_dir" + echo "Initializing $fso_rw with files from $fso_ro" >&2 + parent_directory="$(dirname "$fso_rw")" + test -d "$parent_directory" || mkdir --parents "$parent_directory" + cp --archive --recursive "$fso_ro" "$fso_rw" else echo "$fso_ro is neither a directory nor a file and the path does not exist below /rw, skipping." continue From 55d297b08ff45bff0a848bafd368696f0f15e45d Mon Sep 17 00:00:00 2001 From: Guillaume Chinal Date: Thu, 27 Feb 2025 21:05:42 +0100 Subject: [PATCH 15/18] custom-persist: pre-create parents with correct ownership When using custom-persist to pre-create the resource before bind mounting it, we might have to create its parents too. That was done using mkdir --parents that was causing parents to be created with root:root ownership which can leads to errors if, for example, a user wants to bind mount a directory inside its home dir. With this fix, parents are created with the same ownership as the resource. --- vm-systemd/bind-dirs.sh | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/vm-systemd/bind-dirs.sh b/vm-systemd/bind-dirs.sh index 462499e74..aa75726d0 100755 --- a/vm-systemd/bind-dirs.sh +++ b/vm-systemd/bind-dirs.sh @@ -133,6 +133,19 @@ bind_dirs() { done } +mk_parent_dirs() { + local target="$1" + local owner="$2" + local group="$3" + local depth="$4" + [[ "$depth" -gt 100 ]] && echo "Maximum recursion depth reached" >&2 && return 1 + [ -e "$target" ] && return 0 + mk_parent_dirs "$(dirname "$target")" "$owner" "$group" "$(( depth + 1 ))" || return 1 + mkdir "$target" || return 1 + chown "$owner":"$group" "$target" || return 1 + return 0 +} + main() { prerequisite "$@" init "$@" @@ -185,11 +198,19 @@ if is_custom_persist_enabled; then # for files, we need to create parent directories parent_directory="$(dirname "$rw_path")" echo "custom-persist: pre-creating file ${rw_path} with rights ${owner}:${group} ${mode}" - [ -d "$parent_directory" ] || mkdir -p "${parent_directory}" + if [ ! -d "$parent_directory" ]; then + if ! mk_parent_dirs "$parent_directory" "$owner" "$group"; then + echo "Unable to create ${rw_path} parent dirs, skipping" + continue + fi + fi touch "${rw_path}" elif [ "$resource_type" = "dir" ]; then echo "custom-persist: pre-creating directory ${rw_path} with rights ${owner}:${group} ${mode}" - mkdir -p "${rw_path}" + if ! mk_parent_dirs "$rw_path" "$owner" "$group"; then + echo "Unable to create ${rw_path} parent dirs, skipping" + continue + fi else echo "Invalid entry ${target}, skipping" continue From 0a8274bd49ec082759aded1f981fb403c0d42d0c Mon Sep 17 00:00:00 2001 From: Guillaume Chinal Date: Thu, 27 Feb 2025 21:26:32 +0100 Subject: [PATCH 16/18] custom-persist: prevent mount units from starting instead of bind mounting When disabling persistent /home or /usr/local, custom-persist was using a systemd drop-in to override the What= option and set it to the same value as the Where= one. This bind mount is unnecessary and was causing trouble when bind mounting other resources in /home or /usr/local. Instead, a ConditionPathExists= option is added to control whether this mount happens. --- init/functions | 6 ++---- vm-systemd/home.mount | 3 +++ vm-systemd/usr-local.mount | 3 +++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/init/functions b/init/functions index ae9185a51..f4c2d4d3b 100644 --- a/init/functions +++ b/init/functions @@ -255,12 +255,10 @@ initialize_home() { disable_persistent_home() { echo "Disabling persistent /home" - mkdir /run/systemd/system/home.mount.d - echo -e '[Mount]\nWhat=/home' > /run/systemd/system/home.mount.d/30_qubes.conf + touch /var/run/qubes/disable_persistent_home_dir } disable_persistent_usrlocal() { echo "Disabling persistent /usr/local" - mkdir /run/systemd/system/usr-local.mount.d - echo -e '[Mount]\nWhat=/usr/local' > /run/systemd/system/usr-local.mount.d/30_qubes.conf + touch /var/run/qubes/disable_persistent_usrlocal_dir } diff --git a/vm-systemd/home.mount b/vm-systemd/home.mount index 683b791a0..07648b6c9 100644 --- a/vm-systemd/home.mount +++ b/vm-systemd/home.mount @@ -1,3 +1,6 @@ +[Unit] +ConditionPathExists=!/var/run/qubes/disable_persistent_home_dir + [Mount] What=/rw/home Where=/home diff --git a/vm-systemd/usr-local.mount b/vm-systemd/usr-local.mount index 0a3481177..d305d71c7 100644 --- a/vm-systemd/usr-local.mount +++ b/vm-systemd/usr-local.mount @@ -1,3 +1,6 @@ +[Unit] +ConditionPathExists=!/var/run/qubes/disable_persistent_usrlocal_dir + [Mount] What=/rw/usrlocal Where=/usr/local From f18831c5007190e164cece8cd1cfb9416946348b Mon Sep 17 00:00:00 2001 From: Guillaume Chinal Date: Thu, 27 Feb 2025 21:35:15 +0100 Subject: [PATCH 17/18] bind-dirs: add x-gvfs-hide mount option to bind dirs This allows to hide mountpoints from Thunar sidebar (happens when bind mounting a file or dir in $HOME). --- vm-systemd/bind-dirs.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm-systemd/bind-dirs.sh b/vm-systemd/bind-dirs.sh index aa75726d0..41c8ab9db 100755 --- a/vm-systemd/bind-dirs.sh +++ b/vm-systemd/bind-dirs.sh @@ -129,7 +129,7 @@ bind_dirs() { # Bind the fso. echo "Bind mounting $fso_rw onto $fso_ro" >&2 - mount --bind "$fso_rw" "$fso_ro" + mount --bind -o x-gvfs-hide "$fso_rw" "$fso_ro" done } From cc84ec631079c1224cd84a37eceb0fc52aefe306 Mon Sep 17 00:00:00 2001 From: Guillaume Chinal Date: Thu, 27 Feb 2025 23:25:24 +0100 Subject: [PATCH 18/18] bind-dirs: fix permissions on $fso_ro --- vm-systemd/bind-dirs.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/vm-systemd/bind-dirs.sh b/vm-systemd/bind-dirs.sh index 41c8ab9db..84325cd8f 100755 --- a/vm-systemd/bind-dirs.sh +++ b/vm-systemd/bind-dirs.sh @@ -107,10 +107,12 @@ bind_dirs() { if [ -d "$fso_rw" ] || [ -f "$fso_rw" ]; then if [ ! -e "$fso_ro" ]; then ## Create empty file or directory if path exists in /rw to allow to bind mount none existing files/dirs. - test -d "$fso_rw" && mkdir --parents "$fso_ro" + # shellcheck disable=SC2046 + test -d "$fso_rw" && mk_parent_dirs "$fso_ro" $(stat --printf "%U %G" "$fso_rw") if [ -f "$fso_rw" ]; then parent_directory="$(dirname "$fso_ro")" - test -d "$parent_directory" || mkdir --parents "$parent_directory" + # shellcheck disable=SC2046 + test -d "$parent_directory" || mk_parent_dirs "$parent_directory" $(stat --printf "%U %G" "$fso_rw") touch "$fso_ro" fi fi