]> glassweightruler.freedombox.rocks Git - waydroid.git/blob - tools/actions/initializer.py
initializer: Differentiate HALIUM vndk31 and vndk32
[waydroid.git] / tools / actions / initializer.py
1 # Copyright 2021 Erfan Abdi
2 # SPDX-License-Identifier: GPL-3.0-or-later
3 import logging
4 import os
5 from tools import helpers
6 import tools.config
7
8 import sys
9 import threading
10 import multiprocessing
11 import select
12 import queue
13 import time
14 import dbus
15 import dbus.service
16 from gi.repository import GLib
17
18 def is_initialized(args):
19 return os.path.isfile(args.config) and os.path.isdir(tools.config.defaults["rootfs"])
20
21 def get_vendor_type(args):
22 vndk_str = helpers.props.host_get(args, "ro.vndk.version")
23 ret = "MAINLINE"
24 if vndk_str != "":
25 vndk = int(vndk_str)
26 if vndk > 19:
27 halium_ver = vndk - 19
28 if vndk > 31:
29 halium_ver -= 1 # 12L -> Halium 12
30 ret = "HALIUM_" + str(halium_ver)
31 if vndk == 32:
32 ret += "L"
33
34 return ret
35
36 def setup_config(args):
37 cfg = tools.config.load(args)
38 args.arch = helpers.arch.host()
39 cfg["waydroid"]["arch"] = args.arch
40
41 args.vendor_type = get_vendor_type(args)
42 cfg["waydroid"]["vendor_type"] = args.vendor_type
43
44 helpers.drivers.setupBinderNodes(args)
45 cfg["waydroid"]["binder"] = args.BINDER_DRIVER
46 cfg["waydroid"]["vndbinder"] = args.VNDBINDER_DRIVER
47 cfg["waydroid"]["hwbinder"] = args.HWBINDER_DRIVER
48
49 has_preinstalled_images = False
50 preinstalled_images_paths = tools.config.defaults["preinstalled_images_paths"]
51 for preinstalled_images in preinstalled_images_paths:
52 if os.path.isdir(preinstalled_images):
53 if os.path.isfile(preinstalled_images + "/system.img") and os.path.isfile(preinstalled_images + "/vendor.img"):
54 has_preinstalled_images = True
55 args.images_path = preinstalled_images
56 break
57 else:
58 logging.warning("Found directory {} but missing system or vendor image, ignoring...".format(preinstalled_images))
59
60 if not args.images_path:
61 args.images_path = tools.config.defaults["images_path"]
62 cfg["waydroid"]["images_path"] = args.images_path
63
64 if has_preinstalled_images:
65 cfg["waydroid"]["system_ota"] = args.system_ota = "None"
66 cfg["waydroid"]["vendor_ota"] = args.vendor_ota = "None"
67 cfg["waydroid"]["system_datetime"] = tools.config.defaults["system_datetime"]
68 cfg["waydroid"]["vendor_datetime"] = tools.config.defaults["vendor_datetime"]
69 tools.config.save(args, cfg)
70 return True
71
72 channels_cfg = tools.config.load_channels()
73 if not args.system_channel:
74 args.system_channel = channels_cfg["channels"]["system_channel"]
75 if not args.vendor_channel:
76 args.vendor_channel = channels_cfg["channels"]["vendor_channel"]
77 if not args.rom_type:
78 args.rom_type = channels_cfg["channels"]["rom_type"]
79 if not args.system_type:
80 args.system_type = channels_cfg["channels"]["system_type"]
81
82 if not args.system_channel or not args.vendor_channel:
83 logging.error("ERROR: You must provide 'System OTA' and 'Vendor OTA' URLs.")
84 return False
85
86 args.system_ota = args.system_channel + "/" + args.rom_type + \
87 "/waydroid_" + args.arch + "/" + args.system_type + ".json"
88 system_request = helpers.http.retrieve(args.system_ota)
89 if system_request[0] != 200:
90 raise ValueError(
91 "Failed to get system OTA channel: {}, error: {}".format(args.system_ota, system_request[0]))
92
93 device_codename = helpers.props.host_get(args, "ro.product.device")
94 args.vendor_type = None
95 for vendor in [device_codename, get_vendor_type(args)]:
96 vendor_ota = args.vendor_channel + "/waydroid_" + \
97 args.arch + "/" + vendor.replace(" ", "_") + ".json"
98 vendor_request = helpers.http.retrieve(vendor_ota)
99 if vendor_request[0] == 200:
100 args.vendor_type = vendor
101 args.vendor_ota = vendor_ota
102 break
103
104 if not args.vendor_type:
105 raise ValueError(
106 "Failed to get vendor OTA channel: {}".format(vendor_ota))
107
108 if args.system_ota != cfg["waydroid"].get("system_ota"):
109 cfg["waydroid"]["system_datetime"] = tools.config.defaults["system_datetime"]
110 if args.vendor_ota != cfg["waydroid"].get("vendor_ota"):
111 cfg["waydroid"]["vendor_datetime"] = tools.config.defaults["vendor_datetime"]
112
113 cfg["waydroid"]["vendor_type"] = args.vendor_type
114 cfg["waydroid"]["system_ota"] = args.system_ota
115 cfg["waydroid"]["vendor_ota"] = args.vendor_ota
116 tools.config.save(args, cfg)
117 return True
118
119 def init(args):
120 if not is_initialized(args) or args.force:
121 initializer_service = None
122 try:
123 initializer_service = tools.helpers.ipc.DBusContainerService("/Initializer", "id.waydro.Initializer")
124 except dbus.DBusException:
125 pass
126 if not setup_config(args):
127 return
128 status = "STOPPED"
129 if os.path.exists(tools.config.defaults["lxc"] + "/waydroid"):
130 status = helpers.lxc.status(args)
131 if status != "STOPPED":
132 logging.info("Stopping container")
133 try:
134 container = tools.helpers.ipc.DBusContainerService()
135 args.session = container.GetSession()
136 container.Stop(False)
137 except Exception as e:
138 logging.debug(e)
139 tools.actions.container_manager.stop(args)
140 if args.images_path not in tools.config.defaults["preinstalled_images_paths"]:
141 helpers.images.get(args)
142 else:
143 helpers.images.remove_overlay(args)
144 if not os.path.isdir(tools.config.defaults["rootfs"]):
145 os.mkdir(tools.config.defaults["rootfs"])
146 if not os.path.isdir(tools.config.defaults["overlay"]):
147 os.mkdir(tools.config.defaults["overlay"])
148 os.mkdir(tools.config.defaults["overlay"]+"/vendor")
149 if not os.path.isdir(tools.config.defaults["overlay_rw"]):
150 os.mkdir(tools.config.defaults["overlay_rw"])
151 os.mkdir(tools.config.defaults["overlay_rw"]+"/system")
152 os.mkdir(tools.config.defaults["overlay_rw"]+"/vendor")
153 helpers.drivers.probeAshmemDriver(args)
154 helpers.lxc.setup_host_perms(args)
155 helpers.lxc.set_lxc_config(args)
156 helpers.lxc.make_base_props(args)
157 if status != "STOPPED":
158 logging.info("Starting container")
159 try:
160 container.Start(args.session)
161 except Exception as e:
162 logging.debug(e)
163 logging.error("Failed to restart container. Please do so manually.")
164
165 if "running_init_in_service" not in args or not args.running_init_in_service:
166 try:
167 if initializer_service:
168 initializer_service.Done()
169 except dbus.DBusException:
170 pass
171 else:
172 logging.info("Already initialized")
173
174 def wait_for_init(args):
175 helpers.ipc.create_channel("remote_init_output")
176
177 mainloop = GLib.MainLoop()
178 dbus_obj = DbusInitializer(mainloop, dbus.SystemBus(), '/Initializer', args)
179 mainloop.run()
180
181 # After init
182 dbus_obj.remove_from_connection()
183
184 class DbusInitializer(dbus.service.Object):
185 def __init__(self, looper, bus, object_path, args):
186 self.args = args
187 self.looper = looper
188 dbus.service.Object.__init__(self, bus, object_path)
189
190 @dbus.service.method("id.waydro.Initializer", in_signature='a{ss}', out_signature='', sender_keyword="sender", connection_keyword="conn")
191 def Init(self, params, sender=None, conn=None):
192 channels_cfg = tools.config.load_channels()
193 no_auth = params["system_channel"] == channels_cfg["channels"]["system_channel"] and \
194 params["vendor_channel"] == channels_cfg["channels"]["vendor_channel"]
195 if no_auth or ensure_polkit_auth(sender, conn, "id.waydro.Initializer.Init"):
196 threading.Thread(target=remote_init_server, args=(self.args, params)).start()
197 else:
198 raise PermissionError("Polkit: Authentication failed")
199
200 @dbus.service.method("id.waydro.Initializer", in_signature='', out_signature='')
201 def Done(self):
202 if is_initialized(self.args):
203 self.looper.quit()
204
205 def ensure_polkit_auth(sender, conn, privilege):
206 dbus_info = dbus.Interface(conn.get_object("org.freedesktop.DBus", "/org/freedesktop/DBus/Bus", False), "org.freedesktop.DBus")
207 pid = dbus_info.GetConnectionUnixProcessID(sender)
208 polkit = dbus.Interface(dbus.SystemBus().get_object("org.freedesktop.PolicyKit1", "/org/freedesktop/PolicyKit1/Authority", False), "org.freedesktop.PolicyKit1.Authority")
209 try:
210 (is_auth, _, _) = polkit.CheckAuthorization(
211 ("unix-process", {
212 "pid": dbus.UInt32(pid, variant_level=1),
213 "start-time": dbus.UInt64(0, variant_level=1)}),
214 privilege, {"AllowUserInteraction": "true"},
215 dbus.UInt32(1),
216 "",
217 timeout=300)
218 return is_auth
219 except dbus.DBusException:
220 raise PermissionError("Polkit: Authentication timed out")
221
222 def background_remote_init_process(args):
223 with helpers.ipc.open_channel("remote_init_output", "wb") as channel_out:
224 class StdoutRedirect(logging.StreamHandler):
225 def write(self, s):
226 channel_out.write(str.encode(s))
227 def flush(self):
228 pass
229 def emit(self, record):
230 if record.levelno >= logging.INFO:
231 self.write(self.format(record) + self.terminator)
232
233 out = StdoutRedirect()
234 sys.stdout = sys.stderr = out
235 logging.getLogger().addHandler(out)
236
237 ctl_queue = queue.Queue()
238 def try_init(args):
239 try:
240 init(args)
241 except Exception as e:
242 print(str(e))
243 finally:
244 ctl_queue.put(0)
245
246 def poll_pipe():
247 poller = select.poll()
248 poller.register(channel_out, select.POLLERR)
249 poller.poll()
250 # When reaching here the client was terminated
251 ctl_queue.put(0)
252
253 init_thread = threading.Thread(target=try_init, args=(args,))
254 init_thread.daemon = True
255 init_thread.start()
256
257 poll_thread = threading.Thread(target=poll_pipe)
258 poll_thread.daemon = True
259 poll_thread.start()
260
261 # Join any one of the two threads
262 # Then exit the subprocess to kill the remaining thread.
263 # Can you believe this is the only way to kill a thread in python???
264 ctl_queue.get()
265
266 sys.stdout = sys.__stdout__
267 sys.stderr = sys.__stderr__
268 logging.getLogger().removeHandler(out)
269
270 def remote_init_server(args, params):
271 args.force = True
272 args.images_path = ""
273 args.rom_type = ""
274 args.system_channel = params["system_channel"]
275 args.vendor_channel = params["vendor_channel"]
276 args.system_type = params["system_type"]
277 args.running_init_in_service = True
278
279 p = multiprocessing.Process(target=background_remote_init_process, args=(args,))
280 p.daemon = True
281 p.start()
282 p.join()
283
284 def remote_init_client(args):
285 # Local imports cause Gtk is intrusive
286 import gi
287 gi.require_version("Gtk", "3.0")
288 from gi.repository import Gtk
289
290 bus = dbus.SystemBus()
291
292 if is_initialized(args):
293 try:
294 tools.helpers.ipc.DBusContainerService("/Initializer", "id.waydro.Initializer").Done()
295 except dbus.DBusException:
296 pass
297 return
298
299 def notify_and_quit(caller):
300 if is_initialized(args):
301 try:
302 tools.helpers.ipc.DBusContainerService("/Initializer", "id.waydro.Initializer").Done()
303 except dbus.DBusException:
304 pass
305 GLib.idle_add(Gtk.main_quit)
306
307 class WaydroidInitWindow(Gtk.Window):
308 def __init__(self):
309 super().__init__(title="Initialize Waydroid")
310 channels_cfg = tools.config.load_channels()
311
312 self.set_default_size(600, 250)
313 self.set_icon_name("waydroid")
314
315 grid = Gtk.Grid(row_spacing=6, column_spacing=6, margin=10, column_homogeneous=True)
316 grid.set_hexpand(True)
317 grid.set_vexpand(True)
318 self.add(grid)
319
320 sysOtaLabel = Gtk.Label("System OTA")
321 sysOtaEntry = Gtk.Entry()
322 sysOtaEntry.set_text(channels_cfg["channels"]["system_channel"])
323 grid.attach(sysOtaLabel, 0, 0, 1, 1)
324 grid.attach_next_to(sysOtaEntry ,sysOtaLabel, Gtk.PositionType.RIGHT, 2, 1)
325 self.sysOta = sysOtaEntry.get_buffer()
326
327 vndOtaLabel = Gtk.Label("Vendor OTA")
328 vndOtaEntry = Gtk.Entry()
329 vndOtaEntry.set_text(channels_cfg["channels"]["vendor_channel"])
330 grid.attach(vndOtaLabel, 0, 1, 1, 1)
331 grid.attach_next_to(vndOtaEntry, vndOtaLabel, Gtk.PositionType.RIGHT, 2, 1)
332 self.vndOta = vndOtaEntry.get_buffer()
333
334 sysTypeLabel = Gtk.Label("Android Type")
335 sysTypeCombo = Gtk.ComboBoxText()
336 sysTypeCombo.set_entry_text_column(0)
337 for t in ["VANILLA", "GAPPS"]:
338 sysTypeCombo.append_text(t)
339 sysTypeCombo.set_active(0)
340 grid.attach(sysTypeLabel, 0, 2, 1, 1)
341 grid.attach_next_to(sysTypeCombo, sysTypeLabel, Gtk.PositionType.RIGHT, 2, 1)
342 self.sysType = sysTypeCombo
343
344 downloadBtn = Gtk.Button("Download")
345 downloadBtn.connect("clicked", self.on_download_btn_clicked)
346 grid.attach(downloadBtn, 1,3,1,1)
347 self.downloadBtn = downloadBtn
348
349 doneBtn = Gtk.Button("Done")
350 doneBtn.connect("clicked", lambda x: self.destroy())
351 doneBtn.get_style_context().add_class('suggested-action')
352 grid.attach_next_to(doneBtn, downloadBtn, Gtk.PositionType.RIGHT, 1, 1)
353 self.doneBtn = doneBtn
354
355 outScrolledWindow = Gtk.ScrolledWindow()
356 outScrolledWindow.set_hexpand(True)
357 outScrolledWindow.set_vexpand(True)
358 outTextView = Gtk.TextView()
359 outTextView.set_property('editable', False)
360 outTextView.set_property('cursor-visible', False)
361 outScrolledWindow.add(outTextView)
362 grid.attach(outScrolledWindow, 0, 4, 3, 1)
363 self.outScrolledWindow = outScrolledWindow
364 self.outTextView = outTextView
365 self.outBuffer = outTextView.get_buffer()
366 self.outBuffer.create_mark("end", self.outBuffer.get_end_iter(), False)
367
368 self.open_channel = None
369
370 def scroll_to_bottom(self):
371 self.outTextView.scroll_mark_onscreen(self.outBuffer.get_mark("end"))
372
373 def on_download_btn_clicked(self, widget):
374 widget.set_sensitive(False)
375 self.doneBtn.hide()
376 self.outTextView.show()
377 init_params = (self.sysOta.get_text(), self.vndOta.get_text(), self.sysType.get_active_text())
378 init_runner = threading.Thread(target=self.run_init, args=init_params)
379 init_runner.daemon = True
380 init_runner.start()
381
382 def run_init(self, systemOta, vendorOta, systemType):
383 def draw_sync(s):
384 if s.startswith('\r'):
385 last = self.outBuffer.get_iter_at_line(self.outBuffer.get_line_count()-1)
386 last.backward_char()
387 self.outBuffer.delete(last, self.outBuffer.get_end_iter())
388 self.outBuffer.insert(self.outBuffer.get_end_iter(), s)
389 self.scroll_to_bottom()
390 def draw(s):
391 GLib.idle_add(draw_sync, s)
392
393 if self.open_channel is not None:
394 self.open_channel.close()
395 # Wait for other end to reset
396 time.sleep(1)
397
398 draw("Waiting for waydroid container service...\n")
399 try:
400 params = {
401 "system_channel": self.sysOta.get_text(),
402 "vendor_channel": self.vndOta.get_text(),
403 "system_type": self.sysType.get_active_text()
404 }
405 tools.helpers.ipc.DBusContainerService("/Initializer", "id.waydro.Initializer").Init(params, timeout=310)
406 except dbus.DBusException as e:
407 if e.get_dbus_name() == "org.freedesktop.DBus.Python.PermissionError":
408 draw(e.get_dbus_message().splitlines()[-1] + "\n")
409 else:
410 draw("The waydroid container service is not listening\n")
411 GLib.idle_add(self.downloadBtn.set_sensitive, True)
412 return
413
414 with helpers.ipc.open_channel("remote_init_output", "rb") as channel:
415 self.open_channel = channel
416 GLib.idle_add(self.downloadBtn.set_sensitive, True)
417 line = ""
418 try:
419 while True:
420 data = channel.read(1)
421 if len(data) == 0:
422 draw(line)
423 break
424 c = data.decode()
425 if c == '\r':
426 draw(line)
427 line = c
428 else:
429 line += c
430 if c == '\n':
431 draw(line)
432 line = ""
433 except:
434 draw("\nInterrupted\n")
435
436 if is_initialized(args):
437 GLib.idle_add(self.doneBtn.show)
438 draw("Done\n")
439
440
441 GLib.set_prgname("Waydroid")
442 win = WaydroidInitWindow()
443 win.connect("destroy", notify_and_quit)
444
445 win.show_all()
446 win.outTextView.hide()
447 win.doneBtn.hide()
448
449 Gtk.main()