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