From da4772c4e54467920d642e2a792c5d16d3b7bf33 Mon Sep 17 00:00:00 2001 From: Alessandro Astone Date: Thu, 29 Dec 2022 02:18:55 +0100 Subject: [PATCH 01/16] tools: Remove umask 0 Try to remove old permissions on upgrade --- tools/__init__.py | 1 - tools/actions/upgrader.py | 14 ++++++++++++++ tools/helpers/logging.py | 4 ++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/tools/__init__.py b/tools/__init__.py index 9a4701b..d317414 100644 --- a/tools/__init__.py +++ b/tools/__init__.py @@ -24,7 +24,6 @@ def main(): # Wrap everything to display nice error messages args = None try: - os.umask(0o000) # Parse arguments, set up logging args = helpers.arguments() args.cache = {} diff --git a/tools/actions/upgrader.py b/tools/actions/upgrader.py index 2181971..605de44 100644 --- a/tools/actions/upgrader.py +++ b/tools/actions/upgrader.py @@ -15,8 +15,22 @@ def get_config(args): args.vendor_ota = cfg["waydroid"]["vendor_ota"] args.session = None +def migration(args): + def versiontuple(v): + return tuple(map(int, (v.split(".")))) + + try: + old_ver = tools.helpers.props.file_get(args, args.work + "/waydroid_base.prop", "waydroid.tools_version") + if versiontuple(old_ver) <= versiontuple("1.3.4"): + chmod_paths = ["cache_http", "host-permissions", "lxc", "images", "waydroid_base.prop", "waydroid.prop", "waydroid.cfg"] + tools.helpers.run.user(args, ["chmod", "-R", "g-w,o-w"] + [os.path.join(args.work, f) for f in chmod_paths], check=False) + tools.helpers.run.user(args, ["chmod", "g-w,o-w", args.work], check=False) + except: + pass + def upgrade(args): get_config(args) + migration(args) status = "STOPPED" if os.path.exists(tools.config.defaults["lxc"] + "/waydroid"): status = helpers.lxc.status(args) diff --git a/tools/helpers/logging.py b/tools/helpers/logging.py index 094bf82..768f2bf 100644 --- a/tools/helpers/logging.py +++ b/tools/helpers/logging.py @@ -69,6 +69,10 @@ def init(args): dir = os.path.dirname(args.log) if os.path.exists(dir): setattr(args, "logfd", open(args.log, "a+")) + try: + os.chmod(args.log, 0o666) + except PermissionError: + pass else: setattr(args, "logfd", open(os.devnull, "a+")) if args.action != "init": -- 2.47.3 From 3b3081af3da09ae93449f34cb16556ed84caad07 Mon Sep 17 00:00:00 2001 From: Alessandro Astone Date: Mon, 2 Jan 2023 19:16:27 +0100 Subject: [PATCH 02/16] initializer: Require admin authentication for remote initialization with custom channels --- Makefile | 5 +++- dbus/id.waydro.Container.policy | 18 ++++++++++++++ debian/control | 3 ++- tools/actions/initializer.py | 44 +++++++++++++++++++++++++++------ 4 files changed, 60 insertions(+), 10 deletions(-) create mode 100644 dbus/id.waydro.Container.policy diff --git a/Makefile b/Makefile index f838328..67e74fa 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,7 @@ APPS_DIR := $(PREFIX)/share/applications METAINFO_DIR := $(PREFIX)/share/metainfo SYSD_DIR := $(PREFIX)/lib/systemd/system DBUS_DIR := $(PREFIX)/share/dbus-1 +POLKIT_DIR := $(PREFIX)/share/polkit-1 APPARMOR_DIR := /etc/apparmor.d INSTALL_WAYDROID_DIR := $(DESTDIR)$(WAYDROID_DIR) @@ -18,18 +19,20 @@ INSTALL_APPS_DIR := $(DESTDIR)$(APPS_DIR) INSTALL_METAINFO_DIR := $(DESTDIR)$(METAINFO_DIR) INSTALL_SYSD_DIR := $(DESTDIR)$(SYSD_DIR) INSTALL_DBUS_DIR := $(DESTDIR)$(DBUS_DIR) +INSTALL_POLKIT_DIR := $(DESTDIR)$(POLKIT_DIR) INSTALL_APPARMOR_DIR := $(DESTDIR)$(APPARMOR_DIR) build: @echo "Nothing to build, run 'make install' to copy the files!" install: - install -d $(INSTALL_WAYDROID_DIR) $(INSTALL_BIN_DIR) $(INSTALL_APPS_DIR) $(INSTALL_METAINFO_DIR) $(INSTALL_DBUS_DIR)/system.d + install -d $(INSTALL_WAYDROID_DIR) $(INSTALL_BIN_DIR) $(INSTALL_APPS_DIR) $(INSTALL_METAINFO_DIR) $(INSTALL_DBUS_DIR)/system.d $(INSTALL_POLKIT_DIR)/actions cp -a data tools waydroid.py $(INSTALL_WAYDROID_DIR) ln -sf $(WAYDROID_DIR)/waydroid.py $(INSTALL_BIN_DIR)/waydroid mv $(INSTALL_WAYDROID_DIR)/data/*.desktop $(INSTALL_APPS_DIR) mv $(INSTALL_WAYDROID_DIR)/data/*.metainfo.xml $(INSTALL_METAINFO_DIR) cp dbus/id.waydro.Container.conf $(INSTALL_DBUS_DIR)/system.d/ + cp dbus/id.waydro.Container.policy $(INSTALL_POLKIT_DIR)/actions/ if [ $(USE_DBUS_ACTIVATION) = 1 ]; then \ install -d $(INSTALL_DBUS_DIR)/system-services; \ cp dbus/id.waydro.Container.service $(INSTALL_DBUS_DIR)/system-services/; \ diff --git a/dbus/id.waydro.Container.policy b/dbus/id.waydro.Container.policy new file mode 100644 index 0000000..52cb807 --- /dev/null +++ b/dbus/id.waydro.Container.policy @@ -0,0 +1,18 @@ + + + + + Waydroid + https://waydro.id + + Waydroid Initialization + Initialize Waydroid with user-provided OTA channels. Do you trust the source? + + auth_admin + auth_admin + auth_admin_keep + + + diff --git a/debian/control b/debian/control index 89a4cd4..51e416b 100644 --- a/debian/control +++ b/debian/control @@ -19,7 +19,8 @@ Depends: ${misc:Depends}, python3-gi, gir1.2-gtk-3.0, python3-dbus, - dbus + dbus, + policykit-1 Description: Android™ application support waydroid allows running a separate Android™ environment confined to a LXC container. diff --git a/tools/actions/initializer.py b/tools/actions/initializer.py index 5238b3c..7fec732 100644 --- a/tools/actions/initializer.py +++ b/tools/actions/initializer.py @@ -152,15 +152,38 @@ class DbusInitializer(dbus.service.Object): self.looper = looper dbus.service.Object.__init__(self, bus, object_path) - @dbus.service.method("id.waydro.Initializer", in_signature='a{ss}', out_signature='') - def Init(self, params): - threading.Thread(target=remote_init_server, args=(self.args, params)).start() + @dbus.service.method("id.waydro.Initializer", in_signature='a{ss}', out_signature='', sender_keyword="sender", connection_keyword="conn") + def Init(self, params, sender=None, conn=None): + channels_cfg = tools.config.load_channels() + no_auth = params["system_channel"] == channels_cfg["channels"]["system_channel"] and \ + params["vendor_channel"] == channels_cfg["channels"]["vendor_channel"] + if no_auth or ensure_polkit_auth(sender, conn, "id.waydro.Initializer.Init"): + threading.Thread(target=remote_init_server, args=(self.args, params)).start() + else: + raise PermissionError("Polkit: Authentication failed") @dbus.service.method("id.waydro.Initializer", in_signature='', out_signature='') def Done(self): if is_initialized(self.args): self.looper.quit() +def ensure_polkit_auth(sender, conn, privilege): + dbus_info = dbus.Interface(conn.get_object("org.freedesktop.DBus", "/org/freedesktop/DBus/Bus", False), "org.freedesktop.DBus") + pid = dbus_info.GetConnectionUnixProcessID(sender) + polkit = dbus.Interface(dbus.SystemBus().get_object("org.freedesktop.PolicyKit1", "/org/freedesktop/PolicyKit1/Authority", False), "org.freedesktop.PolicyKit1.Authority") + try: + (is_auth, _, _) = polkit.CheckAuthorization( + ("unix-process", { + "pid": dbus.UInt32(pid, variant_level=1), + "start-time": dbus.UInt64(0, variant_level=1)}), + privilege, {"AllowUserInteraction": "true"}, + dbus.UInt32(1), + "", + timeout=300) + return is_auth + except dbus.DBusException: + raise PermissionError("Polkit: Authentication timed out") + def background_remote_init_process(args): with helpers.ipc.open_channel("remote_init_output", "wb") as channel_out: class StdoutRedirect(logging.StreamHandler): @@ -249,6 +272,8 @@ def remote_init_client(args): class WaydroidInitWindow(Gtk.Window): def __init__(self): super().__init__(title="Initialize Waydroid") + channels_cfg = tools.config.load_channels() + self.set_default_size(600, 250) self.set_icon_from_file(tools.config.tools_src + "/data/AppIcon.png") @@ -259,14 +284,14 @@ def remote_init_client(args): sysOtaLabel = Gtk.Label("System OTA") sysOtaEntry = Gtk.Entry() - sysOtaEntry.set_text(tools.config.channels_defaults["system_channel"]) + sysOtaEntry.set_text(channels_cfg["channels"]["system_channel"]) grid.attach(sysOtaLabel, 0, 0, 1, 1) grid.attach_next_to(sysOtaEntry ,sysOtaLabel, Gtk.PositionType.RIGHT, 2, 1) self.sysOta = sysOtaEntry.get_buffer() vndOtaLabel = Gtk.Label("Vendor OTA") vndOtaEntry = Gtk.Entry() - vndOtaEntry.set_text(tools.config.channels_defaults["vendor_channel"]) + vndOtaEntry.set_text(channels_cfg["channels"]["vendor_channel"]) grid.attach(vndOtaLabel, 0, 1, 1, 1) grid.attach_next_to(vndOtaEntry, vndOtaLabel, Gtk.PositionType.RIGHT, 2, 1) self.vndOta = vndOtaEntry.get_buffer() @@ -342,9 +367,12 @@ def remote_init_client(args): "vendor_channel": self.vndOta.get_text(), "system_type": self.sysType.get_active_text() } - tools.helpers.ipc.DBusContainerService("/Initializer", "id.waydro.Initializer").Init(params) - except: - draw("The waydroid container service is not listening\n") + tools.helpers.ipc.DBusContainerService("/Initializer", "id.waydro.Initializer").Init(params, timeout=310) + except dbus.DBusException as e: + if e.get_dbus_name() == "org.freedesktop.DBus.Python.PermissionError": + draw(e.get_dbus_message().splitlines()[-1] + "\n") + else: + draw("The waydroid container service is not listening\n") GLib.idle_add(self.downloadBtn.set_sensitive, True) return -- 2.47.3 From fb92d3a016a956e8520e938b59f55bd0fe9c2deb Mon Sep 17 00:00:00 2001 From: Alessandro Astone Date: Wed, 4 Jan 2023 15:03:09 +0100 Subject: [PATCH 03/16] lxc: Workaround against lxc changing logfile permissions Running lxc-start changes the permissions of stdout/stderr to 700. The previous workaround of changing the permissions back after lxc-status only worked because of the lxc-status loop in container_manager.start Make it more generic by applying it to every caller of helpers.lxc.start See: https://github.com/lxc/lxc/blob/6564e6ccb22e6e3c6cf36e6ae3cb5d5f73122486/src/lxc/utils.c#L1859 --- tools/actions/container_manager.py | 11 ----------- tools/helpers/lxc.py | 17 ++++++++++++++++- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/tools/actions/container_manager.py b/tools/actions/container_manager.py index e36cfbf..29ba552 100644 --- a/tools/actions/container_manager.py +++ b/tools/actions/container_manager.py @@ -165,17 +165,6 @@ def do_start(args, session): set_permissions(args) helpers.lxc.start(args) - lxc_status = helpers.lxc.status(args) - timeout = 10 - while lxc_status != "RUNNING" and timeout > 0: - lxc_status = helpers.lxc.status(args) - logging.info( - "waiting {} seconds for container to start...".format(timeout)) - timeout = timeout - 1 - time.sleep(1) - if lxc_status != "RUNNING": - raise OSError("container failed to start") - services.hardware_manager.start(args) def stop(args): diff --git a/tools/helpers/lxc.py b/tools/helpers/lxc.py index cac4bb5..0808d98 100644 --- a/tools/helpers/lxc.py +++ b/tools/helpers/lxc.py @@ -6,6 +6,7 @@ import re import logging import glob import shutil +import time import platform import gbinder import tools.config @@ -344,13 +345,27 @@ def setup_host_perms(args): def status(args): command = ["lxc-info", "-P", tools.config.defaults["lxc"], "-n", "waydroid", "-sH"] out = subprocess.run(command, stdout=subprocess.PIPE).stdout.decode('utf-8').strip() - os.chmod(args.log, 0o666) return out +def wait_for_running(args): + lxc_status = status(args) + timeout = 10 + while lxc_status != "RUNNING" and timeout > 0: + lxc_status = status(args) + logging.info( + "waiting {} seconds for container to start...".format(timeout)) + timeout = timeout - 1 + time.sleep(1) + if lxc_status != "RUNNING": + raise OSError("container failed to start") + def start(args): command = ["lxc-start", "-P", tools.config.defaults["lxc"], "-F", "-n", "waydroid", "--", "/init"] tools.helpers.run.user(args, command, output="background") + wait_for_running(args) + # Workaround lxc-start changing stdout/stderr permissions to 700 + os.chmod(args.log, 0o666) def stop(args): command = ["lxc-stop", "-P", -- 2.47.3 From 1780c9488ccce1737928c68e86896455f2732d02 Mon Sep 17 00:00:00 2001 From: Alessandro Astone Date: Thu, 5 Jan 2023 20:00:05 +0100 Subject: [PATCH 04/16] tools: Allow prop commands while frozen --- tools/__init__.py | 6 ++---- tools/actions/__init__.py | 1 + tools/actions/prop.py | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 tools/actions/prop.py diff --git a/tools/__init__.py b/tools/__init__.py index d317414..819d54a 100644 --- a/tools/__init__.py +++ b/tools/__init__.py @@ -110,11 +110,9 @@ def main(): "Run waydroid {} -h for usage information.".format(args.action)) elif args.action == "prop": if args.subaction == "get": - ret = helpers.props.get(args, args.key) - if ret: - print(ret) + actions.prop.get(args) elif args.subaction == "set": - helpers.props.set(args, args.key, args.value) + actions.prop.set(args) else: logging.info( "Run waydroid {} -h for usage information.".format(args.action)) diff --git a/tools/actions/__init__.py b/tools/actions/__init__.py index 0416733..8a134c4 100644 --- a/tools/actions/__init__.py +++ b/tools/actions/__init__.py @@ -6,3 +6,4 @@ from tools.actions.session_manager import start, stop from tools.actions.container_manager import start, stop, freeze, unfreeze from tools.actions.app_manager import install, remove, launch, list from tools.actions.status import print_status +from tools.actions.prop import get, set diff --git a/tools/actions/prop.py b/tools/actions/prop.py new file mode 100644 index 0000000..2915df6 --- /dev/null +++ b/tools/actions/prop.py @@ -0,0 +1,38 @@ +import logging +import tools.helpers.props +import tools.helpers.ipc +import dbus + +def get(args): + try: + tools.helpers.ipc.DBusSessionService() + + cm = tools.helpers.ipc.DBusContainerService() + session = cm.GetSession() + if session["state"] == "FROZEN": + cm.Unfreeze() + + ret = tools.helpers.props.get(args, args.key) + if ret: + print(ret) + + if session["state"] == "FROZEN": + cm.Freeze() + except (dbus.DBusException, KeyError): + logging.error("WayDroid session is stopped") + +def set(args): + try: + tools.helpers.ipc.DBusSessionService() + + cm = tools.helpers.ipc.DBusContainerService() + session = cm.GetSession() + if session["state"] == "FROZEN": + cm.Unfreeze() + + helpers.props.set(args, args.key, args.value) + + if session["state"] == "FROZEN": + cm.Freeze() + except (dbus.DBusException, KeyError): + logging.error("WayDroid session is stopped") -- 2.47.3 From 094a4d970ccefa2e6409c8147c1edbdaaa74df0f Mon Sep 17 00:00:00 2001 From: Zhao Zuohong <1040110848@qq.com> Date: Fri, 6 Jan 2023 18:34:50 +0800 Subject: [PATCH 05/16] Fix prop set command --- tools/actions/prop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/actions/prop.py b/tools/actions/prop.py index 2915df6..09e66a5 100644 --- a/tools/actions/prop.py +++ b/tools/actions/prop.py @@ -30,7 +30,7 @@ def set(args): if session["state"] == "FROZEN": cm.Unfreeze() - helpers.props.set(args, args.key, args.value) + tools.helpers.props.set(args, args.key, args.value) if session["state"] == "FROZEN": cm.Freeze() -- 2.47.3 From 27b99dd5c8cc79f792bf3c7f71d0088600c56546 Mon Sep 17 00:00:00 2001 From: Alessandro Astone Date: Thu, 10 Nov 2022 19:17:08 +0100 Subject: [PATCH 06/16] gpu: Also mount card node Necessary for minigbm_gbm_mesa --- tools/helpers/gpu.py | 14 ++++++++++---- tools/helpers/lxc.py | 6 ++++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/tools/helpers/gpu.py b/tools/helpers/gpu.py index cf88540..eac3184 100644 --- a/tools/helpers/gpu.py +++ b/tools/helpers/gpu.py @@ -7,12 +7,18 @@ unsupported = ["nvidia", "nouveau"] def getKernelDriver(args, dev): return tools.helpers.props.file_get(args, "/sys/class/drm/{}/device/uevent".format(dev), "DRIVER") +def getCardFromRender(args, dev): + try: + return "/dev/dri/" + os.path.basename(glob.glob("/sys/class/drm/{}/device/drm/card*".format(dev))[0]) + except IndexError: + return "" + def getDriNode(args): for node in glob.glob("/dev/dri/renderD*"): - dev = os.path.basename(node) - if getKernelDriver(args, dev) not in unsupported: - return node - return "" + renderDev = os.path.basename(node) + if getKernelDriver(args, renderDev) not in unsupported: + return node, getCardFromRender(args, renderDev) + return "", "" def getVulkanDriver(args, dev): mapping = { diff --git a/tools/helpers/lxc.py b/tools/helpers/lxc.py index 0808d98..b7bbf66 100644 --- a/tools/helpers/lxc.py +++ b/tools/helpers/lxc.py @@ -53,7 +53,9 @@ def generate_nodes_lxc_config(args): make_entry("/dev/pvr_sync") make_entry("/dev/pmsg0") make_entry("/dev/dxg") - make_entry(tools.helpers.gpu.getDriNode(args), "dev/dri/renderD128") + render, card = tools.helpers.gpu.getDriNode(args) + make_entry(render, "dev/dri/renderD128") + make_entry(card, "dev/dri/card0") for n in glob.glob("/dev/fb*"): make_entry(n) @@ -217,7 +219,7 @@ def make_base_props(args): props.append("sys.use_memfd=true") egl = tools.helpers.props.host_get(args, "ro.hardware.egl") - dri = tools.helpers.gpu.getDriNode(args) + dri, _ = tools.helpers.gpu.getDriNode(args) gralloc = find_hal("gralloc") if not gralloc: -- 2.47.3 From 4660f35884893fd69a0b1379c0af687d1e9eda66 Mon Sep 17 00:00:00 2001 From: Sebastian Krzyszkowiak Date: Wed, 2 Nov 2022 19:08:07 +0100 Subject: [PATCH 07/16] helpers/mount: Check for failed umounts only umounting the whole list Otherwise we raise bogus RuntimeErrors on paths with multiple mount points, which may be desired - for example when using overlayfs. --- tools/helpers/mount.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/helpers/mount.py b/tools/helpers/mount.py index fdf6477..fce1035 100644 --- a/tools/helpers/mount.py +++ b/tools/helpers/mount.py @@ -102,8 +102,10 @@ def umount_all(args, folder): """ Umount all folders, that are mounted inside a given folder. """ - for mountpoint in umount_all_list(folder): + all_list = umount_all_list(folder) + for mountpoint in all_list: tools.helpers.run.user(args, ["umount", mountpoint]) + for mountpoint in all_list: if ismount(mountpoint): raise RuntimeError("Failed to umount: " + mountpoint) -- 2.47.3 From e620e51bbb86b3fc2776e4d041df928391f944ab Mon Sep 17 00:00:00 2001 From: Sebastian Krzyszkowiak Date: Thu, 10 Nov 2022 21:39:21 +0100 Subject: [PATCH 08/16] helpers/mount: Allow to specify explicit mount type and options Also, set mount option to ro right away instead of relying on remount. --- tools/helpers/mount.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/tools/helpers/mount.py b/tools/helpers/mount.py index fce1035..1d61037 100644 --- a/tools/helpers/mount.py +++ b/tools/helpers/mount.py @@ -109,7 +109,7 @@ def umount_all(args, folder): if ismount(mountpoint): raise RuntimeError("Failed to umount: " + mountpoint) -def mount(args, source, destination, create_folders=True, umount=False, readonly=True): +def mount(args, source, destination, create_folders=True, umount=False, readonly=True, mount_type=None, options=None): """ Mount and create necessary directory structure. :param umount: when destination is already a mount point, umount it first. @@ -129,10 +129,19 @@ def mount(args, source, destination, create_folders=True, umount=False, readonly raise RuntimeError("Mount failed, folder does not exist: " + destination) - # Actually mount the folder - tools.helpers.run.user(args, ["mount", source, destination]) + extra_args = [] + opt_args = [] + if mount_type: + extra_args.extend(["-t", mount_type]) if readonly: - tools.helpers.run.user(args, ["mount", "-o", "remount,ro", source, destination]) + opt_args.append("ro") + if options: + opt_args.extend(options) + if opt_args: + extra_args.extend(["-o", ",".join(opt_args)]) + + # Actually mount the folder + tools.helpers.run.user(args, ["mount", *extra_args, source, destination]) # Verify, that it has worked if not ismount(destination): -- 2.47.3 From e2f4f942f28d3a7132cd29d047a0a243c867a089 Mon Sep 17 00:00:00 2001 From: Sebastian Krzyszkowiak Date: Thu, 10 Nov 2022 21:55:44 +0100 Subject: [PATCH 09/16] helpers/mount: Add an option to mount even if mount point already exists This is needed for mounting overlays on top of existing mount points. --- tools/helpers/mount.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tools/helpers/mount.py b/tools/helpers/mount.py index 1d61037..2660952 100644 --- a/tools/helpers/mount.py +++ b/tools/helpers/mount.py @@ -109,17 +109,20 @@ def umount_all(args, folder): if ismount(mountpoint): raise RuntimeError("Failed to umount: " + mountpoint) -def mount(args, source, destination, create_folders=True, umount=False, readonly=True, mount_type=None, options=None): +def mount(args, source, destination, create_folders=True, umount=False, + readonly=True, mount_type=None, options=None, force=True): """ Mount and create necessary directory structure. :param umount: when destination is already a mount point, umount it first. + :param force: attempt mounting even if the mount point already exists. """ # Check/umount destination if ismount(destination): if umount: umount_all(args, destination) else: - return + if not force: + return # Check/create folders if not os.path.exists(destination): -- 2.47.3 From 40c6aa7ac2bcf00f42efd43be4c0a06dec1ed0a6 Mon Sep 17 00:00:00 2001 From: Sebastian Krzyszkowiak Date: Thu, 10 Nov 2022 22:00:34 +0100 Subject: [PATCH 10/16] Mount overlays on top of image mount points This allows the user to make modifications to the images that may persist between image upgrades. For both the system and vendor image there's a set of two overlays. One, specified in config as "overlay", is a read-only persistent overlay meant for stuff like installing privileged apps that should persist. Second one, specified as "overlay-rw", is a read-write overlay that stores the changes made by the user in case they remount the mount point in read-write mode. This one is meant to be removed when performing image upgrade to not carry on potentially incompatible changes between images. --- tools/actions/initializer.py | 7 +++++++ tools/config/__init__.py | 3 +++ tools/helpers/images.py | 11 +++++++++++ tools/helpers/mount.py | 25 +++++++++++++++++++++++++ 4 files changed, 46 insertions(+) diff --git a/tools/actions/initializer.py b/tools/actions/initializer.py index 7fec732..31b4fc9 100644 --- a/tools/actions/initializer.py +++ b/tools/actions/initializer.py @@ -119,6 +119,13 @@ def init(args): helpers.images.get(args) if not os.path.isdir(tools.config.defaults["rootfs"]): os.mkdir(tools.config.defaults["rootfs"]) + if not os.path.isdir(tools.config.defaults["overlay"]): + os.mkdir(tools.config.defaults["overlay"]) + os.mkdir(tools.config.defaults["overlay"]+"/vendor") + if not os.path.isdir(tools.config.defaults["overlay_rw"]): + os.mkdir(tools.config.defaults["overlay_rw"]) + os.mkdir(tools.config.defaults["overlay_rw"]+"/system") + os.mkdir(tools.config.defaults["overlay_rw"]+"/vendor") helpers.lxc.setup_host_perms(args) helpers.lxc.set_lxc_config(args) helpers.lxc.make_base_props(args) diff --git a/tools/config/__init__.py b/tools/config/__init__.py index 0ed3d46..1edbf17 100644 --- a/tools/config/__init__.py +++ b/tools/config/__init__.py @@ -40,6 +40,9 @@ defaults = { } defaults["images_path"] = defaults["work"] + "/images" defaults["rootfs"] = defaults["work"] + "/rootfs" +defaults["overlay"] = defaults["work"] + "/overlay" +defaults["overlay_rw"] = defaults["work"] + "/overlay_rw" +defaults["overlay_work"] = defaults["work"] + "/overlay_work" defaults["data"] = defaults["work"] + "/data" defaults["lxc"] = defaults["work"] + "/lxc" defaults["host_perms"] = defaults["work"] + "/host-permissions" diff --git a/tools/helpers/images.py b/tools/helpers/images.py index cf65a11..e00e9b1 100644 --- a/tools/helpers/images.py +++ b/tools/helpers/images.py @@ -128,8 +128,19 @@ def make_prop(args, cfg, full_props_path): def mount_rootfs(args, images_dir, session): helpers.mount.mount(args, images_dir + "/system.img", tools.config.defaults["rootfs"], umount=True) + helpers.mount.mount_overlay(args, [tools.config.defaults["overlay"], + tools.config.defaults["rootfs"]], + tools.config.defaults["rootfs"], + upper_dir=tools.config.defaults["overlay_rw"] + "/system", + work_dir=tools.config.defaults["overlay_work"] + "/system") helpers.mount.mount(args, images_dir + "/vendor.img", tools.config.defaults["rootfs"] + "/vendor") + helpers.mount.mount_overlay(args, [tools.config.defaults["overlay"] + "/vendor", + tools.config.defaults["rootfs"] + "/vendor"], + tools.config.defaults["rootfs"] + "/vendor", + upper_dir=tools.config.defaults["overlay_rw"] + "/vendor", + work_dir=tools.config.defaults["overlay_work"] + "/vendor") + for egl_path in ["/vendor/lib/egl", "/vendor/lib64/egl"]: if os.path.isdir(egl_path): helpers.mount.bind( diff --git a/tools/helpers/mount.py b/tools/helpers/mount.py index 2660952..a0d34be 100644 --- a/tools/helpers/mount.py +++ b/tools/helpers/mount.py @@ -149,3 +149,28 @@ def mount(args, source, destination, create_folders=True, umount=False, # Verify, that it has worked if not ismount(destination): raise RuntimeError("Mount failed: " + source + " -> " + destination) + +def mount_overlay(args, lower_dirs, destination, upper_dir=None, work_dir=None, + create_folders=True, readonly=True): + """ + Mount an overlay. + """ + dirs = [*lower_dirs] + options = ["xino=off", "lowerdir=" + (":".join(lower_dirs))] + + if upper_dir: + dirs.append(upper_dir) + dirs.append(work_dir) + options.append("upperdir=" + upper_dir) + options.append("workdir=" + work_dir) + + for dir_path in dirs: + if not os.path.exists(dir_path): + if create_folders: + tools.helpers.run.user(args, ["mkdir", "-p", dir_path]) + else: + raise RuntimeError("Mount failed, folder does not exist: " + + dir_path) + + mount(args, "overlay", destination, mount_type="overlay", options=options, + readonly=readonly, create_folders=create_folders, force=True) -- 2.47.3 From 5718ba8b068a83ad07ebdfdfd02d5c3a2bc35aca Mon Sep 17 00:00:00 2001 From: Sebastian Krzyszkowiak Date: Thu, 10 Nov 2022 22:15:27 +0100 Subject: [PATCH 11/16] Remove RW image overlay when upgrading images --- tools/actions/initializer.py | 2 ++ tools/helpers/images.py | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/tools/actions/initializer.py b/tools/actions/initializer.py index 31b4fc9..fdc4a40 100644 --- a/tools/actions/initializer.py +++ b/tools/actions/initializer.py @@ -117,6 +117,8 @@ def init(args): helpers.images.umount_rootfs(args) if args.images_path not in tools.config.defaults["preinstalled_images_paths"]: helpers.images.get(args) + else: + helpers.images.remove_overlay(args) if not os.path.isdir(tools.config.defaults["rootfs"]): os.mkdir(tools.config.defaults["rootfs"]) if not os.path.isdir(tools.config.defaults["overlay"]): diff --git a/tools/helpers/images.py b/tools/helpers/images.py index e00e9b1..c5d5fad 100644 --- a/tools/helpers/images.py +++ b/tools/helpers/images.py @@ -4,6 +4,7 @@ import logging import zipfile import json import hashlib +import shutil import os import tools.config from tools import helpers @@ -78,6 +79,7 @@ def get(args): tools.config.save(args, cfg) os.remove(images_zip) break + remove_overlay(args) def replace(args, system_zip, system_time, vendor_zip, vendor_time): cfg = tools.config.load(args) @@ -92,6 +94,13 @@ def replace(args, system_zip, system_time, vendor_zip, vendor_time): zip_ref.extractall(args.images_path) cfg["waydroid"]["vendor_datetime"] = str(vendor_time) tools.config.save(args, cfg) + remove_overlay(args) + +def remove_overlay(args): + if os.path.isdir(tools.config.defaults["overlay_rw"]): + shutil.rmtree(tools.config.defaults["overlay_rw"]) + if os.path.isdir(tools.config.defaults["overlay_work"]): + shutil.rmtree(tools.config.defaults["overlay_work"]) def make_prop(args, cfg, full_props_path): if not os.path.isfile(args.work + "/waydroid_base.prop"): -- 2.47.3 From e87b848b9b2acaa4952eb92e339e575d25281d3a Mon Sep 17 00:00:00 2001 From: Alessandro Astone Date: Mon, 9 Jan 2023 22:32:33 +0100 Subject: [PATCH 12/16] Make overlays opt-out --- tools/config/__init__.py | 6 ++++-- tools/helpers/images.py | 23 +++++++++++++---------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/tools/config/__init__.py b/tools/config/__init__.py index 1edbf17..354f384 100644 --- a/tools/config/__init__.py +++ b/tools/config/__init__.py @@ -21,7 +21,8 @@ config_keys = ["arch", "vendor_type", "system_datetime", "vendor_datetime", - "suspend_action"] + "suspend_action", + "mount_overlays"] # Config file/commandline default values # $WORK gets replaced with the actual value for args.work (which may be @@ -36,7 +37,8 @@ defaults = { "/etc/waydroid-extra/images", "/usr/share/waydroid-extra/images", ], - "suspend_action": "freeze" + "suspend_action": "freeze", + "mount_overlays": "True", } defaults["images_path"] = defaults["work"] + "/images" defaults["rootfs"] = defaults["work"] + "/rootfs" diff --git a/tools/helpers/images.py b/tools/helpers/images.py index c5d5fad..3f1929c 100644 --- a/tools/helpers/images.py +++ b/tools/helpers/images.py @@ -135,20 +135,23 @@ def make_prop(args, cfg, full_props_path): os.chmod(full_props_path, 0o644) def mount_rootfs(args, images_dir, session): + cfg = tools.config.load(args) helpers.mount.mount(args, images_dir + "/system.img", tools.config.defaults["rootfs"], umount=True) - helpers.mount.mount_overlay(args, [tools.config.defaults["overlay"], - tools.config.defaults["rootfs"]], - tools.config.defaults["rootfs"], - upper_dir=tools.config.defaults["overlay_rw"] + "/system", - work_dir=tools.config.defaults["overlay_work"] + "/system") + if cfg["waydroid"]["mount_overlays"] == "True": + helpers.mount.mount_overlay(args, [tools.config.defaults["overlay"], + tools.config.defaults["rootfs"]], + tools.config.defaults["rootfs"], + upper_dir=tools.config.defaults["overlay_rw"] + "/system", + work_dir=tools.config.defaults["overlay_work"] + "/system") helpers.mount.mount(args, images_dir + "/vendor.img", tools.config.defaults["rootfs"] + "/vendor") - helpers.mount.mount_overlay(args, [tools.config.defaults["overlay"] + "/vendor", - tools.config.defaults["rootfs"] + "/vendor"], - tools.config.defaults["rootfs"] + "/vendor", - upper_dir=tools.config.defaults["overlay_rw"] + "/vendor", - work_dir=tools.config.defaults["overlay_work"] + "/vendor") + if cfg["waydroid"]["mount_overlays"] == "True": + helpers.mount.mount_overlay(args, [tools.config.defaults["overlay"] + "/vendor", + tools.config.defaults["rootfs"] + "/vendor"], + tools.config.defaults["rootfs"] + "/vendor", + upper_dir=tools.config.defaults["overlay_rw"] + "/vendor", + work_dir=tools.config.defaults["overlay_work"] + "/vendor") for egl_path in ["/vendor/lib/egl", "/vendor/lib64/egl"]: if os.path.isdir(egl_path): -- 2.47.3 From eabe5d6c3b31360a33621284f2dcc98e9d03b81d Mon Sep 17 00:00:00 2001 From: Alessandro Astone Date: Sun, 15 Jan 2023 16:34:02 +0100 Subject: [PATCH 13/16] Make session more resilient to start failures --- tools/actions/container_manager.py | 11 +++++------ tools/actions/session_manager.py | 8 +++++--- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/tools/actions/container_manager.py b/tools/actions/container_manager.py index 29ba552..cbd6298 100644 --- a/tools/actions/container_manager.py +++ b/tools/actions/container_manager.py @@ -123,10 +123,7 @@ def start(args): def do_start(args, session): if "session" in args: - logging.info("Already tracking a session") - return - - args.session = session + raise RuntimeError("Already tracking a session") # Networking command = [tools.config.tools_src + @@ -140,12 +137,12 @@ def do_start(args, session): # Mount rootfs cfg = tools.config.load(args) - helpers.images.mount_rootfs(args, cfg["waydroid"]["images_path"], args.session) + helpers.images.mount_rootfs(args, cfg["waydroid"]["images_path"], session) helpers.protocol.set_aidl_version(args) # Mount data - helpers.mount.bind(args, args.session["waydroid_data"], + helpers.mount.bind(args, session["waydroid_data"], tools.config.defaults["data"]) # Cgroup hacks @@ -167,6 +164,8 @@ def do_start(args, session): helpers.lxc.start(args) services.hardware_manager.start(args) + args.session = session + def stop(args): try: services.hardware_manager.stop(args) diff --git a/tools/actions/session_manager.py b/tools/actions/session_manager.py index cfcd267..abb4f35 100644 --- a/tools/actions/session_manager.py +++ b/tools/actions/session_manager.py @@ -69,11 +69,13 @@ def start(args, unlocked_cb=None): GLib.unix_signal_add(GLib.PRIORITY_HIGH, signal.SIGUSR1, sigusr_handler, None) try: tools.helpers.ipc.DBusContainerService().Start(session) - except dbus.DBusException: - logging.error("WayDroid container is not listening") + except dbus.DBusException as e: + if e.get_dbus_name().startswith("org.freedesktop.DBus.Python"): + logging.error(e.get_dbus_message().splitlines()[-1]) + else: + logging.error("WayDroid container is not listening") sys.exit(0) - services.user_manager.start(args, session, unlocked_cb) services.clipboard_manager.start(args) service(args, mainloop) -- 2.47.3 From 2c223d412e8dfcb89efaf715f1e1f657a8c7e509 Mon Sep 17 00:00:00 2001 From: Jami Kettunen Date: Sun, 15 Jan 2023 06:17:23 +0200 Subject: [PATCH 14/16] session: Add strict WAYLAND_DISPLAY validity checks Enough with not checking we actually have a Wayland compositor around at all: start requiring the WAYLAND_DISPLAY socket actually exists as an absolute path or relatively under XDG_RUNTIME_DIR. Additionally if WAYLAND_DISPLAY isn't an absolute path to the socket (most setups) ensure XDG_RUNTIME_DIR is set and error with a typically appropriate message. --- tools/actions/session_manager.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/tools/actions/session_manager.py b/tools/actions/session_manager.py index abb4f35..c1d4271 100644 --- a/tools/actions/session_manager.py +++ b/tools/actions/session_manager.py @@ -39,13 +39,30 @@ def start(args, unlocked_cb=None): unlocked_cb() return - session = copy.copy(tools.config.session_defaults); + session = copy.copy(tools.config.session_defaults) + + # TODO: also support WAYLAND_SOCKET? wayland_display = session["wayland_display"] if wayland_display == "None" or not wayland_display: logging.warning('WAYLAND_DISPLAY is not set, defaulting to "wayland-0"') + wayland_display = session["wayland_display"] = "wayland-0" + + if os.path.isabs(wayland_display): + wayland_socket_path = wayland_display + else: + xdg_runtime_dir = session["xdg_runtime_dir"] + if xdg_runtime_dir == "None" or not xdg_runtime_dir: + logging.error(f"XDG_RUNTIME_DIR is not set; please don't start a Waydroid session with 'sudo'!") + sys.exit(1) + wayland_socket_path = os.path.join(xdg_runtime_dir, wayland_display) + if not os.path.exists(wayland_socket_path): + logging.error(f"Wayland socket '{wayland_socket_path}' doesn't exist; are you running a Wayland compositor?") + sys.exit(1) + waydroid_data = session["waydroid_data"] if not os.path.isdir(waydroid_data): os.makedirs(waydroid_data) + dpi = tools.helpers.props.host_get(args, "ro.sf.lcd_density") if dpi == "": dpi = os.getenv("GRID_UNIT_PX") -- 2.47.3 From 51c335fb2af0c292b51fa0b030ce4c7b39e97825 Mon Sep 17 00:00:00 2001 From: Jami Kettunen Date: Sun, 15 Jan 2023 06:24:44 +0200 Subject: [PATCH 15/16] debian: Unify waydroid.postinst indentation This tiny detail has bugged me for too long, let's just use tabs since that's what the rest of the file was indented with anyway. --- debian/waydroid.postinst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/waydroid.postinst b/debian/waydroid.postinst index 10f9eea..f81eabe 100644 --- a/debian/waydroid.postinst +++ b/debian/waydroid.postinst @@ -3,7 +3,7 @@ set -e configure() { # Update configs - waydroid upgrade -o + waydroid upgrade -o } case "$1" in -- 2.47.3 From 9aae6c4ab39d12056a8df086f1277d43ab27f14e Mon Sep 17 00:00:00 2001 From: Jami Kettunen Date: Sun, 15 Jan 2023 06:26:10 +0200 Subject: [PATCH 16/16] props: Drop unneeded ';' from end of return line --- tools/helpers/props.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/helpers/props.py b/tools/helpers/props.py index 0d7f4bc..8b6ca5b 100644 --- a/tools/helpers/props.py +++ b/tools/helpers/props.py @@ -42,5 +42,5 @@ def file_get(args, file, prop): continue k,v = line.partition("=")[::2] if k == prop: - return v; + return v return "" -- 2.47.3