win32exelink.zig 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. const std = @import("std");
  2. const log = std.log.scoped(.zigexelink);
  3. // NOTE: to prevent the exe from having multiple markers, I can't create a separate string literal
  4. // for the marker and get the length from that, I have to hardcode the length
  5. const exe_marker_len = 42;
  6. // I'm exporting this and making it mutable to make sure the compiler keeps it around
  7. // and prevent it from evaluting its contents at comptime
  8. export var zig_exe_string: [exe_marker_len + std.fs.MAX_PATH_BYTES + 1]u8 =
  9. ("!!!THIS MARKS THE zig_exe_string MEMORY!!#" ++ ([1]u8 {0} ** (std.fs.MAX_PATH_BYTES + 1))).*;
  10. const global = struct {
  11. var child: std.ChildProcess = undefined;
  12. var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);
  13. const arena = arena_instance.allocator();
  14. };
  15. pub fn main() !u8 {
  16. // Sanity check that the exe_marker_len is right (note: not fullproof)
  17. std.debug.assert(zig_exe_string[exe_marker_len - 1] == '#');
  18. if (zig_exe_string[exe_marker_len] == 0) {
  19. log.err("the zig target executable has not been set in the exelink", .{});
  20. return 0xff; // fail
  21. }
  22. var zig_exe_len: usize = 1;
  23. while (zig_exe_string[exe_marker_len + zig_exe_len] != 0) {
  24. zig_exe_len += 1;
  25. if (exe_marker_len + zig_exe_len > std.fs.MAX_PATH_BYTES) {
  26. log.err("the zig target execuable is either too big (over {}) or the exe is corrupt", .{std.fs.MAX_PATH_BYTES});
  27. return 1;
  28. }
  29. }
  30. const zig_exe = zig_exe_string[exe_marker_len .. exe_marker_len + zig_exe_len :0];
  31. const args = try std.process.argsAlloc(global.arena);
  32. if (args.len >= 2 and std.mem.eql(u8, args[1], "exelink")) {
  33. try std.io.getStdOut().writer().writeAll(zig_exe);
  34. return 0;
  35. }
  36. args[0] = zig_exe;
  37. // NOTE: create the ChildProcess before calling SetConsoleCtrlHandler because it uses it
  38. global.child = std.ChildProcess.init(args, global.arena);
  39. if (0 == win32.SetConsoleCtrlHandler(consoleCtrlHandler, 1)) {
  40. log.err("SetConsoleCtrlHandler failed, error={}", .{win32.GetLastError()});
  41. return 0xff; // fail
  42. }
  43. try global.child.spawn();
  44. return switch (try global.child.wait()) {
  45. .Exited => |e| e,
  46. .Signal => 0xff,
  47. .Stopped => 0xff,
  48. .Unknown => 0xff,
  49. };
  50. }
  51. fn consoleCtrlHandler(ctrl_type: u32) callconv(@import("std").os.windows.WINAPI) win32.BOOL {
  52. //
  53. // NOTE: Do I need to synchronize this with the main thread?
  54. //
  55. const name: []const u8 = switch (ctrl_type) {
  56. win32.CTRL_C_EVENT => "Control-C",
  57. win32.CTRL_BREAK_EVENT => "Break",
  58. win32.CTRL_CLOSE_EVENT => "Close",
  59. win32.CTRL_LOGOFF_EVENT => "Logoff",
  60. win32.CTRL_SHUTDOWN_EVENT => "Shutdown",
  61. else => "Unknown",
  62. };
  63. // TODO: should we stop the process on a break event?
  64. log.info("caught ctrl signal {d} ({s}), stopping process...", .{ctrl_type, name});
  65. const exit_code = switch (global.child.kill() catch |err| {
  66. log.err("failed to kill process, error={s}", .{@errorName(err)});
  67. std.os.exit(0xff);
  68. }) {
  69. .Exited => |e| e,
  70. .Signal => 0xff,
  71. .Stopped => 0xff,
  72. .Unknown => 0xff,
  73. };
  74. std.os.exit(exit_code);
  75. unreachable;
  76. }
  77. const win32 = struct {
  78. pub const BOOL = i32;
  79. pub const CTRL_C_EVENT = @as(u32, 0);
  80. pub const CTRL_BREAK_EVENT = @as(u32, 1);
  81. pub const CTRL_CLOSE_EVENT = @as(u32, 2);
  82. pub const CTRL_LOGOFF_EVENT = @as(u32, 5);
  83. pub const CTRL_SHUTDOWN_EVENT = @as(u32, 6);
  84. pub const GetLastError = std.os.windows.kernel32.GetLastError;
  85. pub const PHANDLER_ROUTINE = fn(
  86. CtrlType: u32,
  87. ) callconv(@import("std").os.windows.WINAPI) BOOL;
  88. pub extern "KERNEL32" fn SetConsoleCtrlHandler(
  89. HandlerRoutine: ?PHANDLER_ROUTINE,
  90. Add: BOOL,
  91. ) callconv(@import("std").os.windows.WINAPI) BOOL;
  92. };