Commit 1408288b95

Jonathan Marler <johnnymarler@gmail.com>
2025-03-03 04:27:17
support more process creation options on Windows
Adds a CreateProcessFlags packed struct for all the possible flags to CreateProcessW on windows. In addition, propagates the existing `start_suspended` option in std.process.Child which was previously only used on Darwin. Also adds a `create_no_window` option to std.process.Child which is a commonly used flag for launching console executables on windows without causing a new console window to "pop up".
1 parent 8a8b301
Changed files (4)
lib
std
test
standalone
windows_argv
lib/std/os/windows/kernel32.zig
@@ -314,7 +314,7 @@ pub extern "kernel32" fn CreateProcessW(
     lpProcessAttributes: ?*SECURITY_ATTRIBUTES,
     lpThreadAttributes: ?*SECURITY_ATTRIBUTES,
     bInheritHandles: BOOL,
-    dwCreationFlags: DWORD,
+    dwCreationFlags: windows.CreateProcessFlags,
     lpEnvironment: ?LPVOID,
     lpCurrentDirectory: ?LPCWSTR,
     lpStartupInfo: *STARTUPINFOW,
lib/std/os/windows.zig
@@ -1918,13 +1918,48 @@ pub const CreateProcessError = error{
     Unexpected,
 };
 
+pub const CreateProcessFlags = packed struct(u32) {
+    debug_process: bool = false,
+    debug_only_this_process: bool = false,
+    create_suspended: bool = false,
+    detached_process: bool = false,
+    create_new_console: bool = false,
+    normal_priority_class: bool = false,
+    idle_priority_class: bool = false,
+    high_priority_class: bool = false,
+    realtime_priority_class: bool = false,
+    create_new_process_group: bool = false,
+    create_unicode_environment: bool = false,
+    create_separate_wow_vdm: bool = false,
+    create_shared_wow_vdm: bool = false,
+    create_forcedos: bool = false,
+    below_normal_priority_class: bool = false,
+    above_normal_priority_class: bool = false,
+    inherit_parent_affinity: bool = false,
+    inherit_caller_priority: bool = false,
+    create_protected_process: bool = false,
+    extended_startupinfo_present: bool = false,
+    process_mode_background_begin: bool = false,
+    process_mode_background_end: bool = false,
+    create_secure_process: bool = false,
+    _reserved: bool = false,
+    create_breakaway_from_job: bool = false,
+    create_preserve_code_authz_level: bool = false,
+    create_default_error_mode: bool = false,
+    create_no_window: bool = false,
+    profile_user: bool = false,
+    profile_kernel: bool = false,
+    profile_server: bool = false,
+    create_ignore_system_default: bool = false,
+};
+
 pub fn CreateProcessW(
     lpApplicationName: ?LPCWSTR,
     lpCommandLine: ?LPWSTR,
     lpProcessAttributes: ?*SECURITY_ATTRIBUTES,
     lpThreadAttributes: ?*SECURITY_ATTRIBUTES,
     bInheritHandles: BOOL,
-    dwCreationFlags: DWORD,
+    dwCreationFlags: CreateProcessFlags,
     lpEnvironment: ?*anyopaque,
     lpCurrentDirectory: ?LPCWSTR,
     lpStartupInfo: *STARTUPINFOW,
lib/std/process/Child.zig
@@ -80,9 +80,13 @@ expand_arg0: Arg0Expand,
 /// Darwin-only. Disable ASLR for the child process.
 disable_aslr: bool = false,
 
-/// Darwin-only. Start child process in suspended state as if SIGSTOP was sent.
+/// Darwin and Windows only. Start child process in suspended state. For Darwin it's started
+/// as if SIGSTOP was sent.
 start_suspended: bool = false,
 
+/// Windows-only. Sets the CREATE_NO_WINDOW flag in CreateProcess.
+create_no_window: bool = false,
+
 /// Set to true to obtain rusage information for the child process.
 /// Depending on the target platform and implementation status, the
 /// requested statistics may or may not be available. If they are
@@ -854,6 +858,12 @@ fn spawnWindows(self: *ChildProcess) SpawnError!void {
     const app_name_w = try unicode.wtf8ToWtf16LeAllocZ(self.allocator, app_basename_wtf8);
     defer self.allocator.free(app_name_w);
 
+    const flags: windows.CreateProcessFlags = .{
+        .create_suspended = self.start_suspended,
+        .create_unicode_environment = true,
+        .create_no_window = self.create_no_window,
+    };
+
     run: {
         const PATH: [:0]const u16 = process.getenvW(unicode.utf8ToUtf16LeStringLiteral("PATH")) orelse &[_:0]u16{};
         const PATHEXT: [:0]const u16 = process.getenvW(unicode.utf8ToUtf16LeStringLiteral("PATHEXT")) orelse &[_:0]u16{};
@@ -889,7 +899,7 @@ fn spawnWindows(self: *ChildProcess) SpawnError!void {
             dir_buf.shrinkRetainingCapacity(normalized_len);
         }
 
-        windowsCreateProcessPathExt(self.allocator, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, &siStartInfo, &piProcInfo) catch |no_path_err| {
+        windowsCreateProcessPathExt(self.allocator, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, flags, &siStartInfo, &piProcInfo) catch |no_path_err| {
             const original_err = switch (no_path_err) {
                 // argv[0] contains unsupported characters that will never resolve to a valid exe.
                 error.InvalidArg0 => return error.FileNotFound,
@@ -917,7 +927,7 @@ fn spawnWindows(self: *ChildProcess) SpawnError!void {
                 const normalized_len = windows.normalizePath(u16, dir_buf.items) catch continue;
                 dir_buf.shrinkRetainingCapacity(normalized_len);
 
-                if (windowsCreateProcessPathExt(self.allocator, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, &siStartInfo, &piProcInfo)) {
+                if (windowsCreateProcessPathExt(self.allocator, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, flags, &siStartInfo, &piProcInfo)) {
                     break :run;
                 } else |err| switch (err) {
                     // argv[0] contains unsupported characters that will never resolve to a valid exe.
@@ -1016,6 +1026,7 @@ fn windowsCreateProcessPathExt(
     cmd_line_cache: *WindowsCommandLineCache,
     envp_ptr: ?[*]u16,
     cwd_ptr: ?[*:0]u16,
+    flags: windows.CreateProcessFlags,
     lpStartupInfo: *windows.STARTUPINFOW,
     lpProcessInformation: *windows.PROCESS_INFORMATION,
 ) !void {
@@ -1166,7 +1177,7 @@ fn windowsCreateProcessPathExt(
             else
                 full_app_name;
 
-            if (windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_ptr, lpStartupInfo, lpProcessInformation)) |_| {
+            if (windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_ptr, flags, lpStartupInfo, lpProcessInformation)) |_| {
                 return;
             } else |err| switch (err) {
                 error.FileNotFound,
@@ -1221,7 +1232,7 @@ fn windowsCreateProcessPathExt(
         else
             full_app_name;
 
-        if (windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_ptr, lpStartupInfo, lpProcessInformation)) |_| {
+        if (windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_ptr, flags, lpStartupInfo, lpProcessInformation)) |_| {
             return;
         } else |err| switch (err) {
             error.FileNotFound => continue,
@@ -1247,6 +1258,7 @@ fn windowsCreateProcess(
     cmd_line: [*:0]u16,
     envp_ptr: ?[*]u16,
     cwd_ptr: ?[*:0]u16,
+    flags: windows.CreateProcessFlags,
     lpStartupInfo: *windows.STARTUPINFOW,
     lpProcessInformation: *windows.PROCESS_INFORMATION,
 ) !void {
@@ -1273,7 +1285,7 @@ fn windowsCreateProcess(
         null,
         null,
         windows.TRUE,
-        windows.CREATE_UNICODE_ENVIRONMENT,
+        flags,
         @as(?*anyopaque, @ptrCast(envp_ptr)),
         cwd_ptr,
         lpStartupInfo,
test/standalone/windows_argv/fuzz.zig
@@ -138,7 +138,7 @@ fn spawnVerify(verify_path: [:0]const u16, cmd_line: [:0]const u16) !windows.DWO
             null,
             null,
             windows.TRUE,
-            0,
+            .{},
             null,
             null,
             &startup_info,