authorgravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2022-12-17 16:04:32-08:00
committergravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2022-12-18 02:48:34-08:00
loge9c48e663145910d4934c3d1d4249da20682ba90
treef7e20a0ba489b1988aa5360ad408586446fdb74c
parent3ee8c4958259efa07968818ae62ba6078e319d1b

spawnWindows: Improve worst-case performance considerably

The name of the game here is to avoid CreateProcessW calls at all costs, and only ever try calling it when we have a real candidate for execution. Secondarily, we want to minimize the number of syscalls used when checking for each PATHEXT-appended version of the app name. An overview of the technique used: - Open the search directory for iteration (either cwd or a path from PATH) - Use NtQueryDirectoryFile with a wildcard filename of `<app name>*` to check if anything that could possibly match either the unappended version of the app name or any of the versions with a PATHEXT value appended exists. - If the wildcard NtQueryDirectoryFile call found nothing, we can exit early without needing to use PATHEXT at all. This allows us to use a <open dir, NtQueryDirectoryFile, close dir> sequence for any directory that doesn't contain any possible matches, instead of having to use a separate look up for each individual filename combination (unappended + each PATHEXT appended). For directories where the wildcard *does* match something, we only need to do a maximum of <number of supported PATHEXT extensions> more NtQueryDirectoryFile calls. --- In addition, we now only evaluate the extensions in PATHEXT that we know we can handle (.COM, .EXE, .BAT, .CMD) and ignore the rest. --- This commit also makes two edge cases match Windows behavior: - If an app name has the extension .exe and it is attempted to be executed, that is now treated as unrecoverable and InvalidExe is immediately returned no matter where the .exe is (cwd or in the PATH). This matches the behavior of the Windows cmd.exe. - If the app name contains more than just a filename (e.g. it has path separators), then it is excluded from PATH searching and only does a cwd search. This matches the behavior of Windows cmd.exe.

2 files changed, 379 insertions(+), 82 deletions(-)

lib/std/child_process.zig+365-82
......@@ -946,109 +946,105 @@ pub const ChildProcess = struct {
946946 defer if (maybe_envp_buf) |envp_buf| self.allocator.free(envp_buf);
947947 const envp_ptr = if (maybe_envp_buf) |envp_buf| envp_buf.ptr else null;
948948
949 const app_name_utf8 = self.argv[0];
950 const app_name_is_absolute = fs.path.isAbsolute(app_name_utf8);
951
949952 // the cwd set in ChildProcess is in effect when choosing the executable path
950953 // to match posix semantics
951 const app_path = x: {
952 if (self.cwd) |cwd| {
953 const resolved = try fs.path.resolve(self.allocator, &[_][]const u8{ cwd, self.argv[0] });
954 defer self.allocator.free(resolved);
955 break :x try cstr.addNullByte(self.allocator, resolved);
954 var cwd_path_w_needs_free = false;
955 const cwd_path_w = x: {
956 // If the app name is absolute, then we need to use its dirname as the cwd
957 if (app_name_is_absolute) {
958 cwd_path_w_needs_free = true;
959 const dir = fs.path.dirname(app_name_utf8).?;
960 break :x try unicode.utf8ToUtf16LeWithNull(self.allocator, dir);
961 } else if (self.cwd) |cwd| {
962 cwd_path_w_needs_free = true;
963 break :x try unicode.utf8ToUtf16LeWithNull(self.allocator, cwd);
956964 } else {
957 break :x try cstr.addNullByte(self.allocator, self.argv[0]);
965 break :x &[_:0]u16{}; // empty for cwd
966 }
967 };
968 defer if (cwd_path_w_needs_free) self.allocator.free(cwd_path_w);
969
970 // If the app name has more than just a filename, then we need to separate that
971 // into the basename and dirname and use the dirname as an addition to the cwd
972 // path. This is because NtQueryDirectoryFile cannot accept FileName params with
973 // path separators.
974 const app_basename_utf8 = fs.path.basename(app_name_utf8);
975 // If the app name is absolute, then the cwd will already have the app's dirname in it,
976 // so only populate app_dirname if app name is a relative path with > 0 path separators.
977 const maybe_app_dirname_utf8 = if (!app_name_is_absolute) fs.path.dirname(app_name_utf8) else null;
978 const app_dirname_w: ?[:0]u16 = x: {
979 if (maybe_app_dirname_utf8) |app_dirname_utf8| {
980 break :x try unicode.utf8ToUtf16LeWithNull(self.allocator, app_dirname_utf8);
958981 }
982 break :x null;
959983 };
960 defer self.allocator.free(app_path);
984 defer if (app_dirname_w != null) self.allocator.free(app_dirname_w.?);
961985
962 const app_path_w = try unicode.utf8ToUtf16LeWithNull(self.allocator, app_path);
963 defer self.allocator.free(app_path_w);
986 const app_name_w = try unicode.utf8ToUtf16LeWithNull(self.allocator, app_basename_utf8);
987 defer self.allocator.free(app_name_w);
964988
965989 const cmd_line_w = try unicode.utf8ToUtf16LeWithNull(self.allocator, cmd_line);
966990 defer self.allocator.free(cmd_line_w);
967991
968992 exec: {
969 windowsCreateProcess(app_path_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_w_ptr, &siStartInfo, &piProcInfo) catch |no_path_err| {
970 switch (no_path_err) {
971 error.FileNotFound, error.InvalidExe => {},
972 else => |e| return e,
973 }
993 const PATH: [:0]const u16 = std.os.getenvW(unicode.utf8ToUtf16LeStringLiteral("PATH")) orelse &[_:0]u16{};
994 const PATHEXT: [:0]const u16 = std.os.getenvW(unicode.utf8ToUtf16LeStringLiteral("PATHEXT")) orelse &[_:0]u16{};
974995
975 const PATH: [:0]const u16 = std.os.getenvW(unicode.utf8ToUtf16LeStringLiteral("PATH")) orelse &[_:0]u16{};
976 const PATHEXT: [:0]const u16 = std.os.getenvW(unicode.utf8ToUtf16LeStringLiteral("PATHEXT")) orelse &[_:0]u16{};
977
978 var path_buf = std.ArrayListUnmanaged(u16){};
979 defer path_buf.deinit(self.allocator);
980
981 // Try again with PATHEXT's extensions appended
982 {
983 try path_buf.appendSlice(self.allocator, app_path_w);
984 var ext_it = mem.tokenize(u16, PATHEXT, &[_]u16{';'});
985 while (ext_it.next()) |ext| {
986 path_buf.shrinkRetainingCapacity(app_path_w.len);
987 try path_buf.appendSlice(self.allocator, ext);
988 try path_buf.append(self.allocator, 0);
989 const path_with_ext = path_buf.items[0 .. path_buf.items.len - 1 :0];
990
991 if (windowsCreateProcess(path_with_ext.ptr, cmd_line_w.ptr, envp_ptr, cwd_w_ptr, &siStartInfo, &piProcInfo)) |_| {
992 break :exec;
993 } else |err| switch (err) {
994 error.FileNotFound, error.AccessDenied, error.InvalidExe => {},
995 else => return err,
996 }
997 }
998 }
996 var app_buf = std.ArrayListUnmanaged(u16){};
997 defer app_buf.deinit(self.allocator);
999998
1000 // No need to search the PATH if the app path is absolute
1001 if (fs.path.isAbsoluteWindowsWTF16(app_path_w)) return no_path_err;
1002
1003 // app_path_w has the cwd prepended to it if cwd is non-null, so when
1004 // searching the PATH we should make sure we use the app_name verbatim.
1005 var app_name_w_needs_free = false;
1006 const app_name_w = x: {
1007 if (self.cwd) |_| {
1008 app_name_w_needs_free = true;
1009 break :x try unicode.utf8ToUtf16LeWithNull(self.allocator, self.argv[0]);
1010 } else {
1011 break :x app_path_w;
1012 }
999 try app_buf.appendSlice(self.allocator, app_name_w);
1000
1001 var dir_buf = std.ArrayListUnmanaged(u16){};
1002 defer dir_buf.deinit(self.allocator);
1003
1004 if (cwd_path_w.len > 0) {
1005 try dir_buf.appendSlice(self.allocator, cwd_path_w);
1006 }
1007 if (app_dirname_w) |app_dir| {
1008 if (dir_buf.items.len > 0) try dir_buf.append(self.allocator, fs.path.sep);
1009 try dir_buf.appendSlice(self.allocator, app_dir);
1010 }
1011 if (dir_buf.items.len > 0) {
1012 // Need to normalize the path, openDirW can't handle things like double backslashes
1013 const normalized_len = windows.normalizePath(u16, dir_buf.items) catch return error.BadPathName;
1014 dir_buf.shrinkRetainingCapacity(normalized_len);
1015 }
1016
1017 windowsCreateProcessPathExt(self.allocator, &dir_buf, &app_buf, PATHEXT, cmd_line_w.ptr, envp_ptr, cwd_w_ptr, &siStartInfo, &piProcInfo) catch |no_path_err| {
1018 var original_err = switch (no_path_err) {
1019 error.FileNotFound, error.InvalidExe, error.AccessDenied => |e| e,
1020 error.UnrecoverableInvalidExe => return error.InvalidExe,
1021 else => |e| return e,
10131022 };
1014 defer if (app_name_w_needs_free) self.allocator.free(app_name_w);
1023
1024 // If the app name had path separators, that disallows PATH searching,
1025 // and there's no need to search the PATH if the cwd path is absolute.
1026 if (app_dirname_w != null or fs.path.isAbsoluteWindowsWTF16(cwd_path_w)) {
1027 return original_err;
1028 }
10151029
10161030 var it = mem.tokenize(u16, PATH, &[_]u16{';'});
10171031 while (it.next()) |search_path| {
1018 path_buf.clearRetainingCapacity();
1019 const search_path_trimmed = mem.trimRight(u16, search_path, &[_]u16{ '\\', '/' });
1020 try path_buf.appendSlice(self.allocator, search_path_trimmed);
1021 try path_buf.append(self.allocator, fs.path.sep);
1022 const app_name_trimmed = mem.trimLeft(u16, app_name_w, &[_]u16{ '\\', '/' });
1023 try path_buf.appendSlice(self.allocator, app_name_trimmed);
1024 try path_buf.append(self.allocator, 0);
1025 const path_no_ext = path_buf.items[0 .. path_buf.items.len - 1 :0];
1026
1027 if (windowsCreateProcess(path_no_ext.ptr, cmd_line_w.ptr, envp_ptr, cwd_w_ptr, &siStartInfo, &piProcInfo)) |_| {
1032 dir_buf.clearRetainingCapacity();
1033 try dir_buf.appendSlice(self.allocator, search_path);
1034 // Need to normalize the path, some PATH values can contain things like double
1035 // backslashes which openDirW can't handle
1036 const normalized_len = windows.normalizePath(u16, dir_buf.items) catch continue;
1037 dir_buf.shrinkRetainingCapacity(normalized_len);
1038
1039 if (windowsCreateProcessPathExt(self.allocator, &dir_buf, &app_buf, PATHEXT, cmd_line_w.ptr, envp_ptr, cwd_w_ptr, &siStartInfo, &piProcInfo)) {
10281040 break :exec;
10291041 } else |err| switch (err) {
1030 error.FileNotFound, error.AccessDenied, error.InvalidExe => {},
1031 else => return err,
1032 }
1033
1034 var ext_it = mem.tokenize(u16, PATHEXT, &[_]u16{';'});
1035 while (ext_it.next()) |ext| {
1036 path_buf.shrinkRetainingCapacity(path_no_ext.len);
1037 try path_buf.appendSlice(self.allocator, ext);
1038 try path_buf.append(self.allocator, 0);
1039 const joined_path = path_buf.items[0 .. path_buf.items.len - 1 :0];
1040
1041 if (windowsCreateProcess(joined_path.ptr, cmd_line_w.ptr, envp_ptr, cwd_w_ptr, &siStartInfo, &piProcInfo)) |_| {
1042 break :exec;
1043 } else |err| switch (err) {
1044 error.FileNotFound => continue,
1045 error.AccessDenied => continue,
1046 error.InvalidExe => continue,
1047 else => return err,
1048 }
1042 error.FileNotFound, error.AccessDenied, error.InvalidExe => continue,
1043 error.UnrecoverableInvalidExe => return error.InvalidExe,
1044 else => |e| return e,
10491045 }
10501046 } else {
1051 return no_path_err; // return the original error
1047 return original_err;
10521048 }
10531049 };
10541050 }
......@@ -1094,6 +1090,235 @@ pub const ChildProcess = struct {
10941090 }
10951091};
10961092
1093/// Expects `app_buf` to contain exactly the app name, and `dir_buf` to contain exactly the dir path.
1094/// After return, `app_buf` will always contain exactly the app name and `dir_buf` will always contain exactly the dir path.
1095/// Note: `app_buf` should not contain any leading path separators.
1096/// Note: If the dir is the cwd, dir_buf should be empty (len = 0).
1097fn windowsCreateProcessPathExt(
1098 allocator: mem.Allocator,
1099 dir_buf: *std.ArrayListUnmanaged(u16),
1100 app_buf: *std.ArrayListUnmanaged(u16),
1101 pathext: [:0]const u16,
1102 cmd_line: [*:0]u16,
1103 envp_ptr: ?[*]u16,
1104 cwd_ptr: ?[*:0]u16,
1105 lpStartupInfo: *windows.STARTUPINFOW,
1106 lpProcessInformation: *windows.PROCESS_INFORMATION,
1107) !void {
1108 const app_name_len = app_buf.items.len;
1109 const dir_path_len = dir_buf.items.len;
1110
1111 if (app_name_len == 0) return error.FileNotFound;
1112
1113 defer app_buf.shrinkRetainingCapacity(app_name_len);
1114 defer dir_buf.shrinkRetainingCapacity(dir_path_len);
1115
1116 // The name of the game here is to avoid CreateProcessW calls at all costs,
1117 // and only ever try calling it when we have a real candidate for execution.
1118 // Secondarily, we want to minimize the number of syscalls used when checking
1119 // for each PATHEXT-appended version of the app name.
1120 //
1121 // An overview of the technique used:
1122 // - Open the search directory for iteration (either cwd or a path from PATH)
1123 // - Use NtQueryDirectoryFile with a wildcard filename of `<app name>*` to
1124 // check if anything that could possibly match either the unappended version
1125 // of the app name or any of the versions with a PATHEXT value appended exists.
1126 // - If the wildcard NtQueryDirectoryFile call found nothing, we can exit early
1127 // without needing to use PATHEXT at all.
1128 //
1129 // This allows us to use a <open dir, NtQueryDirectoryFile, close dir> sequence
1130 // for any directory that doesn't contain any possible matches, instead of having
1131 // to use a separate look up for each individual filename combination (unappended +
1132 // each PATHEXT appended). For directories where the wildcard *does* match something,
1133 // we only need to do a maximum of <number of supported PATHEXT extensions> more
1134 // NtQueryDirectoryFile calls.
1135
1136 var dir = dir: {
1137 if (fs.path.isAbsoluteWindowsWTF16(dir_buf.items[0..dir_path_len])) {
1138 const prefixed_path = try windows.wToPrefixedFileW(dir_buf.items[0..dir_path_len]);
1139 break :dir fs.cwd().openDirW(prefixed_path.span().ptr, .{}, true) catch return error.FileNotFound;
1140 }
1141 // needs to be null-terminated
1142 try dir_buf.append(allocator, 0);
1143 defer dir_buf.shrinkRetainingCapacity(dir_buf.items[0..dir_path_len].len);
1144 const dir_path_z = dir_buf.items[0 .. dir_buf.items.len - 1 :0];
1145 break :dir std.fs.cwd().openDirW(dir_path_z.ptr, .{}, true) catch return error.FileNotFound;
1146 };
1147 defer dir.close();
1148
1149 // Add wildcard and null-terminator
1150 try app_buf.append(allocator, '*');
1151 try app_buf.append(allocator, 0);
1152 const app_name_wildcard = app_buf.items[0 .. app_buf.items.len - 1 :0];
1153
1154 // Enough for the FILE_DIRECTORY_INFORMATION + (NAME_MAX UTF-16 code units [2 bytes each]).
1155 const file_info_buf_size = @sizeOf(windows.FILE_DIRECTORY_INFORMATION) + (windows.NAME_MAX * 2);
1156 var file_information_buf: [file_info_buf_size]u8 align(@alignOf(os.windows.FILE_DIRECTORY_INFORMATION)) = undefined;
1157 var io_status: windows.IO_STATUS_BLOCK = undefined;
1158 const found_name: ?[]const u16 = found_name: {
1159 const app_name_len_bytes = math.cast(u16, app_name_wildcard.len * 2) orelse return error.NameTooLong;
1160 var app_name_unicode_string = windows.UNICODE_STRING{
1161 .Length = app_name_len_bytes,
1162 .MaximumLength = app_name_len_bytes,
1163 .Buffer = @intToPtr([*]u16, @ptrToInt(app_name_wildcard.ptr)),
1164 };
1165 const rc = windows.ntdll.NtQueryDirectoryFile(
1166 dir.fd,
1167 null,
1168 null,
1169 null,
1170 &io_status,
1171 &file_information_buf,
1172 file_information_buf.len,
1173 .FileDirectoryInformation,
1174 // TODO: It might be better to iterate over all wildcard matches and
1175 // only pick the ones that match an appended PATHEXT instead of only
1176 // using the wildcard as a lookup and then restarting iteration
1177 // on future NtQueryDirectoryFile calls.
1178 //
1179 // However, note that this could lead to worse outcomes in the
1180 // case of a very generic command name (e.g. "a"), so it might
1181 // be better to only use the wildcard to determine if it's worth
1182 // checking with PATHEXT (this is the current behavior).
1183 windows.TRUE, // single result
1184 &app_name_unicode_string,
1185 windows.TRUE, // restart iteration
1186 );
1187
1188 // If we get nothing with the wildcard, then we can just bail out
1189 // as we know appending PATHEXT will not yield anything.
1190 switch (rc) {
1191 .SUCCESS => {},
1192 .NO_SUCH_FILE => return error.FileNotFound,
1193 .NO_MORE_FILES => return error.FileNotFound,
1194 .ACCESS_DENIED => return error.AccessDenied,
1195 else => return windows.unexpectedStatus(rc),
1196 }
1197
1198 const dir_info = @ptrCast(*windows.FILE_DIRECTORY_INFORMATION, &file_information_buf);
1199 if (dir_info.FileAttributes & windows.FILE_ATTRIBUTE_DIRECTORY != 0) {
1200 break :found_name null;
1201 }
1202 break :found_name @ptrCast([*]u16, &dir_info.FileName)[0 .. dir_info.FileNameLength / 2];
1203 };
1204
1205 const unappended_err = unappended: {
1206 // NtQueryDirectoryFile returns results in order by filename, so the first result of
1207 // the wildcard call will always be the unappended version if it exists. So, if found_name
1208 // is not the unappended version, we can skip straight to trying versions with PATHEXT appended.
1209 // TODO: This might depend on the filesystem, though; need to somehow verify that it always
1210 // works this way.
1211 if (found_name != null and windows.eqlIgnoreCaseWTF16(found_name.?, app_buf.items[0..app_name_len])) {
1212 if (dir_path_len != 0) switch (dir_buf.items[dir_buf.items.len - 1]) {
1213 '/', '\\' => {},
1214 else => try dir_buf.append(allocator, fs.path.sep),
1215 };
1216 try dir_buf.appendSlice(allocator, app_buf.items[0..app_name_len]);
1217 try dir_buf.append(allocator, 0);
1218 const full_app_name = dir_buf.items[0 .. dir_buf.items.len - 1 :0];
1219
1220 if (windowsCreateProcess(full_app_name.ptr, cmd_line, envp_ptr, cwd_ptr, lpStartupInfo, lpProcessInformation)) |_| {
1221 return;
1222 } else |err| switch (err) {
1223 error.FileNotFound,
1224 error.AccessDenied,
1225 => break :unappended err,
1226 error.InvalidExe => {
1227 // On InvalidExe, if the extension of the app name is .exe then
1228 // it's treated as an unrecoverable error. Otherwise, it'll be
1229 // skipped as normal.
1230 const app_name = app_buf.items[0..app_name_len];
1231 const ext_start = std.mem.lastIndexOfScalar(u16, app_name, '.') orelse break :unappended err;
1232 const ext = app_name[ext_start..];
1233 if (windows.eqlIgnoreCaseWTF16(ext, unicode.utf8ToUtf16LeStringLiteral(".EXE"))) {
1234 return error.UnrecoverableInvalidExe;
1235 }
1236 break :unappended err;
1237 },
1238 else => return err,
1239 }
1240 }
1241 break :unappended error.FileNotFound;
1242 };
1243
1244 // Now we know that at least *a* file matching the wildcard exists, we can loop
1245 // through PATHEXT in order and exec any that exist
1246
1247 var ext_it = mem.tokenize(u16, pathext, &[_]u16{';'});
1248 while (ext_it.next()) |ext| {
1249 if (!windowsCreateProcessSupportsExtension(ext)) continue;
1250
1251 app_buf.shrinkRetainingCapacity(app_name_len);
1252 try app_buf.appendSlice(allocator, ext);
1253 try app_buf.append(allocator, 0);
1254 const app_name_appended = app_buf.items[0 .. app_buf.items.len - 1 :0];
1255
1256 const app_name_len_bytes = math.cast(u16, app_name_appended.len * 2) orelse return error.NameTooLong;
1257 var app_name_unicode_string = windows.UNICODE_STRING{
1258 .Length = app_name_len_bytes,
1259 .MaximumLength = app_name_len_bytes,
1260 .Buffer = @intToPtr([*]u16, @ptrToInt(app_name_appended.ptr)),
1261 };
1262
1263 // Re-use the directory handle but this time we call with the appended app name
1264 // with no wildcard.
1265 const rc = windows.ntdll.NtQueryDirectoryFile(
1266 dir.fd,
1267 null,
1268 null,
1269 null,
1270 &io_status,
1271 &file_information_buf,
1272 file_information_buf.len,
1273 .FileDirectoryInformation,
1274 windows.TRUE, // single result
1275 &app_name_unicode_string,
1276 windows.TRUE, // restart iteration
1277 );
1278
1279 switch (rc) {
1280 .SUCCESS => {},
1281 .NO_SUCH_FILE => continue,
1282 .NO_MORE_FILES => continue,
1283 .ACCESS_DENIED => continue,
1284 else => return windows.unexpectedStatus(rc),
1285 }
1286
1287 const dir_info = @ptrCast(*windows.FILE_DIRECTORY_INFORMATION, &file_information_buf);
1288 // Skip directories
1289 if (dir_info.FileAttributes & windows.FILE_ATTRIBUTE_DIRECTORY != 0) continue;
1290
1291 dir_buf.shrinkRetainingCapacity(dir_path_len);
1292 if (dir_path_len != 0) switch (dir_buf.items[dir_buf.items.len - 1]) {
1293 '/', '\\' => {},
1294 else => try dir_buf.append(allocator, fs.path.sep),
1295 };
1296 try dir_buf.appendSlice(allocator, app_buf.items[0..app_name_len]);
1297 try dir_buf.appendSlice(allocator, ext);
1298 try dir_buf.append(allocator, 0);
1299 const full_app_name = dir_buf.items[0 .. dir_buf.items.len - 1 :0];
1300
1301 if (windowsCreateProcess(full_app_name.ptr, cmd_line, envp_ptr, cwd_ptr, lpStartupInfo, lpProcessInformation)) |_| {
1302 return;
1303 } else |err| switch (err) {
1304 error.FileNotFound => continue,
1305 error.AccessDenied => continue,
1306 error.InvalidExe => {
1307 // On InvalidExe, if the extension of the app name is .exe then
1308 // it's treated as an unrecoverable error. Otherwise, it'll be
1309 // skipped as normal.
1310 if (windows.eqlIgnoreCaseWTF16(ext, unicode.utf8ToUtf16LeStringLiteral(".EXE"))) {
1311 return error.UnrecoverableInvalidExe;
1312 }
1313 continue;
1314 },
1315 else => return err,
1316 }
1317 }
1318
1319 return unappended_err;
1320}
1321
10971322fn windowsCreateProcess(app_name: [*:0]u16, cmd_line: [*:0]u16, envp_ptr: ?[*]u16, cwd_ptr: ?[*:0]u16, lpStartupInfo: *windows.STARTUPINFOW, lpProcessInformation: *windows.PROCESS_INFORMATION) !void {
10981323 // TODO the docs for environment pointer say:
10991324 // > A pointer to the environment block for the new process. If this parameter
......@@ -1126,6 +1351,64 @@ fn windowsCreateProcess(app_name: [*:0]u16, cmd_line: [*:0]u16, envp_ptr: ?[*]u1
11261351 );
11271352}
11281353
1354/// Case-insenstive UTF-16 lookup
1355fn windowsCreateProcessSupportsExtension(ext: []const u16) bool {
1356 const State = enum {
1357 start,
1358 dot,
1359 b,
1360 ba,
1361 c,
1362 cm,
1363 co,
1364 e,
1365 ex,
1366 };
1367 var state: State = .start;
1368 for (ext) |c| switch (state) {
1369 .start => switch (c) {
1370 '.' => state = .dot,
1371 else => return false,
1372 },
1373 .dot => switch (c) {
1374 'b', 'B' => state = .b,
1375 'c', 'C' => state = .c,
1376 'e', 'E' => state = .e,
1377 else => return false,
1378 },
1379 .b => switch (c) {
1380 'a', 'A' => state = .ba,
1381 else => return false,
1382 },
1383 .c => switch (c) {
1384 'm', 'M' => state = .cm,
1385 'o', 'O' => state = .co,
1386 else => return false,
1387 },
1388 .e => switch (c) {
1389 'x', 'X' => state = .ex,
1390 else => return false,
1391 },
1392 .ba => switch (c) {
1393 't', 'T' => return true, // .BAT
1394 else => return false,
1395 },
1396 .cm => switch (c) {
1397 'd', 'D' => return true, // .CMD
1398 else => return false,
1399 },
1400 .co => switch (c) {
1401 'm', 'M' => return true, // .COM
1402 else => return false,
1403 },
1404 .ex => switch (c) {
1405 'e', 'E' => return true, // .EXE
1406 else => return false,
1407 },
1408 };
1409 return false;
1410}
1411
11291412/// Caller must dealloc.
11301413fn windowsCreateCommandLine(allocator: mem.Allocator, argv: []const []const u8) ![:0]u8 {
11311414 var buf = std.ArrayList(u8).init(allocator);
lib/std/os/windows.zig+14
......@@ -3702,6 +3702,20 @@ pub const RTL_DRIVE_LETTER_CURDIR = extern struct {
37023702
37033703pub const PPS_POST_PROCESS_INIT_ROUTINE = ?*const fn () callconv(.C) void;
37043704
3705pub const FILE_DIRECTORY_INFORMATION = extern struct {
3706 NextEntryOffset: ULONG,
3707 FileIndex: ULONG,
3708 CreationTime: LARGE_INTEGER,
3709 LastAccessTime: LARGE_INTEGER,
3710 LastWriteTime: LARGE_INTEGER,
3711 ChangeTime: LARGE_INTEGER,
3712 EndOfFile: LARGE_INTEGER,
3713 AllocationSize: LARGE_INTEGER,
3714 FileAttributes: ULONG,
3715 FileNameLength: ULONG,
3716 FileName: [1]WCHAR,
3717};
3718
37053719pub const FILE_BOTH_DIR_INFORMATION = extern struct {
37063720 NextEntryOffset: ULONG,
37073721 FileIndex: ULONG,