launcher.vala 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. /*
  2. * Copyright (c) 2012-2026 Daniele Bartolini et al.
  3. * SPDX-License-Identifier: GPL-3.0-or-later
  4. */
  5. namespace Crown
  6. {
  7. public static Gee.ArrayList<GLib.Subprocess?> subprocesses = null;
  8. [DBus (name = "org.crownengine.SubprocessLauncherError")]
  9. public errordomain SubprocessLauncherError
  10. {
  11. SUBPROCESS_NOT_FOUND,
  12. SUBPROCESS_CANCELLED
  13. }
  14. [DBus (name = "org.crownengine.SubprocessLauncher")]
  15. public class SubprocessLauncherServer : Object
  16. {
  17. public uint32 _subprocess_id;
  18. public SubprocessLauncherServer()
  19. {
  20. _subprocess_id = 0;
  21. }
  22. /// Spawns a subprocess and returns its ID which is *not* its PID.
  23. public uint32 spawnv_async(GLib.SubprocessFlags flags, string[] argv, string working_dir) throws GLib.SpawnError, GLib.Error
  24. {
  25. GLib.SubprocessLauncher sl = new GLib.SubprocessLauncher(flags);
  26. sl.set_cwd(working_dir);
  27. try {
  28. GLib.Subprocess subproc = sl.spawnv(argv);
  29. subproc.set_data<uint32>("id", _subprocess_id);
  30. subprocesses.add(subproc);
  31. logi("Created suprocess ID %u PID %s".printf(_subprocess_id
  32. , (string)subproc.get_identifier())
  33. );
  34. return _subprocess_id++;
  35. } catch (GLib.Error e) {
  36. throw e;
  37. }
  38. }
  39. /// Waits for @a process_id to terminate and returns its exit status.
  40. public int wait(uint32 process_id) throws GLib.Error
  41. {
  42. int ii;
  43. for (ii = 0; ii < subprocesses.size; ++ii) {
  44. uint32 id = subprocesses[ii].get_data<uint32>("id");
  45. if (id == process_id)
  46. break;
  47. }
  48. if (ii == subprocesses.size)
  49. throw new SubprocessLauncherError.SUBPROCESS_NOT_FOUND("Process ID %u not found".printf(process_id));
  50. try {
  51. if (!subprocesses[ii].wait())
  52. throw new SubprocessLauncherError.SUBPROCESS_CANCELLED("Process ID %u was cancelled".printf(process_id));
  53. if (subprocesses[ii].get_if_exited()) {
  54. int exit_status = subprocesses[ii].get_exit_status();
  55. subprocesses.remove_at(ii);
  56. return exit_status;
  57. }
  58. } catch (GLib.Error e) {
  59. throw e;
  60. }
  61. return int.MAX;
  62. }
  63. }
  64. [DBus (name = "org.crownengine.SubprocessLauncher")]
  65. public interface SubprocessLauncher : Object
  66. {
  67. public abstract uint32 spawnv_async(GLib.SubprocessFlags flags, string[] argv, string working_dir) throws GLib.SpawnError, GLib.Error;
  68. public abstract int wait(uint32 process_id) throws GLib.Error;
  69. }
  70. public static void on_bus_acquired(GLib.DBusConnection conn)
  71. {
  72. try {
  73. conn.register_object("/org/crownengine/subprocess_launcher", new SubprocessLauncherServer());
  74. } catch (GLib.IOError e) {
  75. logi("Could not register DBus service.");
  76. }
  77. }
  78. public static void on_name_acquired(GLib.DBusConnection conn, string name)
  79. {
  80. logi("DBus name acquired: %s".printf(name));
  81. }
  82. public static void on_name_lost(GLib.DBusConnection conn, string name)
  83. {
  84. logi("DBus name lost: %s".printf(name));
  85. }
  86. public static GLib.MainLoop loop = null;
  87. public static void wait_async_ready_callback(Object? source_object, AsyncResult res)
  88. {
  89. GLib.Subprocess subproc = (GLib.Subprocess)source_object;
  90. try {
  91. subproc.wait_async.end(res);
  92. uint32 subproc_id = subproc.get_data<uint32>("id");
  93. if (subproc.get_if_exited()) {
  94. int ec = subproc.get_exit_status();
  95. logi("Process ID %u exited with status %d".printf(subproc_id, ec));
  96. } else {
  97. logi("Process ID %u exited abnormally".printf(subproc_id));
  98. }
  99. } catch (GLib.Error e) {
  100. if (e.code == 19) {
  101. subproc.force_exit();
  102. // Assume subproc is dead now.
  103. } else {
  104. loge(e.message);
  105. }
  106. }
  107. // The last subprocess to exit quits the app.
  108. subprocesses.remove(subproc);
  109. if (subprocesses.size == 0) {
  110. loop.quit();
  111. }
  112. }
  113. public static void monitored_process_exited(GLib.Pid pid, int wait_status)
  114. {
  115. // The monitord process exited.
  116. // Terminate any leftover subprocesses it spawned.
  117. uint wait_timer_id = 0;
  118. GLib.Cancellable cancellable = new GLib.Cancellable();
  119. if (subprocesses.size > 0) {
  120. // Spawn a watchdog to cancel all wait_async() after a while.
  121. uint interval_ms = 500;
  122. wait_timer_id = GLib.Timeout.add(interval_ms, () => {
  123. cancellable.cancel();
  124. return GLib.Source.REMOVE;
  125. });
  126. logi("Waiting %ums for %d subprocesses to terminate...".printf(interval_ms, subprocesses.size));
  127. }
  128. if (wait_timer_id > 0) {
  129. // Wait asynchronously for children to terminate.
  130. foreach (var subproc in subprocesses)
  131. subproc.wait_async.begin(cancellable, wait_async_ready_callback);
  132. } else {
  133. // Force children to exit.
  134. foreach (var subproc in subprocesses)
  135. subproc.force_exit();
  136. loop.quit();
  137. }
  138. }
  139. public static int launcher_main(string[] args)
  140. {
  141. loop = new GLib.MainLoop(null, false);
  142. subprocesses = new Gee.ArrayList<GLib.Subprocess?>();
  143. GLib.Pid monitored_pid = 0;
  144. for (int i = 0; i < args.length; ++i) {
  145. if (args[i] == "--launcher") {
  146. monitored_pid = int.parse(args[i + 1]);
  147. break;
  148. }
  149. }
  150. #if CROWN_PLATFORM_LINUX
  151. // Signal handlers.
  152. GLib.Unix.signal_add(Posix.Signal.INT, () => {
  153. if (monitored_pid != 0)
  154. Posix.kill(monitored_pid, Posix.Signal.INT);
  155. // Try to not terminate prior to the watched process.
  156. return GLib.Source.CONTINUE;
  157. });
  158. GLib.Unix.signal_add(Posix.Signal.TERM, () => {
  159. if (monitored_pid != 0)
  160. Posix.kill(monitored_pid, Posix.Signal.TERM);
  161. // Try to not terminate prior to the watched process.
  162. return GLib.Source.CONTINUE;
  163. });
  164. #endif
  165. // Connect to DBus.
  166. GLib.Bus.own_name(GLib.BusType.SESSION
  167. , "org.crownengine.SubprocessLauncher"
  168. , GLib.BusNameOwnerFlags.NONE
  169. , on_bus_acquired
  170. , on_name_acquired
  171. , on_name_lost
  172. );
  173. uint event_source = 0;
  174. if (monitored_pid != 0) {
  175. // It is unclear whether ChildWatch is intended to work with PIDs
  176. // not coming from GLib.Process.spawn*(). It seems to work fine regardless
  177. // but we get a suspicious warning at exit. We might have to replace it
  178. // with something else in the future:
  179. //
  180. // WARN launcher: ../glib/glib/gmain.c:5933: waitid(pid:988061, pidfd=8) failed: No child processes (10).
  181. event_source = GLib.ChildWatch.add(monitored_pid, monitored_process_exited);
  182. }
  183. if (event_source <= 0) {
  184. loge("Failed to start monitoring PID %d".printf(monitored_pid));
  185. return -1;
  186. }
  187. loop.run();
  188. return 0;
  189. }
  190. } /* namespace Crown */