win32exelink.zig 4.1 KB

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