]> glassweightruler.freedombox.rocks Git - waydroid.git/blob - tools/helpers/lxc.py
Fix prop set command
[waydroid.git] / tools / helpers / lxc.py
1 # Copyright 2021 Erfan Abdi
2 # SPDX-License-Identifier: GPL-3.0-or-later
3 import subprocess
4 import os
5 import re
6 import logging
7 import glob
8 import shutil
9 import time
10 import platform
11 import gbinder
12 import tools.config
13 import tools.helpers.run
14
15
16 def get_lxc_version(args):
17 if shutil.which("lxc-info") is not None:
18 command = ["lxc-info", "--version"]
19 version_str = tools.helpers.run.user(args, command, output_return=True)
20 return int(version_str[0])
21 else:
22 return 0
23
24
25 def generate_nodes_lxc_config(args):
26 def make_entry(src, dist=None, mnt_type="none", options="bind,create=file,optional 0 0", check=True):
27 if check and not os.path.exists(src):
28 return False
29 entry = "lxc.mount.entry = "
30 entry += src + " "
31 if dist is None:
32 dist = src[1:]
33 entry += dist + " "
34 entry += mnt_type + " "
35 entry += options
36 nodes.append(entry)
37 return True
38
39 nodes = []
40 # Necessary dev nodes
41 make_entry("tmpfs", "dev", "tmpfs", "nosuid 0 0", False)
42 make_entry("/dev/zero")
43 make_entry("/dev/null")
44 make_entry("/dev/full")
45 make_entry("/dev/ashmem", check=False)
46 make_entry("/dev/fuse")
47 make_entry("/dev/ion")
48 make_entry("/dev/char", options="bind,create=dir,optional 0 0")
49
50 # Graphic dev nodes
51 make_entry("/dev/kgsl-3d0")
52 make_entry("/dev/mali0")
53 make_entry("/dev/pvr_sync")
54 make_entry("/dev/pmsg0")
55 make_entry("/dev/dxg")
56 make_entry(tools.helpers.gpu.getDriNode(args), "dev/dri/renderD128")
57
58 for n in glob.glob("/dev/fb*"):
59 make_entry(n)
60 for n in glob.glob("/dev/graphics/fb*"):
61 make_entry(n)
62 for n in glob.glob("/dev/video*"):
63 make_entry(n)
64
65 # Binder dev nodes
66 make_entry("/dev/" + args.BINDER_DRIVER, "dev/binder", check=False)
67 make_entry("/dev/" + args.VNDBINDER_DRIVER, "dev/vndbinder", check=False)
68 make_entry("/dev/" + args.HWBINDER_DRIVER, "dev/hwbinder", check=False)
69
70 if args.vendor_type != "MAINLINE":
71 if not make_entry("/dev/hwbinder", "dev/host_hwbinder"):
72 raise OSError('Binder node "hwbinder" of host not found')
73 make_entry("/vendor", "vendor_extra", options="bind,optional 0 0")
74
75 # Necessary device nodes for adb
76 make_entry("none", "dev/pts", "devpts", "defaults,mode=644,ptmxmode=666,create=dir 0 0", False)
77 make_entry("/dev/uhid")
78
79 # TUN/TAP device node for VPN
80 make_entry("/dev/net/tun", "dev/tun")
81
82 # Low memory killer sys node
83 make_entry("/sys/module/lowmemorykiller", options="bind,create=dir,optional 0 0")
84
85 # Mount /data
86 make_entry("tmpfs", "mnt", "tmpfs", "mode=0755,uid=0,gid=1000", False)
87 make_entry(tools.config.defaults["data"], "data", options="bind 0 0", check=False)
88
89 # Mount host permissions
90 make_entry(tools.config.defaults["host_perms"],
91 "vendor/etc/host-permissions", options="bind,optional 0 0")
92
93 # Recursive mount /run to provide necessary host sockets
94 make_entry("/run", options="rbind,create=dir 0 0")
95 # And /dev/shm
96 make_entry("/dev/shm", options="rbind,create=dir,optional 0 0")
97
98 # Necessary sw_sync node for HWC
99 make_entry("/dev/sw_sync")
100 make_entry("/sys/kernel/debug", options="rbind,create=dir,optional 0 0")
101
102 # Vibrator
103 make_entry("/sys/class/leds/vibrator",
104 options="bind,create=dir,optional 0 0")
105 make_entry("/sys/devices/virtual/timed_output/vibrator",
106 options="bind,create=dir,optional 0 0")
107
108 # Media dev nodes (for Mediatek)
109 make_entry("/dev/Vcodec")
110 make_entry("/dev/MTK_SMI")
111 make_entry("/dev/mdp_sync")
112 make_entry("/dev/mtk_cmdq")
113
114 # WSLg
115 make_entry("tmpfs", "mnt_extra", "tmpfs", "nodev 0 0", False)
116 make_entry("/mnt/wslg", "mnt_extra/wslg",
117 options="rbind,create=dir,optional 0 0")
118
119 # var
120 make_entry("tmpfs", "var", "tmpfs", "nodev 0 0", False)
121 make_entry("/var/run", options="rbind,create=dir,optional 0 0")
122
123 # tmp
124 make_entry("tmpfs", "tmp", "tmpfs", "nodev 0 0", False)
125 for n in glob.glob("/tmp/run-*"):
126 make_entry(n, options="rbind,create=dir,optional 0 0")
127
128 # NFC config
129 make_entry("/system/etc/libnfc-nci.conf", options="bind,optional 0 0")
130
131 return nodes
132
133 LXC_APPARMOR_PROFILE = "lxc-waydroid"
134 def get_apparmor_status(args):
135 enabled = False
136 if shutil.which("aa-enabled"):
137 enabled = (tools.helpers.run.user(args, ["aa-enabled", "--quiet"], check=False) == 0)
138 if not enabled and shutil.which("systemctl"):
139 enabled = (tools.helpers.run.user(args, ["systemctl", "is-active", "-q", "apparmor"], check=False) == 0)
140 try:
141 with open("/sys/kernel/security/apparmor/profiles", "r") as f:
142 enabled &= (LXC_APPARMOR_PROFILE in f.read())
143 except:
144 enabled = False
145 return enabled
146
147 def set_lxc_config(args):
148 lxc_path = tools.config.defaults["lxc"] + "/waydroid"
149 lxc_ver = get_lxc_version(args)
150 if lxc_ver == 0:
151 raise OSError("LXC is not installed")
152 config_paths = tools.config.tools_src + "/data/configs/config_"
153 seccomp_profile = tools.config.tools_src + "/data/configs/waydroid.seccomp"
154
155 config_snippets = [ config_paths + "base" ]
156 # lxc v1 and v2 are bit special because some options got renamed later
157 if lxc_ver <= 2:
158 config_snippets.append(config_paths + "1")
159 else:
160 for ver in range(3, 5):
161 snippet = config_paths + str(ver)
162 if lxc_ver >= ver and os.path.exists(snippet):
163 config_snippets.append(snippet)
164
165 command = ["mkdir", "-p", lxc_path]
166 tools.helpers.run.user(args, command)
167 command = ["sh", "-c", "cat {} > \"{}\"".format(' '.join('"{0}"'.format(w) for w in config_snippets), lxc_path + "/config")]
168 tools.helpers.run.user(args, command)
169 command = ["sed", "-i", "s/LXCARCH/{}/".format(platform.machine()), lxc_path + "/config"]
170 tools.helpers.run.user(args, command)
171 command = ["cp", "-fpr", seccomp_profile, lxc_path + "/waydroid.seccomp"]
172 tools.helpers.run.user(args, command)
173 if get_apparmor_status(args):
174 command = ["sed", "-i", "-E", "/lxc.aa_profile|lxc.apparmor.profile/ s/unconfined/{}/g".format(LXC_APPARMOR_PROFILE), lxc_path + "/config"]
175 tools.helpers.run.user(args, command)
176
177 nodes = generate_nodes_lxc_config(args)
178 config_nodes_tmp_path = args.work + "/config_nodes"
179 config_nodes = open(config_nodes_tmp_path, "w")
180 for node in nodes:
181 config_nodes.write(node + "\n")
182 config_nodes.close()
183 command = ["mv", config_nodes_tmp_path, lxc_path]
184 tools.helpers.run.user(args, command)
185
186
187 def make_base_props(args):
188 def find_hal(hardware):
189 hardware_props = [
190 "ro.hardware." + hardware,
191 "ro.hardware",
192 "ro.product.board",
193 "ro.arch",
194 "ro.board.platform"]
195 for p in hardware_props:
196 prop = tools.helpers.props.host_get(args, p)
197 if prop != "":
198 for lib in ["/odm/lib", "/odm/lib64", "/vendor/lib", "/vendor/lib64", "/system/lib", "/system/lib64"]:
199 hal_file = lib + "/hw/" + hardware + "." + prop + ".so"
200 if os.path.isfile(hal_file):
201 return prop
202 return ""
203
204 def find_hidl(intf):
205 if args.vendor_type == "MAINLINE":
206 return False
207
208 try:
209 sm = gbinder.ServiceManager("/dev/hwbinder")
210 return intf in sm.list_sync()
211 except:
212 return False
213
214 props = []
215
216 if not os.path.exists("/dev/ashmem"):
217 props.append("sys.use_memfd=true")
218
219 egl = tools.helpers.props.host_get(args, "ro.hardware.egl")
220 dri = tools.helpers.gpu.getDriNode(args)
221
222 gralloc = find_hal("gralloc")
223 if not gralloc:
224 if find_hidl("android.hardware.graphics.allocator@4.0::IAllocator/default"):
225 gralloc = "android"
226 if not gralloc:
227 if dri:
228 gralloc = "gbm"
229 egl = "mesa"
230 else:
231 gralloc = "default"
232 egl = "swiftshader"
233 props.append("debug.stagefright.ccodec=0")
234 props.append("ro.hardware.gralloc=" + gralloc)
235
236 if egl != "":
237 props.append("ro.hardware.egl=" + egl)
238
239 media_profiles = tools.helpers.props.host_get(args, "media.settings.xml")
240 if media_profiles != "":
241 media_profiles = media_profiles.replace("vendor/", "vendor_extra/")
242 media_profiles = media_profiles.replace("odm/", "odm_extra/")
243 props.append("media.settings.xml=" + media_profiles)
244
245 ccodec = tools.helpers.props.host_get(args, "debug.stagefright.ccodec")
246 if ccodec != "":
247 props.append("debug.stagefright.ccodec=" + ccodec)
248
249 ext_library = tools.helpers.props.host_get(args, "ro.vendor.extension_library")
250 if ext_library != "":
251 ext_library = ext_library.replace("vendor/", "vendor_extra/")
252 ext_library = ext_library.replace("odm/", "odm_extra/")
253 props.append("ro.vendor.extension_library=" + ext_library)
254
255 vulkan = find_hal("vulkan")
256 if not vulkan and dri:
257 vulkan = tools.helpers.gpu.getVulkanDriver(args, os.path.basename(dri))
258 if vulkan:
259 props.append("ro.hardware.vulkan=" + vulkan)
260
261 treble = tools.helpers.props.host_get(args, "ro.treble.enabled")
262 if treble != "true":
263 camera = find_hal("camera")
264 if camera != "":
265 props.append("ro.hardware.camera=" + camera)
266 else:
267 if args.vendor_type == "MAINLINE":
268 props.append("ro.hardware.camera=v4l2")
269
270 opengles = tools.helpers.props.host_get(args, "ro.opengles.version")
271 if opengles == "":
272 opengles = "196609"
273 props.append("ro.opengles.version=" + opengles)
274
275 if args.images_path not in tools.config.defaults["preinstalled_images_paths"]:
276 props.append("waydroid.system_ota=" + args.system_ota)
277 props.append("waydroid.vendor_ota=" + args.vendor_ota)
278 else:
279 props.append("waydroid.updater.disabled=true")
280
281 props.append("waydroid.tools_version=" + tools.config.version)
282
283 if args.vendor_type == "MAINLINE":
284 props.append("ro.vndk.lite=true")
285
286 for product in ["brand", "device", "manufacturer", "model", "name"]:
287 prop_product = tools.helpers.props.host_get(
288 args, "ro.product.vendor." + product)
289 if prop_product != "":
290 props.append("ro.product.waydroid." + product + "=" + prop_product)
291 else:
292 if os.path.isfile("/proc/device-tree/" + product):
293 with open("/proc/device-tree/" + product) as f:
294 f_value = f.read().strip().rstrip('\x00')
295 if f_value != "":
296 props.append("ro.product.waydroid." +
297 product + "=" + f_value)
298
299 prop_fp = tools.helpers.props.host_get(args, "ro.vendor.build.fingerprint")
300 if prop_fp != "":
301 props.append("ro.build.fingerprint=" + prop_fp)
302
303 # now append/override with values in [properties] section of waydroid.cfg
304 cfg = tools.config.load(args)
305 for k, v in cfg["properties"].items():
306 for idx, elem in enumerate(props):
307 if (k+"=") in elem:
308 props.pop(idx)
309 props.append(k+"="+v)
310
311 base_props = open(args.work + "/waydroid_base.prop", "w")
312 for prop in props:
313 base_props.write(prop + "\n")
314 base_props.close()
315
316
317 def setup_host_perms(args):
318 if not os.path.exists(tools.config.defaults["host_perms"]):
319 os.mkdir(tools.config.defaults["host_perms"])
320
321 treble = tools.helpers.props.host_get(args, "ro.treble.enabled")
322 if treble != "true":
323 return
324
325 sku = tools.helpers.props.host_get(args, "ro.boot.product.hardware.sku")
326 copy_list = []
327 copy_list.extend(
328 glob.glob("/vendor/etc/permissions/android.hardware.nfc.*"))
329 if os.path.exists("/vendor/etc/permissions/android.hardware.consumerir.xml"):
330 copy_list.append("/vendor/etc/permissions/android.hardware.consumerir.xml")
331 copy_list.extend(
332 glob.glob("/odm/etc/permissions/android.hardware.nfc.*"))
333 if os.path.exists("/odm/etc/permissions/android.hardware.consumerir.xml"):
334 copy_list.append("/odm/etc/permissions/android.hardware.consumerir.xml")
335 if sku != "":
336 copy_list.extend(
337 glob.glob("/odm/etc/permissions/sku_{}/android.hardware.nfc.*".format(sku)))
338 if os.path.exists("/odm/etc/permissions/sku_{}/android.hardware.consumerir.xml".format(sku)):
339 copy_list.append(
340 "/odm/etc/permissions/sku_{}/android.hardware.consumerir.xml".format(sku))
341
342 for filename in copy_list:
343 shutil.copy(filename, tools.config.defaults["host_perms"])
344
345 def status(args):
346 command = ["lxc-info", "-P", tools.config.defaults["lxc"], "-n", "waydroid", "-sH"]
347 out = subprocess.run(command, stdout=subprocess.PIPE).stdout.decode('utf-8').strip()
348 return out
349
350 def wait_for_running(args):
351 lxc_status = status(args)
352 timeout = 10
353 while lxc_status != "RUNNING" and timeout > 0:
354 lxc_status = status(args)
355 logging.info(
356 "waiting {} seconds for container to start...".format(timeout))
357 timeout = timeout - 1
358 time.sleep(1)
359 if lxc_status != "RUNNING":
360 raise OSError("container failed to start")
361
362 def start(args):
363 command = ["lxc-start", "-P", tools.config.defaults["lxc"],
364 "-F", "-n", "waydroid", "--", "/init"]
365 tools.helpers.run.user(args, command, output="background")
366 wait_for_running(args)
367 # Workaround lxc-start changing stdout/stderr permissions to 700
368 os.chmod(args.log, 0o666)
369
370 def stop(args):
371 command = ["lxc-stop", "-P",
372 tools.config.defaults["lxc"], "-n", "waydroid", "-k"]
373 tools.helpers.run.user(args, command)
374
375 def freeze(args):
376 command = ["lxc-freeze", "-P", tools.config.defaults["lxc"], "-n", "waydroid"]
377 tools.helpers.run.user(args, command)
378
379 def unfreeze(args):
380 command = ["lxc-unfreeze", "-P",
381 tools.config.defaults["lxc"], "-n", "waydroid"]
382 tools.helpers.run.user(args, command)
383
384 def shell(args):
385 state = status(args)
386 if state == "FROZEN":
387 unfreeze(args)
388 elif state != "RUNNING":
389 logging.error("WayDroid container is {}".format(state))
390 return
391 command = ["lxc-attach", "-P", tools.config.defaults["lxc"],
392 "-n", "waydroid", "--"]
393 if args.COMMAND:
394 command.extend(args.COMMAND)
395 else:
396 command.append("/system/bin/sh")
397 subprocess.run(command, env={"PATH": os.environ['PATH'] + ":/system/bin:/vendor/bin"})
398 if state == "FROZEN":
399 freeze(args)
400
401 def logcat(args):
402 state = status(args)
403 if state == "FROZEN":
404 unfreeze(args)
405 elif state != "RUNNING":
406 logging.error("WayDroid container is {}".format(state))
407 return
408 command = ["lxc-attach", "-P", tools.config.defaults["lxc"],
409 "-n", "waydroid", "--", "/system/bin/logcat"]
410 subprocess.run(command)
411 if state == "FROZEN":
412 freeze(args)