authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-01 14:53:21-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-04 00:27:08-08:00
log08447ca47ed2ece816de9b0c5735be3f17edc6ca
tree99b9fa99c03f259c31e7673d5515ae20aff07b5d
parentb64491f2d6e7b6d6d643ce1eff6d24873e414f08

std.fs.path: make relative a pure function

Instead of querying the operating system for current working directory and environment variables, this function now accepts those things as inputs.

15 files changed, 409 insertions(+), 432 deletions(-)

lib/compiler/build_runner.zig+1
...@@ -84,6 +84,7 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -84,6 +84,7 @@ pub fn main(init: process.Init.Minimal) !void {
84 .io = io,84 .io = io,
85 .gpa = arena,85 .gpa = arena,
86 .manifest_dir = try local_cache_directory.handle.createDirPathOpen(io, "h", .{}),86 .manifest_dir = try local_cache_directory.handle.createDirPathOpen(io, "h", .{}),
87 .cwd = try process.getCwdAlloc(single_threaded_arena.allocator()),
87 },88 },
88 .zig_exe = zig_exe,89 .zig_exe = zig_exe,
89 .env_map = try init.environ.createMap(arena),90 .env_map = try init.environ.createMap(arena),
lib/compiler/test_runner.zig+1-1
...@@ -38,7 +38,7 @@ pub fn main(init: std.process.Init.Minimal) void {...@@ -38,7 +38,7 @@ pub fn main(init: std.process.Init.Minimal) void {
38 }38 }
3939
40 if (need_simple) {40 if (need_simple) {
41 return mainSimple() catch @panic("test failure\n");41 return mainSimple() catch @panic("test failure");
42 }42 }
4343
44 const args = init.args.toSlice(fba.allocator()) catch @panic("unable to parse command line args");44 const args = init.args.toSlice(fba.allocator()) catch @panic("unable to parse command line args");
lib/std/Build.zig+1-2
...@@ -1738,8 +1738,7 @@ pub fn pathFromRoot(b: *Build, sub_path: []const u8) []u8 {...@@ -1738,8 +1738,7 @@ pub fn pathFromRoot(b: *Build, sub_path: []const u8) []u8 {
1738}1738}
17391739
1740fn pathFromCwd(b: *Build, sub_path: []const u8) []u8 {1740fn pathFromCwd(b: *Build, sub_path: []const u8) []u8 {
1741 const cwd = process.getCwdAlloc(b.allocator) catch @panic("OOM");1741 return b.pathResolve(&.{ b.graph.cache.cwd, sub_path });
1742 return b.pathResolve(&.{ cwd, sub_path });
1743}1742}
17441743
1745pub fn pathJoin(b: *Build, paths: []const []const u8) []u8 {1744pub fn pathJoin(b: *Build, paths: []const []const u8) []u8 {
lib/std/Build/Cache.zig+27-8
...@@ -30,6 +30,8 @@ mutex: Io.Mutex = .init,...@@ -30,6 +30,8 @@ mutex: Io.Mutex = .init,
30/// and usefulness of the cache for advanced use cases.30/// and usefulness of the cache for advanced use cases.
31prefixes_buffer: [4]Directory = undefined,31prefixes_buffer: [4]Directory = undefined,
32prefixes_len: usize = 0,32prefixes_len: usize = 0,
33/// Used to identify prefixes. References external memory.
34cwd: []const u8,
3335
34pub const Path = @import("Cache/Path.zig");36pub const Path = @import("Cache/Path.zig");
35pub const Directory = @import("Cache/Directory.zig");37pub const Directory = @import("Cache/Directory.zig");
...@@ -78,11 +80,12 @@ fn findPrefix(cache: *const Cache, file_path: []const u8) !PrefixedPath {...@@ -78,11 +80,12 @@ fn findPrefix(cache: *const Cache, file_path: []const u8) !PrefixedPath {
78/// Takes ownership of `resolved_path` on success.80/// Takes ownership of `resolved_path` on success.
79fn findPrefixResolved(cache: *const Cache, resolved_path: []u8) !PrefixedPath {81fn findPrefixResolved(cache: *const Cache, resolved_path: []u8) !PrefixedPath {
80 const gpa = cache.gpa;82 const gpa = cache.gpa;
83 const cwd = cache.cwd;
81 const prefixes_slice = cache.prefixes();84 const prefixes_slice = cache.prefixes();
82 var i: u8 = 1; // Start at 1 to skip over checking the null prefix.85 var i: u8 = 1; // Start at 1 to skip over checking the null prefix.
83 while (i < prefixes_slice.len) : (i += 1) {86 while (i < prefixes_slice.len) : (i += 1) {
84 const p = prefixes_slice[i].path.?;87 const p = prefixes_slice[i].path.?;
85 const sub_path = getPrefixSubpath(gpa, p, resolved_path) catch |err| switch (err) {88 const sub_path = getPrefixSubpath(gpa, cwd, p, resolved_path) catch |err| switch (err) {
86 error.NotASubPath => continue,89 error.NotASubPath => continue,
87 else => |e| return e,90 else => |e| return e,
88 };91 };
...@@ -100,10 +103,10 @@ fn findPrefixResolved(cache: *const Cache, resolved_path: []u8) !PrefixedPath {...@@ -100,10 +103,10 @@ fn findPrefixResolved(cache: *const Cache, resolved_path: []u8) !PrefixedPath {
100 };103 };
101}104}
102105
103fn getPrefixSubpath(allocator: Allocator, prefix: []const u8, path: []u8) ![]u8 {106fn getPrefixSubpath(gpa: Allocator, cwd: []const u8, prefix: []const u8, path: []u8) ![]u8 {
104 const relative = try std.fs.path.relative(allocator, prefix, path);107 const relative = try std.fs.path.relative(gpa, cwd, null, prefix, path);
105 errdefer allocator.free(relative);108 errdefer gpa.free(relative);
106 var component_iterator = std.fs.path.NativeComponentIterator.init(relative);109 var component_iterator: std.fs.path.NativeComponentIterator = .init(relative);
107 if (component_iterator.root() != null) {110 if (component_iterator.root() != null) {
108 return error.NotASubPath;111 return error.NotASubPath;
109 }112 }
...@@ -1307,11 +1310,14 @@ fn testGetCurrentFileTimestamp(io: Io, dir: Io.Dir) !Io.Timestamp {...@@ -1307,11 +1310,14 @@ fn testGetCurrentFileTimestamp(io: Io, dir: Io.Dir) !Io.Timestamp {
1307}1310}
13081311
1309test "cache file and then recall it" {1312test "cache file and then recall it" {
1310 const io = std.testing.io;1313 const io = testing.io;
13111314
1312 var tmp = testing.tmpDir(.{});1315 var tmp = testing.tmpDir(.{});
1313 defer tmp.cleanup();1316 defer tmp.cleanup();
13141317
1318 const cwd = try std.process.getCwdAlloc(testing.allocator);
1319 defer testing.allocator.free(cwd);
1320
1315 const temp_file = "test.txt";1321 const temp_file = "test.txt";
1316 const temp_manifest_dir = "temp_manifest_dir";1322 const temp_manifest_dir = "temp_manifest_dir";
13171323
...@@ -1331,6 +1337,7 @@ test "cache file and then recall it" {...@@ -1331,6 +1337,7 @@ test "cache file and then recall it" {
1331 .io = io,1337 .io = io,
1332 .gpa = testing.allocator,1338 .gpa = testing.allocator,
1333 .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}),1339 .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}),
1340 .cwd = cwd,
1334 };1341 };
1335 cache.addPrefix(.{ .path = null, .handle = tmp.dir });1342 cache.addPrefix(.{ .path = null, .handle = tmp.dir });
1336 defer cache.manifest_dir.close(io);1343 defer cache.manifest_dir.close(io);
...@@ -1371,11 +1378,14 @@ test "cache file and then recall it" {...@@ -1371,11 +1378,14 @@ test "cache file and then recall it" {
1371}1378}
13721379
1373test "check that changing a file makes cache fail" {1380test "check that changing a file makes cache fail" {
1374 const io = std.testing.io;1381 const io = testing.io;
13751382
1376 var tmp = testing.tmpDir(.{});1383 var tmp = testing.tmpDir(.{});
1377 defer tmp.cleanup();1384 defer tmp.cleanup();
13781385
1386 const cwd = try std.process.getCwdAlloc(testing.allocator);
1387 defer testing.allocator.free(cwd);
1388
1379 const temp_file = "cache_hash_change_file_test.txt";1389 const temp_file = "cache_hash_change_file_test.txt";
1380 const temp_manifest_dir = "cache_hash_change_file_manifest_dir";1390 const temp_manifest_dir = "cache_hash_change_file_manifest_dir";
1381 const original_temp_file_contents = "Hello, world!\n";1391 const original_temp_file_contents = "Hello, world!\n";
...@@ -1397,6 +1407,7 @@ test "check that changing a file makes cache fail" {...@@ -1397,6 +1407,7 @@ test "check that changing a file makes cache fail" {
1397 .io = io,1407 .io = io,
1398 .gpa = testing.allocator,1408 .gpa = testing.allocator,
1399 .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}),1409 .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}),
1410 .cwd = cwd,
1400 };1411 };
1401 cache.addPrefix(.{ .path = null, .handle = tmp.dir });1412 cache.addPrefix(.{ .path = null, .handle = tmp.dir });
1402 defer cache.manifest_dir.close(io);1413 defer cache.manifest_dir.close(io);
...@@ -1448,6 +1459,9 @@ test "no file inputs" {...@@ -1448,6 +1459,9 @@ test "no file inputs" {
1448 var tmp = testing.tmpDir(.{});1459 var tmp = testing.tmpDir(.{});
1449 defer tmp.cleanup();1460 defer tmp.cleanup();
14501461
1462 const cwd = try std.process.getCwdAlloc(testing.allocator);
1463 defer testing.allocator.free(cwd);
1464
1451 const temp_manifest_dir = "no_file_inputs_manifest_dir";1465 const temp_manifest_dir = "no_file_inputs_manifest_dir";
14521466
1453 var digest1: HexDigest = undefined;1467 var digest1: HexDigest = undefined;
...@@ -1457,6 +1471,7 @@ test "no file inputs" {...@@ -1457,6 +1471,7 @@ test "no file inputs" {
1457 .io = io,1471 .io = io,
1458 .gpa = testing.allocator,1472 .gpa = testing.allocator,
1459 .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}),1473 .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}),
1474 .cwd = cwd,
1460 };1475 };
1461 cache.addPrefix(.{ .path = null, .handle = tmp.dir });1476 cache.addPrefix(.{ .path = null, .handle = tmp.dir });
1462 defer cache.manifest_dir.close(io);1477 defer cache.manifest_dir.close(io);
...@@ -1489,11 +1504,14 @@ test "no file inputs" {...@@ -1489,11 +1504,14 @@ test "no file inputs" {
1489}1504}
14901505
1491test "Manifest with files added after initial hash work" {1506test "Manifest with files added after initial hash work" {
1492 const io = std.testing.io;1507 const io = testing.io;
14931508
1494 var tmp = testing.tmpDir(.{});1509 var tmp = testing.tmpDir(.{});
1495 defer tmp.cleanup();1510 defer tmp.cleanup();
14961511
1512 const cwd = try std.process.getCwdAlloc(testing.allocator);
1513 defer testing.allocator.free(cwd);
1514
1497 const temp_file1 = "cache_hash_post_file_test1.txt";1515 const temp_file1 = "cache_hash_post_file_test1.txt";
1498 const temp_file2 = "cache_hash_post_file_test2.txt";1516 const temp_file2 = "cache_hash_post_file_test2.txt";
1499 const temp_manifest_dir = "cache_hash_post_file_manifest_dir";1517 const temp_manifest_dir = "cache_hash_post_file_manifest_dir";
...@@ -1516,6 +1534,7 @@ test "Manifest with files added after initial hash work" {...@@ -1516,6 +1534,7 @@ test "Manifest with files added after initial hash work" {
1516 .io = io,1534 .io = io,
1517 .gpa = testing.allocator,1535 .gpa = testing.allocator,
1518 .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}),1536 .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}),
1537 .cwd = cwd,
1519 };1538 };
1520 cache.addPrefix(.{ .path = null, .handle = tmp.dir });1539 cache.addPrefix(.{ .path = null, .handle = tmp.dir });
1521 defer cache.manifest_dir.close(io);1540 defer cache.manifest_dir.close(io);
lib/std/Build/Step/Options.zig+4
...@@ -537,6 +537,9 @@ test Options {...@@ -537,6 +537,9 @@ test Options {
537 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);537 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
538 defer arena.deinit();538 defer arena.deinit();
539539
540 const cwd = try std.process.getCwdAlloc(std.testing.allocator);
541 defer std.testing.allocator.free(cwd);
542
540 var graph: std.Build.Graph = .{543 var graph: std.Build.Graph = .{
541 .io = io,544 .io = io,
542 .arena = arena.allocator(),545 .arena = arena.allocator(),
...@@ -544,6 +547,7 @@ test Options {...@@ -544,6 +547,7 @@ test Options {
544 .io = io,547 .io = io,
545 .gpa = arena.allocator(),548 .gpa = arena.allocator(),
546 .manifest_dir = Io.Dir.cwd(),549 .manifest_dir = Io.Dir.cwd(),
550 .cwd = cwd,
547 },551 },
548 .zig_exe = "test",552 .zig_exe = "test",
549 .env_map = std.process.Environ.Map.init(arena.allocator()),553 .env_map = std.process.Environ.Map.init(arena.allocator()),
lib/std/Build/Step/Run.zig+9-7
...@@ -750,28 +750,30 @@ fn checksContainStderr(checks: []const StdIo.Check) bool {...@@ -750,28 +750,30 @@ fn checksContainStderr(checks: []const StdIo.Check) bool {
750/// to make sure the child doesn't see paths relative to a cwd other than its own.750/// to make sure the child doesn't see paths relative to a cwd other than its own.
751fn convertPathArg(run: *Run, path: Build.Cache.Path) []const u8 {751fn convertPathArg(run: *Run, path: Build.Cache.Path) []const u8 {
752 const b = run.step.owner;752 const b = run.step.owner;
753 const path_str = path.toString(b.graph.arena) catch @panic("OOM");753 const graph = b.graph;
754 const arena = graph.arena;
755
756 const path_str = path.toString(arena) catch @panic("OOM");
754 if (Dir.path.isAbsolute(path_str)) {757 if (Dir.path.isAbsolute(path_str)) {
755 // Absolute paths don't need changing.758 // Absolute paths don't need changing.
756 return path_str;759 return path_str;
757 }760 }
758 const child_cwd_rel: []const u8 = rel: {761 const child_cwd_rel: []const u8 = rel: {
759 const child_lazy_cwd = run.cwd orelse break :rel path_str;762 const child_lazy_cwd = run.cwd orelse break :rel path_str;
760 const child_cwd = child_lazy_cwd.getPath3(b, &run.step).toString(b.graph.arena) catch @panic("OOM");763 const child_cwd = child_lazy_cwd.getPath3(b, &run.step).toString(arena) catch @panic("OOM");
761 // Convert it from relative to *our* cwd, to relative to the *child's* cwd.764 // Convert it from relative to *our* cwd, to relative to the *child's* cwd.
762 break :rel Dir.path.relative(b.graph.arena, child_cwd, path_str) catch @panic("OOM");765 break :rel Dir.path.relative(arena, graph.cache.cwd, &graph.env_map, child_cwd, path_str) catch @panic("OOM");
763 };766 };
764 // Not every path can be made relative, e.g. if the path and the child cwd are on different767 // Not every path can be made relative, e.g. if the path and the child cwd are on different
765 // disk designators on Windows. In that case, `relative` will return an absolute path which we can768 // disk designators on Windows. In that case, `relative` will return an absolute path which we can
766 // just return.769 // just return.
767 if (Dir.path.isAbsolute(child_cwd_rel)) {770 if (Dir.path.isAbsolute(child_cwd_rel)) return child_cwd_rel;
768 return child_cwd_rel;771
769 }
770 // We're not done yet. In some cases this path must be prefixed with './':772 // We're not done yet. In some cases this path must be prefixed with './':
771 // * On POSIX, the executable name cannot be a single component like 'foo'773 // * On POSIX, the executable name cannot be a single component like 'foo'
772 // * Some executables might treat a leading '-' like a flag, which we must avoid774 // * Some executables might treat a leading '-' like a flag, which we must avoid
773 // There's no harm in it, so just *always* apply this prefix.775 // There's no harm in it, so just *always* apply this prefix.
774 return Dir.path.join(b.graph.arena, &.{ ".", child_cwd_rel }) catch @panic("OOM");776 return Dir.path.join(arena, &.{ ".", child_cwd_rel }) catch @panic("OOM");
775}777}
776778
777const IndexedOutput = struct {779const IndexedOutput = struct {
lib/std/Build/Watch/FsEvents.zig+7-5
...@@ -43,6 +43,8 @@ dispatch_queue: dispatch_queue_t,...@@ -43,6 +43,8 @@ dispatch_queue: dispatch_queue_t,
43/// of writing. See the comment at the start of `wait` for details.43/// of writing. See the comment at the start of `wait` for details.
44since_event: FSEventStreamEventId,44since_event: FSEventStreamEventId,
4545
46cwd_path: []const u8,
47
46/// All of the symbols we pull from the `dlopen`ed CoreServices framework. If any of these symbols48/// All of the symbols we pull from the `dlopen`ed CoreServices framework. If any of these symbols
47/// is not present, `init` will close the framework and return an error.49/// is not present, `init` will close the framework and return an error.
48const ResolvedSymbols = struct {50const ResolvedSymbols = struct {
...@@ -78,7 +80,7 @@ const ResolvedSymbols = struct {...@@ -78,7 +80,7 @@ const ResolvedSymbols = struct {
78 kCFAllocatorUseContext: *const CFAllocatorRef,80 kCFAllocatorUseContext: *const CFAllocatorRef,
79};81};
8082
81pub fn init() error{ OpenFrameworkFailed, MissingCoreServicesSymbol }!FsEvents {83pub fn init(cwd_path: []const u8) error{ OpenFrameworkFailed, MissingCoreServicesSymbol }!FsEvents {
82 var core_services = std.DynLib.open("/System/Library/Frameworks/CoreServices.framework/CoreServices") catch84 var core_services = std.DynLib.open("/System/Library/Frameworks/CoreServices.framework/CoreServices") catch
83 return error.OpenFrameworkFailed;85 return error.OpenFrameworkFailed;
84 errdefer core_services.close();86 errdefer core_services.close();
...@@ -99,6 +101,7 @@ pub fn init() error{ OpenFrameworkFailed, MissingCoreServicesSymbol }!FsEvents {...@@ -99,6 +101,7 @@ pub fn init() error{ OpenFrameworkFailed, MissingCoreServicesSymbol }!FsEvents {
99 // Not `.since_now`, because this means we can init `FsEvents` *before* we do work in order101 // Not `.since_now`, because this means we can init `FsEvents` *before* we do work in order
100 // to notice any changes which happened during said work.102 // to notice any changes which happened during said work.
101 .since_event = resolved_symbols.FSEventsGetCurrentEventId(),103 .since_event = resolved_symbols.FSEventsGetCurrentEventId(),
104 .cwd_path = cwd_path,
102 };105 };
103}106}
104107
...@@ -120,9 +123,6 @@ pub fn setPaths(fse: *FsEvents, gpa: Allocator, steps: []const *std.Build.Step)...@@ -120,9 +123,6 @@ pub fn setPaths(fse: *FsEvents, gpa: Allocator, steps: []const *std.Build.Step)
120 defer fse.paths_arena = paths_arena_instance.state;123 defer fse.paths_arena = paths_arena_instance.state;
121 const paths_arena = paths_arena_instance.allocator();124 const paths_arena = paths_arena_instance.allocator();
122125
123 const cwd_path = try std.process.getCwdAlloc(gpa);
124 defer gpa.free(cwd_path);
125
126 var need_dirs: std.StringArrayHashMapUnmanaged(void) = .empty;126 var need_dirs: std.StringArrayHashMapUnmanaged(void) = .empty;
127 defer need_dirs.deinit(gpa);127 defer need_dirs.deinit(gpa);
128128
...@@ -131,7 +131,9 @@ pub fn setPaths(fse: *FsEvents, gpa: Allocator, steps: []const *std.Build.Step)...@@ -131,7 +131,9 @@ pub fn setPaths(fse: *FsEvents, gpa: Allocator, steps: []const *std.Build.Step)
131 // We take `step` by pointer for a slight memory optimization in a moment.131 // We take `step` by pointer for a slight memory optimization in a moment.
132 for (steps) |*step| {132 for (steps) |*step| {
133 for (step.*.inputs.table.keys(), step.*.inputs.table.values()) |path, *files| {133 for (step.*.inputs.table.keys(), step.*.inputs.table.values()) |path, *files| {
134 const resolved_dir = try std.fs.path.resolvePosix(paths_arena, &.{ cwd_path, path.root_dir.path orelse ".", path.sub_path });134 const resolved_dir = try std.fs.path.resolvePosix(paths_arena, &.{
135 fse.cwd_path, path.root_dir.path orelse ".", path.sub_path,
136 });
135 try need_dirs.put(gpa, resolved_dir, {});137 try need_dirs.put(gpa, resolved_dir, {});
136 for (files.items) |file_name| {138 for (files.items) |file_name| {
137 const watch_path = if (std.mem.eql(u8, file_name, "."))139 const watch_path = if (std.mem.eql(u8, file_name, "."))
lib/std/Build/WebServer.zig+3-9
...@@ -482,8 +482,8 @@ pub fn serveFile(...@@ -482,8 +482,8 @@ pub fn serveFile(
482 });482 });
483}483}
484pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []const Cache.Path) !void {484pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []const Cache.Path) !void {
485 const gpa = ws.gpa;485 const graph = ws.graph;
486 const io = ws.graph.io;486 const io = graph.io;
487487
488 var send_buffer: [0x4000]u8 = undefined;488 var send_buffer: [0x4000]u8 = undefined;
489 var response = try request.respondStreaming(&send_buffer, .{489 var response = try request.respondStreaming(&send_buffer, .{
...@@ -495,9 +495,6 @@ pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []cons...@@ -495,9 +495,6 @@ pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []cons
495 },495 },
496 });496 });
497497
498 var cached_cwd_path: ?[]const u8 = null;
499 defer if (cached_cwd_path) |p| gpa.free(p);
500
501 var archiver: std.tar.Writer = .{ .underlying_writer = &response.writer };498 var archiver: std.tar.Writer = .{ .underlying_writer = &response.writer };
502499
503 for (paths) |path| {500 for (paths) |path| {
...@@ -516,10 +513,7 @@ pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []cons...@@ -516,10 +513,7 @@ pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []cons
516 // resulting in modules named "" and "src". The compiler needs to tell the build system513 // resulting in modules named "" and "src". The compiler needs to tell the build system
517 // about the module graph so that the build system can correctly encode this information in514 // about the module graph so that the build system can correctly encode this information in
518 // the tar file.515 // the tar file.
519 archiver.prefix = path.root_dir.path orelse cwd: {516 archiver.prefix = path.root_dir.path orelse graph.cache.cwd;
520 if (cached_cwd_path == null) cached_cwd_path = try std.process.getCwdAlloc(gpa);
521 break :cwd cached_cwd_path.?;
522 };
523 try archiver.writeFile(path.sub_path, &file_reader, @intCast(stat.mtime.toSeconds()));517 try archiver.writeFile(path.sub_path, &file_reader, @intCast(stat.mtime.toSeconds()));
524 }518 }
525519
lib/std/Io/Threaded.zig+8-7
...@@ -86,14 +86,14 @@ pub const Argv0 = switch (native_os) {...@@ -86,14 +86,14 @@ pub const Argv0 = switch (native_os) {
8686
87const Environ = struct {87const Environ = struct {
88 /// Unmodified data directly from the OS.88 /// Unmodified data directly from the OS.
89 block: process.Environ.Block = &.{},89 process_environ: process.Environ = .empty,
90 /// Protected by `mutex`. Determines whether the other fields have been90 /// Protected by `mutex`. Determines whether the other fields have been
91 /// memoized based on `block`.91 /// memoized based on `process_environ`.
92 initialized: bool = false,92 initialized: bool = false,
93 /// Protected by `mutex`. Memoized based on `block`. Tracks whether the93 /// Protected by `mutex`. Memoized based on `process_environ`. Tracks whether the
94 /// environment variables are present, ignoring their value.94 /// environment variables are present, ignoring their value.
95 exist: Exist = .{},95 exist: Exist = .{},
96 /// Protected by `mutex`. Memoized based on `block`.96 /// Protected by `mutex`. Memoized based on `process_environ`.
97 string: String = .{},97 string: String = .{},
98 /// ZIG_PROGRESS98 /// ZIG_PROGRESS
99 zig_progress_handle: std.Progress.ParentFileError!u31 = error.EnvironmentVariableMissing,99 zig_progress_handle: std.Progress.ParentFileError!u31 = error.EnvironmentVariableMissing,
...@@ -1186,7 +1186,7 @@ pub fn init(...@@ -1186,7 +1186,7 @@ pub fn init(
1186 .have_signal_handler = false,1186 .have_signal_handler = false,
1187 .argv0 = options.argv0,1187 .argv0 = options.argv0,
1188 .worker_threads = .init(null),1188 .worker_threads = .init(null),
1189 .environ = .{ .block = options.environ.block },1189 .environ = .{ .process_environ = options.environ },
1190 .robust_cancel = options.robust_cancel,1190 .robust_cancel = options.robust_cancel,
1191 };1191 };
11921192
...@@ -12693,7 +12693,7 @@ fn scanEnviron(t: *Threaded) void {...@@ -12693,7 +12693,7 @@ fn scanEnviron(t: *Threaded) void {
12693 comptime assert(@sizeOf(Environ.String) == 0);12693 comptime assert(@sizeOf(Environ.String) == 0);
12694 }12694 }
12695 } else {12695 } else {
12696 for (t.environ.block) |opt_line| {12696 for (t.environ.process_environ.block) |opt_line| {
12697 const line = opt_line.?;12697 const line = opt_line.?;
12698 var line_i: usize = 0;12698 var line_i: usize = 0;
12699 while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {}12699 while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {}
...@@ -12837,7 +12837,7 @@ fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) proce...@@ -12837,7 +12837,7 @@ fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) proce
12837 .zig_progress_fd = prog_fd,12837 .zig_progress_fd = prog_fd,
12838 })).ptr;12838 })).ptr;
12839 }12839 }
12840 break :m (try process.Environ.createBlockPosix(.{ .block = t.environ.block }, arena, .{12840 break :m (try process.Environ.createBlockPosix(t.environ.process_environ, arena, .{
12841 .zig_progress_fd = prog_fd,12841 .zig_progress_fd = prog_fd,
12842 })).ptr;12842 })).ptr;
12843 };12843 };
...@@ -12934,6 +12934,7 @@ fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) proce...@@ -12934,6 +12934,7 @@ fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) proce
1293412934
12935 return .{12935 return .{
12936 .id = pid,12936 .id = pid,
12937 .thread_handle = {},
12937 .stdin = switch (options.stdin) {12938 .stdin = switch (options.stdin) {
12938 .pipe => .{ .handle = stdin_pipe[1] },12939 .pipe => .{ .handle = stdin_pipe[1] },
12939 else => null,12940 else => null,
lib/std/fs/path.zig+277-147
...@@ -13,16 +13,15 @@...@@ -13,16 +13,15 @@
13//! https://github.com/WebAssembly/wasi-filesystem/issues/17#issuecomment-143063935313//! https://github.com/WebAssembly/wasi-filesystem/issues/17#issuecomment-1430639353
1414
15const builtin = @import("builtin");15const builtin = @import("builtin");
16const native_os = builtin.target.os.tag;
17
16const std = @import("../std.zig");18const std = @import("../std.zig");
17const debug = std.debug;19const assert = std.debug.assert;
18const assert = debug.assert;
19const testing = std.testing;20const testing = std.testing;
20const mem = std.mem;21const mem = std.mem;
21const ascii = std.ascii;22const Allocator = std.mem.Allocator;
22const Allocator = mem.Allocator;23const eqlIgnoreCaseWtf8 = std.os.windows.eqlIgnoreCaseWtf8;
23const windows = std.os.windows;24const eqlIgnoreCaseWtf16 = std.os.windows.eqlIgnoreCaseWtf16;
24const process = std.process;
25const native_os = builtin.target.os.tag;
2625
27pub const sep_windows: u8 = '\\';26pub const sep_windows: u8 = '\\';
28pub const sep_posix: u8 = '/';27pub const sep_posix: u8 = '/';
...@@ -281,7 +280,7 @@ pub fn isAbsolute(path: []const u8) bool {...@@ -281,7 +280,7 @@ pub fn isAbsolute(path: []const u8) bool {
281}280}
282281
283fn isAbsoluteWindowsImpl(comptime T: type, path: []const T) bool {282fn isAbsoluteWindowsImpl(comptime T: type, path: []const T) bool {
284 return switch (windows.getWin32PathType(T, path)) {283 return switch (getWin32PathType(T, path)) {
285 // Unambiguously absolute284 // Unambiguously absolute
286 .drive_absolute, .unc_absolute, .local_device, .root_local_device => true,285 .drive_absolute, .unc_absolute, .local_device, .root_local_device => true,
287 // Unambiguously relative286 // Unambiguously relative
...@@ -515,13 +514,13 @@ test parsePathPosix {...@@ -515,13 +514,13 @@ test parsePathPosix {
515514
516pub fn WindowsPath2(comptime T: type) type {515pub fn WindowsPath2(comptime T: type) type {
517 return struct {516 return struct {
518 kind: windows.Win32PathType,517 kind: Win32PathType,
519 root: []const T,518 root: []const T,
520 };519 };
521}520}
522521
523pub fn parsePathWindows(comptime T: type, path: []const T) WindowsPath2(T) {522pub fn parsePathWindows(comptime T: type, path: []const T) WindowsPath2(T) {
524 const kind = windows.getWin32PathType(T, path);523 const kind = getWin32PathType(T, path);
525 const root = root: switch (kind) {524 const root = root: switch (kind) {
526 .drive_absolute, .drive_relative => {525 .drive_absolute, .drive_relative => {
527 const drive_letter_len = getDriveLetter(T, path).len;526 const drive_letter_len = getDriveLetter(T, path).len;
...@@ -731,7 +730,7 @@ fn parseUNC(comptime T: type, path: []const T) WindowsUNC(T) {...@@ -731,7 +730,7 @@ fn parseUNC(comptime T: type, path: []const T) WindowsUNC(T) {
731 // For the share, there can be any number of path separators between the server730 // For the share, there can be any number of path separators between the server
732 // and the share, so we want to skip over all of them instead of just looking for731 // and the share, so we want to skip over all of them instead of just looking for
733 // the first one.732 // the first one.
734 var it = std.mem.tokenizeAny(T, path[server_end + 1 ..], any_sep);733 var it = mem.tokenizeAny(T, path[server_end + 1 ..], any_sep);
735 const share = it.next() orelse return .{734 const share = it.next() orelse return .{
736 .server = path[2..server_end],735 .server = path[2..server_end],
737 .sep_after_server = true,736 .sep_after_server = true,
...@@ -803,8 +802,8 @@ const DiskDesignatorKind = enum { drive, unc };...@@ -803,8 +802,8 @@ const DiskDesignatorKind = enum { drive, unc };
803/// `p1` and `p2` are both assumed to be the `kind` provided.802/// `p1` and `p2` are both assumed to be the `kind` provided.
804fn compareDiskDesignators(comptime T: type, kind: DiskDesignatorKind, p1: []const T, p2: []const T) bool {803fn compareDiskDesignators(comptime T: type, kind: DiskDesignatorKind, p1: []const T, p2: []const T) bool {
805 const eql = switch (T) {804 const eql = switch (T) {
806 u8 => windows.eqlIgnoreCaseWtf8,805 u8 => eqlIgnoreCaseWtf8,
807 u16 => windows.eqlIgnoreCaseWtf16,806 u16 => eqlIgnoreCaseWtf16,
808 else => @compileError("only u8 (WTF-8) and u16 (WTF-16LE) is supported"),807 else => @compileError("only u8 (WTF-8) and u16 (WTF-16LE) is supported"),
809 };808 };
810 switch (kind) {809 switch (kind) {
...@@ -1094,10 +1093,14 @@ pub fn resolveWindows(allocator: Allocator, paths: []const []const u8) Allocator...@@ -1094,10 +1093,14 @@ pub fn resolveWindows(allocator: Allocator, paths: []const []const u8) Allocator
1094}1093}
10951094
1096/// This function is like a series of `cd` statements executed one after another.1095/// This function is like a series of `cd` statements executed one after another.
1096///
1097/// It resolves "." and ".." to the best of its ability, but will not convert relative paths to1097/// It resolves "." and ".." to the best of its ability, but will not convert relative paths to
1098/// an absolute path, use Io.Dir.realpath instead.1098/// an absolute path, use Io.Dir.realpath instead.
1099///
1099/// ".." components may persist in the resolved path if the resolved path is relative.1100/// ".." components may persist in the resolved path if the resolved path is relative.
1101///
1100/// The result does not have a trailing path separator.1102/// The result does not have a trailing path separator.
1103///
1101/// This function does not perform any syscalls. Executing this series of path1104/// This function does not perform any syscalls. Executing this series of path
1102/// lookups on the actual filesystem may produce different results due to1105/// lookups on the actual filesystem may produce different results due to
1103/// symlinks.1106/// symlinks.
...@@ -1494,25 +1497,54 @@ fn testBasenameWindows(input: []const u8, expected_output: []const u8) !void {...@@ -1494,25 +1497,54 @@ fn testBasenameWindows(input: []const u8, expected_output: []const u8) !void {
1494 try testing.expectEqualSlices(u8, expected_output, basenameWindows(input));1497 try testing.expectEqualSlices(u8, expected_output, basenameWindows(input));
1495}1498}
14961499
1497pub const RelativeError = std.process.GetCwdAllocError;1500/// Returns the non-absolute path from `from` to `to`.
14981501///
1499/// Returns the relative path from `from` to `to`. If `from` and `to` each1502/// Other than memory allocation, this is a pure function; the result solely
1500/// resolve to the same path (after calling `resolve` on each), a zero-length1503/// depends on the input parameters.
1501/// string is returned.1504///
1502/// On Windows, the result is not guaranteed to be relative, as the paths may be1505/// If `from` and `to` each resolve to the same path (after calling `resolve`
1503/// on different volumes. In that case, the result will be the canonicalized absolute1506/// on each), a zero-length string is returned.
1504/// path of `to`.1507///
1505pub fn relative(allocator: Allocator, from: []const u8, to: []const u8) RelativeError![]u8 {1508/// See `relativePosix` and `relativeWindows` for operating system specific
1509/// details and for how `env_map` is used.
1510pub fn relative(
1511 gpa: Allocator,
1512 cwd: []const u8,
1513 env_map: ?*const std.process.Environ.Map,
1514 from: []const u8,
1515 to: []const u8,
1516) Allocator.Error![]u8 {
1506 if (native_os == .windows) {1517 if (native_os == .windows) {
1507 return relativeWindows(allocator, from, to);1518 return relativeWindows(gpa, cwd, env_map, from, to);
1508 } else {1519 } else {
1509 return relativePosix(allocator, from, to);1520 return relativePosix(gpa, cwd, from, to);
1510 }1521 }
1511}1522}
15121523
1513pub fn relativeWindows(allocator: Allocator, from: []const u8, to: []const u8) ![]u8 {1524/// Returns the non-absolute path from `from` to `to` according to Windows rules.
1514 if (native_os != .windows) @compileError("this function relies on Windows-specific semantics");1525///
15151526/// Other than memory allocation, this is a pure function; the result solely
1527/// depends on the input parameters.
1528///
1529/// If `from` and `to` each resolve to the same path (after calling `resolve`
1530/// on each), a zero-length string is returned.
1531///
1532/// The result is not guaranteed to be relative, as the paths may be on
1533/// different volumes. In that case, the result will be the canonicalized
1534/// absolute path of `to`.
1535///
1536/// Per-drive CWDs are stored in special semi-hidden environment variables of
1537/// the format `=<drive-letter>:`, e.g. `=C:`. This type of CWD is purely a
1538/// shell concept, so there's no guarantee that it'll be set or that it'll even
1539/// be accurate. This is the only reason for the `env_map` parameter. `null` is
1540/// treated equivalent to the environment variable missing.
1541pub fn relativeWindows(
1542 gpa: Allocator,
1543 cwd: []const u8,
1544 env_map: ?*const std.process.Environ.Map,
1545 from: []const u8,
1546 to: []const u8,
1547) Allocator.Error![]u8 {
1516 const parsed_from = parsePathWindows(u8, from);1548 const parsed_from = parsePathWindows(u8, from);
1517 const parsed_to = parsePathWindows(u8, to);1549 const parsed_to = parsePathWindows(u8, to);
15181550
...@@ -1533,14 +1565,14 @@ pub fn relativeWindows(allocator: Allocator, from: []const u8, to: []const u8) !...@@ -1533,14 +1565,14 @@ pub fn relativeWindows(allocator: Allocator, from: []const u8, to: []const u8) !
1533 };1565 };
15341566
1535 if (result_is_always_to) {1567 if (result_is_always_to) {
1536 return windowsResolveAgainstCwd(allocator, to, parsed_to);1568 return windowsResolveAgainstCwd(gpa, cwd, env_map, to, parsed_to);
1537 }1569 }
15381570
1539 const resolved_from = try windowsResolveAgainstCwd(allocator, from, parsed_from);1571 const resolved_from = try windowsResolveAgainstCwd(gpa, cwd, env_map, from, parsed_from);
1540 defer allocator.free(resolved_from);1572 defer gpa.free(resolved_from);
1541 var clean_up_resolved_to = true;1573 var clean_up_resolved_to = true;
1542 const resolved_to = try windowsResolveAgainstCwd(allocator, to, parsed_to);1574 const resolved_to = try windowsResolveAgainstCwd(gpa, cwd, env_map, to, parsed_to);
1543 defer if (clean_up_resolved_to) allocator.free(resolved_to);1575 defer if (clean_up_resolved_to) gpa.free(resolved_to);
15441576
1545 const parsed_resolved_from = parsePathWindows(u8, resolved_from);1577 const parsed_resolved_from = parsePathWindows(u8, resolved_from);
1546 const parsed_resolved_to = parsePathWindows(u8, resolved_to);1578 const parsed_resolved_to = parsePathWindows(u8, resolved_to);
...@@ -1569,18 +1601,18 @@ pub fn relativeWindows(allocator: Allocator, from: []const u8, to: []const u8) !...@@ -1569,18 +1601,18 @@ pub fn relativeWindows(allocator: Allocator, from: []const u8, to: []const u8) !
1569 var from_it = mem.tokenizeAny(u8, resolved_from[parsed_resolved_from.root.len..], "/\\");1601 var from_it = mem.tokenizeAny(u8, resolved_from[parsed_resolved_from.root.len..], "/\\");
1570 var to_it = mem.tokenizeAny(u8, resolved_to[parsed_resolved_to.root.len..], "/\\");1602 var to_it = mem.tokenizeAny(u8, resolved_to[parsed_resolved_to.root.len..], "/\\");
1571 while (true) {1603 while (true) {
1572 const from_component = from_it.next() orelse return allocator.dupe(u8, to_it.rest());1604 const from_component = from_it.next() orelse return gpa.dupe(u8, to_it.rest());
1573 const to_rest = to_it.rest();1605 const to_rest = to_it.rest();
1574 if (to_it.next()) |to_component| {1606 if (to_it.next()) |to_component| {
1575 if (windows.eqlIgnoreCaseWtf8(from_component, to_component))1607 if (eqlIgnoreCaseWtf8(from_component, to_component))
1576 continue;1608 continue;
1577 }1609 }
1578 var up_index_end = "..".len;1610 var up_index_end = "..".len;
1579 while (from_it.next()) |_| {1611 while (from_it.next()) |_| {
1580 up_index_end += "\\..".len;1612 up_index_end += "\\..".len;
1581 }1613 }
1582 const result = try allocator.alloc(u8, up_index_end + @intFromBool(to_rest.len > 0) + to_rest.len);1614 const result = try gpa.alloc(u8, up_index_end + @intFromBool(to_rest.len > 0) + to_rest.len);
1583 errdefer allocator.free(result);1615 errdefer gpa.free(result);
15841616
1585 result[0..2].* = "..".*;1617 result[0..2].* = "..".*;
1586 var result_index: usize = 2;1618 var result_index: usize = 2;
...@@ -1597,85 +1629,60 @@ pub fn relativeWindows(allocator: Allocator, from: []const u8, to: []const u8) !...@@ -1597,85 +1629,60 @@ pub fn relativeWindows(allocator: Allocator, from: []const u8, to: []const u8) !
1597 result_index += to_component.len;1629 result_index += to_component.len;
1598 }1630 }
15991631
1600 return allocator.realloc(result, result_index);1632 return gpa.realloc(result, result_index);
1601 }1633 }
1602 return [_]u8{};1634 return [_]u8{};
1603}1635}
16041636
1605fn windowsResolveAgainstCwd(allocator: Allocator, path: []const u8, parsed: WindowsPath2(u8)) ![]u8 {1637fn windowsResolveAgainstCwd(
1638 gpa: Allocator,
1639 cwd: []const u8,
1640 env_map: ?*const std.process.Environ.Map,
1641 path: []const u8,
1642 parsed: WindowsPath2(u8),
1643) ![]u8 {
1606 // Space for 256 WTF-16 code units; potentially 3 WTF-8 bytes per WTF-16 code unit1644 // Space for 256 WTF-16 code units; potentially 3 WTF-8 bytes per WTF-16 code unit
1607 var temp_allocator_state = std.heap.stackFallback(256 * 3, allocator);1645 var temp_allocator_state = std.heap.stackFallback(256 * 3, gpa);
1608 return switch (parsed.kind) {1646 return switch (parsed.kind) {
1609 .drive_absolute,1647 .drive_absolute,
1610 .unc_absolute,1648 .unc_absolute,
1611 .root_local_device,1649 .root_local_device,
1612 .local_device,1650 .local_device,
1613 => try resolveWindows(allocator, &.{path}),1651 => try resolveWindows(gpa, &.{path}),
1614 .relative => blk: {
1615 const temp_allocator = temp_allocator_state.get();
16161652
1617 const peb_cwd = windows.peb().ProcessParameters.CurrentDirectory.DosPath;1653 .relative => try resolveWindows(gpa, &.{ cwd, path }),
1618 const cwd_w = (peb_cwd.Buffer.?)[0 .. peb_cwd.Length / 2];
16191654
1620 const wtf8_len = std.unicode.calcWtf8Len(cwd_w);
1621 const wtf8_buf = try temp_allocator.alloc(u8, wtf8_len);
1622 defer temp_allocator.free(wtf8_buf);
1623 assert(std.unicode.wtf16LeToWtf8(wtf8_buf, cwd_w) == wtf8_len);
1624
1625 break :blk try resolveWindows(allocator, &.{ wtf8_buf, path });
1626 },
1627 .rooted => blk: {1655 .rooted => blk: {
1628 const peb_cwd = windows.peb().ProcessParameters.CurrentDirectory.DosPath;1656 const parsed_cwd = parsePathWindows(u8, cwd);
1629 const cwd_w = (peb_cwd.Buffer.?)[0 .. peb_cwd.Length / 2];
1630 const parsed_cwd = parsePathWindows(u16, cwd_w);
1631 switch (parsed_cwd.kind) {1657 switch (parsed_cwd.kind) {
1632 .drive_absolute => {1658 .drive_absolute => {
1633 var drive_buf = "_:\\".*;1659 var drive_buf = "_:\\".*;
1634 drive_buf[0] = @truncate(cwd_w[0]);1660 drive_buf[0] = cwd[0];
1635 break :blk try resolveWindows(allocator, &.{ &drive_buf, path });1661 break :blk try resolveWindows(gpa, &.{ &drive_buf, path });
1636 },1662 },
1637 .unc_absolute => {1663 .unc_absolute => {
1638 const temp_allocator = temp_allocator_state.get();1664 break :blk try resolveWindows(gpa, &.{ parsed_cwd.root, path });
1639 var root_buf = try temp_allocator.alloc(u8, parsed_cwd.root.len * 3);
1640 defer temp_allocator.free(root_buf);
1641
1642 const wtf8_len = std.unicode.wtf16LeToWtf8(root_buf, parsed_cwd.root);
1643 const root = root_buf[0..wtf8_len];
1644 break :blk try resolveWindows(allocator, &.{ root, path });
1645 },1665 },
1646 // Effectively a malformed CWD, give up and just return a normalized path1666 // Effectively a malformed CWD, give up and just return a normalized path
1647 else => break :blk try resolveWindows(allocator, &.{path}),1667 else => break :blk try resolveWindows(gpa, &.{path}),
1648 }1668 }
1649 },1669 },
1650 .drive_relative => blk: {1670 .drive_relative => blk: {
1651 const temp_allocator = temp_allocator_state.get();1671 const temp_allocator = temp_allocator_state.get();
1652 const drive_cwd = drive_cwd: {1672 const drive_cwd = drive_cwd: {
1653 const peb_cwd = windows.peb().ProcessParameters.CurrentDirectory.DosPath;1673 const parsed_cwd = parsePathWindows(u8, cwd);
1654 const cwd_w = (peb_cwd.Buffer.?)[0 .. peb_cwd.Length / 2];
1655 const parsed_cwd = parsePathWindows(u16, cwd_w);
16561674
1657 if (parsed_cwd.kind == .drive_absolute) {1675 if (parsed_cwd.kind == .drive_absolute) {
1658 const drive_letter_w = parsed_cwd.root[0];1676 const drive_letter_w = parsed_cwd.root[0];
1659 const drive_letters_match = drive_letter_w <= 0x7F and1677 const drive_letters_match = drive_letter_w <= 0x7F and
1660 ascii.toUpper(@intCast(drive_letter_w)) == ascii.toUpper(parsed.root[0]);1678 std.ascii.toUpper(@intCast(drive_letter_w)) == std.ascii.toUpper(parsed.root[0]);
1661 if (drive_letters_match) {1679 if (drive_letters_match)
1662 const wtf8_len = std.unicode.calcWtf8Len(cwd_w);1680 break :drive_cwd cwd;
1663 const wtf8_buf = try temp_allocator.alloc(u8, wtf8_len);1681
1664 assert(std.unicode.wtf16LeToWtf8(wtf8_buf, cwd_w) == wtf8_len);1682 if (env_map) |m| {
1665 break :drive_cwd wtf8_buf[0..];1683 if (m.get(&.{ '=', parsed.root[0], ':' })) |v| {
1666 }1684 break :drive_cwd try temp_allocator.dupe(u8, v);
16671685 }
1668 // Per-drive CWD's are stored in special semi-hidden environment variables
1669 // of the format `=<drive-letter>:`, e.g. `=C:`. This type of CWD is
1670 // purely a shell concept, so there's no guarantee that it'll be set
1671 // or that it'll even be accurate.
1672 var key_buf = std.unicode.wtf8ToWtf16LeStringLiteral("=_:").*;
1673 key_buf[1] = parsed.root[0];
1674 if (std.process.getenvW(&key_buf)) |drive_cwd_w| {
1675 const wtf8_len = std.unicode.calcWtf8Len(drive_cwd_w);
1676 const wtf8_buf = try temp_allocator.alloc(u8, wtf8_len);
1677 assert(std.unicode.wtf16LeToWtf8(wtf8_buf, drive_cwd_w) == wtf8_len);
1678 break :drive_cwd wtf8_buf[0..];
1679 }1686 }
1680 }1687 }
16811688
...@@ -1686,16 +1693,20 @@ fn windowsResolveAgainstCwd(allocator: Allocator, path: []const u8, parsed: Wind...@@ -1686,16 +1693,20 @@ fn windowsResolveAgainstCwd(allocator: Allocator, path: []const u8, parsed: Wind
1686 break :drive_cwd drive_buf;1693 break :drive_cwd drive_buf;
1687 };1694 };
1688 defer temp_allocator.free(drive_cwd);1695 defer temp_allocator.free(drive_cwd);
1689 break :blk try resolveWindows(allocator, &.{ drive_cwd, path });1696 break :blk try resolveWindows(gpa, &.{ drive_cwd, path });
1690 },1697 },
1691 };1698 };
1692}1699}
16931700
1694pub fn relativePosix(allocator: Allocator, from: []const u8, to: []const u8) ![]u8 {1701/// Returns the non-absolute path from `from` to `to` according to Windows rules.
1695 if (native_os == .windows) @compileError("this function relies on semantics that do not apply to Windows");1702///
16961703/// Other than memory allocation, this is a pure function; the result solely
1697 const cwd = try process.getCwdAlloc(allocator);1704/// depends on the input parameters.
1698 defer allocator.free(cwd);1705///
1706/// If `from` and `to` each resolve to the same path (after calling `resolve`
1707/// on each), a zero-length string is returned.
1708///
1709pub fn relativePosix(allocator: Allocator, cwd: []const u8, from: []const u8, to: []const u8) Allocator.Error![]u8 {
1699 const resolved_from = try resolvePosix(allocator, &[_][]const u8{ cwd, from });1710 const resolved_from = try resolvePosix(allocator, &[_][]const u8{ cwd, from });
1700 defer allocator.free(resolved_from);1711 defer allocator.free(resolved_from);
1701 const resolved_to = try resolvePosix(allocator, &[_][]const u8{ cwd, to });1712 const resolved_to = try resolvePosix(allocator, &[_][]const u8{ cwd, to });
...@@ -1736,69 +1747,67 @@ pub fn relativePosix(allocator: Allocator, from: []const u8, to: []const u8) ![]...@@ -1736,69 +1747,67 @@ pub fn relativePosix(allocator: Allocator, from: []const u8, to: []const u8) ![]
1736}1747}
17371748
1738test relative {1749test relative {
1739 if (native_os == .windows) {1750 try testRelativeWindows("c:/blah\\blah", "d:/games", "D:\\games");
1740 try testRelativeWindows("c:/blah\\blah", "d:/games", "D:\\games");1751 try testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa", "..");
1741 try testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa", "..");1752 try testRelativeWindows("c:/aaaa/bbbb", "c:/cccc", "..\\..\\cccc");
1742 try testRelativeWindows("c:/aaaa/bbbb", "c:/cccc", "..\\..\\cccc");1753 try testRelativeWindows("c:/aaaa/bbbb", "C:/aaaa/bbbb", "");
1743 try testRelativeWindows("c:/aaaa/bbbb", "C:/aaaa/bbbb", "");1754 try testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa/cccc", "..\\cccc");
1744 try testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa/cccc", "..\\cccc");1755 try testRelativeWindows("c:/aaaa/", "c:/aaaa/cccc", "cccc");
1745 try testRelativeWindows("c:/aaaa/", "c:/aaaa/cccc", "cccc");1756 try testRelativeWindows("c:/", "c:\\aaaa\\bbbb", "aaaa\\bbbb");
1746 try testRelativeWindows("c:/", "c:\\aaaa\\bbbb", "aaaa\\bbbb");1757 try testRelativeWindows("c:/aaaa/bbbb", "d:\\", "D:\\");
1747 try testRelativeWindows("c:/aaaa/bbbb", "d:\\", "D:\\");1758 try testRelativeWindows("c:/AaAa/bbbb", "c:/aaaa/bbbb", "");
1748 try testRelativeWindows("c:/AaAa/bbbb", "c:/aaaa/bbbb", "");1759 try testRelativeWindows("c:/aaaaa/", "c:/aaaa/cccc", "..\\aaaa\\cccc");
1749 try testRelativeWindows("c:/aaaaa/", "c:/aaaa/cccc", "..\\aaaa\\cccc");1760 try testRelativeWindows("C:\\foo\\bar\\baz\\quux", "C:\\", "..\\..\\..\\..");
1750 try testRelativeWindows("C:\\foo\\bar\\baz\\quux", "C:\\", "..\\..\\..\\..");1761 try testRelativeWindows("C:\\foo\\test", "C:\\foo\\test\\bar\\package.json", "bar\\package.json");
1751 try testRelativeWindows("C:\\foo\\test", "C:\\foo\\test\\bar\\package.json", "bar\\package.json");1762 try testRelativeWindows("C:\\foo\\bar\\baz-quux", "C:\\foo\\bar\\baz", "..\\baz");
1752 try testRelativeWindows("C:\\foo\\bar\\baz-quux", "C:\\foo\\bar\\baz", "..\\baz");1763 try testRelativeWindows("C:\\foo\\bar\\baz", "C:\\foo\\bar\\baz-quux", "..\\baz-quux");
1753 try testRelativeWindows("C:\\foo\\bar\\baz", "C:\\foo\\bar\\baz-quux", "..\\baz-quux");1764 try testRelativeWindows("\\\\foo\\bar", "\\\\foo\\bar\\baz", "baz");
1754 try testRelativeWindows("\\\\foo\\bar", "\\\\foo\\bar\\baz", "baz");1765 try testRelativeWindows("\\\\foo\\bar\\baz", "\\\\foo\\bar", "..");
1755 try testRelativeWindows("\\\\foo\\bar\\baz", "\\\\foo\\bar", "..");1766 try testRelativeWindows("\\\\foo\\bar\\baz-quux", "\\\\foo\\bar\\baz", "..\\baz");
1756 try testRelativeWindows("\\\\foo\\bar\\baz-quux", "\\\\foo\\bar\\baz", "..\\baz");1767 try testRelativeWindows("\\\\foo/bar\\baz-quux", "//foo\\bar/baz", "..\\baz");
1757 try testRelativeWindows("\\\\foo/bar\\baz-quux", "//foo\\bar/baz", "..\\baz");1768 try testRelativeWindows("\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz-quux", "..\\baz-quux");
1758 try testRelativeWindows("\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz-quux", "..\\baz-quux");1769 try testRelativeWindows("C:\\baz-quux", "C:\\baz", "..\\baz");
1759 try testRelativeWindows("C:\\baz-quux", "C:\\baz", "..\\baz");1770 try testRelativeWindows("C:\\baz", "C:\\baz-quux", "..\\baz-quux");
1760 try testRelativeWindows("C:\\baz", "C:\\baz-quux", "..\\baz-quux");1771 try testRelativeWindows("\\\\foo\\baz-quux", "\\\\foo\\baz", "\\\\foo\\baz");
1761 try testRelativeWindows("\\\\foo\\baz-quux", "\\\\foo\\baz", "\\\\foo\\baz");1772 try testRelativeWindows("\\\\foo\\baz", "\\\\foo\\baz-quux", "\\\\foo\\baz-quux");
1762 try testRelativeWindows("\\\\foo\\baz", "\\\\foo\\baz-quux", "\\\\foo\\baz-quux");1773 try testRelativeWindows("C:\\baz", "\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz");
1763 try testRelativeWindows("C:\\baz", "\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz");1774 try testRelativeWindows("\\\\foo\\bar\\baz", "C:\\baz", "C:\\baz");
1764 try testRelativeWindows("\\\\foo\\bar\\baz", "C:\\baz", "C:\\baz");1775
17651776 try testRelativeWindows("c:blah\\blah", "c:foo", "..\\..\\foo");
1766 try testRelativeWindows("c:blah\\blah", "c:foo", "..\\..\\foo");1777 try testRelativeWindows("c:foo", "c:foo\\bar", "bar");
1767 try testRelativeWindows("c:foo", "c:foo\\bar", "bar");1778 try testRelativeWindows("\\blah\\blah", "\\foo", "..\\..\\foo");
1768 try testRelativeWindows("\\blah\\blah", "\\foo", "..\\..\\foo");1779 try testRelativeWindows("\\foo", "\\foo\\bar", "bar");
1769 try testRelativeWindows("\\foo", "\\foo\\bar", "bar");1780
17701781 try testRelativeWindows("a/b/c", "a\\b", "..");
1771 try testRelativeWindows("a/b/c", "a\\b", "..");1782 try testRelativeWindows("a/b/c", "a", "..\\..");
1772 try testRelativeWindows("a/b/c", "a", "..\\..");1783 try testRelativeWindows("a/b/c", "a\\b\\c\\d", "d");
1773 try testRelativeWindows("a/b/c", "a\\b\\c\\d", "d");1784
17741785 try testRelativeWindows("\\\\FOO\\bar\\baz", "\\\\foo\\BAR\\BAZ", "");
1775 try testRelativeWindows("\\\\FOO\\bar\\baz", "\\\\foo\\BAR\\BAZ", "");1786 // Unicode-aware case-insensitive path comparison
1776 // Unicode-aware case-insensitive path comparison1787 try testRelativeWindows("\\\\кириллица\\ελληνικά\\português", "\\\\КИРИЛЛИЦА\\ΕΛΛΗΝΙΚΆ\\PORTUGUÊS", "");
1777 try testRelativeWindows("\\\\кириллица\\ελληνικά\\português", "\\\\КИРИЛЛИЦА\\ΕΛΛΗΝΙΚΆ\\PORTUGUÊS", "");1788
1778 } else {1789 try testRelativePosix("/var/lib", "/var", "..");
1779 try testRelativePosix("/var/lib", "/var", "..");1790 try testRelativePosix("/var/lib", "/bin", "../../bin");
1780 try testRelativePosix("/var/lib", "/bin", "../../bin");1791 try testRelativePosix("/var/lib", "/var/lib", "");
1781 try testRelativePosix("/var/lib", "/var/lib", "");1792 try testRelativePosix("/var/lib", "/var/apache", "../apache");
1782 try testRelativePosix("/var/lib", "/var/apache", "../apache");1793 try testRelativePosix("/var/", "/var/lib", "lib");
1783 try testRelativePosix("/var/", "/var/lib", "lib");1794 try testRelativePosix("/", "/var/lib", "var/lib");
1784 try testRelativePosix("/", "/var/lib", "var/lib");1795 try testRelativePosix("/foo/test", "/foo/test/bar/package.json", "bar/package.json");
1785 try testRelativePosix("/foo/test", "/foo/test/bar/package.json", "bar/package.json");1796 try testRelativePosix("/Users/a/web/b/test/mails", "/Users/a/web/b", "../..");
1786 try testRelativePosix("/Users/a/web/b/test/mails", "/Users/a/web/b", "../..");1797 try testRelativePosix("/foo/bar/baz-quux", "/foo/bar/baz", "../baz");
1787 try testRelativePosix("/foo/bar/baz-quux", "/foo/bar/baz", "../baz");1798 try testRelativePosix("/foo/bar/baz", "/foo/bar/baz-quux", "../baz-quux");
1788 try testRelativePosix("/foo/bar/baz", "/foo/bar/baz-quux", "../baz-quux");1799 try testRelativePosix("/baz-quux", "/baz", "../baz");
1789 try testRelativePosix("/baz-quux", "/baz", "../baz");1800 try testRelativePosix("/baz", "/baz-quux", "../baz-quux");
1790 try testRelativePosix("/baz", "/baz-quux", "../baz-quux");
1791 }
1792}1801}
17931802
1794fn testRelativePosix(from: []const u8, to: []const u8, expected_output: []const u8) !void {1803fn testRelativePosix(from: []const u8, to: []const u8, expected_output: []const u8) !void {
1795 const result = try relativePosix(testing.allocator, from, to);1804 const result = try relativePosix(testing.allocator, ".", from, to);
1796 defer testing.allocator.free(result);1805 defer testing.allocator.free(result);
1797 try testing.expectEqualStrings(expected_output, result);1806 try testing.expectEqualStrings(expected_output, result);
1798}1807}
17991808
1800fn testRelativeWindows(from: []const u8, to: []const u8, expected_output: []const u8) !void {1809fn testRelativeWindows(from: []const u8, to: []const u8, expected_output: []const u8) !void {
1801 const result = try relativeWindows(testing.allocator, from, to);1810 const result = try relativeWindows(testing.allocator, ".", null, from, to);
1802 defer testing.allocator.free(result);1811 defer testing.allocator.free(result);
1803 try testing.expectEqualStrings(expected_output, result);1812 try testing.expectEqualStrings(expected_output, result);
1804}1813}
...@@ -2554,3 +2563,124 @@ pub const fmtAsUtf8Lossy = std.unicode.fmtUtf8;...@@ -2554,3 +2563,124 @@ pub const fmtAsUtf8Lossy = std.unicode.fmtUtf8;
2554/// a lossy conversion if the path contains any unpaired surrogates.2563/// a lossy conversion if the path contains any unpaired surrogates.
2555/// Unpaired surrogates are replaced by the replacement character (U+FFFD).2564/// Unpaired surrogates are replaced by the replacement character (U+FFFD).
2556pub const fmtWtf16LeAsUtf8Lossy = std.unicode.fmtUtf16Le;2565pub const fmtWtf16LeAsUtf8Lossy = std.unicode.fmtUtf16Le;
2566
2567/// Similar to `RTL_PATH_TYPE`, but without the `UNKNOWN` path type.
2568pub const Win32PathType = enum {
2569 /// `\\server\share\foo`
2570 unc_absolute,
2571 /// `C:\foo`
2572 drive_absolute,
2573 /// `C:foo`
2574 drive_relative,
2575 /// `\foo`
2576 rooted,
2577 /// `foo`
2578 relative,
2579 /// `\\.\foo`, `\\?\foo`
2580 local_device,
2581 /// `\\.`, `\\?`
2582 root_local_device,
2583};
2584
2585/// Get the path type of a Win32 namespace path.
2586/// Similar to `RtlDetermineDosPathNameType_U`.
2587/// If `T` is `u16`, then `path` should be encoded as WTF-16LE.
2588pub fn getWin32PathType(comptime T: type, path: []const T) Win32PathType {
2589 if (path.len < 1) return .relative;
2590
2591 const windows_path = std.fs.path.PathType.windows;
2592 if (windows_path.isSep(T, path[0])) {
2593 // \x
2594 if (path.len < 2 or !windows_path.isSep(T, path[1])) return .rooted;
2595 // \\. or \\?
2596 if (path.len > 2 and (path[2] == mem.nativeToLittle(T, '.') or path[2] == mem.nativeToLittle(T, '?'))) {
2597 // exactly \\. or \\? with nothing trailing
2598 if (path.len == 3) return .root_local_device;
2599 // \\.\x or \\?\x
2600 if (windows_path.isSep(T, path[3])) return .local_device;
2601 }
2602 // \\x
2603 return .unc_absolute;
2604 } else {
2605 // Some choice has to be made about how non-ASCII code points as drive-letters are handled, since
2606 // path[0] is a different size for WTF-16 vs WTF-8, leading to a potential mismatch in classification
2607 // for a WTF-8 path and its WTF-16 equivalent. For example, `€:\` encoded in WTF-16 is three code
2608 // units `<0x20AC>:\` whereas `€:\` encoded as WTF-8 is 6 code units `<0xE2><0x82><0xAC>:\` so
2609 // checking path[0], path[1] and path[2] would not behave the same between WTF-8/WTF-16.
2610 //
2611 // `RtlDetermineDosPathNameType_U` exclusively deals with WTF-16 and considers
2612 // `€:\` a drive-absolute path, but code points that take two WTF-16 code units to encode get
2613 // classified as a relative path (e.g. with U+20000 as the drive-letter that'd be encoded
2614 // in WTF-16 as `<0xD840><0xDC00>:\` and be considered a relative path).
2615 //
2616 // The choice made here is to emulate the behavior of `RtlDetermineDosPathNameType_U` for both
2617 // WTF-16 and WTF-8. This is because, while unlikely and not supported by the Disk Manager GUI,
2618 // drive letters are not actually restricted to A-Z. Using `SetVolumeMountPointW` will allow you
2619 // to set any byte value as a drive letter, and going through `IOCTL_MOUNTMGR_CREATE_POINT` will
2620 // allow you to set any WTF-16 code unit as a drive letter.
2621 //
2622 // Non-A-Z drive letters don't interact well with most of Windows, but certain things do work, e.g.
2623 // `cd /D €:\` will work, filesystem functions still work, etc.
2624 //
2625 // The unfortunate part of this is that this makes handling WTF-8 more complicated as we can't
2626 // just check path[0], path[1], path[2].
2627 const colon_i: usize = switch (T) {
2628 u8 => i: {
2629 const code_point_len = std.unicode.utf8ByteSequenceLength(path[0]) catch return .relative;
2630 // Conveniently, 4-byte sequences in WTF-8 have the same starting code point
2631 // as 2-code-unit sequences in WTF-16.
2632 if (code_point_len > 3) return .relative;
2633 break :i code_point_len;
2634 },
2635 u16 => 1,
2636 else => @compileError("unsupported type: " ++ @typeName(T)),
2637 };
2638 // x
2639 if (path.len < colon_i + 1 or path[colon_i] != mem.nativeToLittle(T, ':')) return .relative;
2640 // x:\
2641 if (path.len > colon_i + 1 and windows_path.isSep(T, path[colon_i + 1])) return .drive_absolute;
2642 // x:
2643 return .drive_relative;
2644 }
2645}
2646
2647test getWin32PathType {
2648 try std.testing.expectEqual(.relative, getWin32PathType(u8, ""));
2649 try std.testing.expectEqual(.relative, getWin32PathType(u8, "x"));
2650 try std.testing.expectEqual(.relative, getWin32PathType(u8, "x\\"));
2651
2652 try std.testing.expectEqual(.root_local_device, getWin32PathType(u8, "//."));
2653 try std.testing.expectEqual(.root_local_device, getWin32PathType(u8, "/\\?"));
2654 try std.testing.expectEqual(.root_local_device, getWin32PathType(u8, "\\\\?"));
2655
2656 try std.testing.expectEqual(.local_device, getWin32PathType(u8, "//./x"));
2657 try std.testing.expectEqual(.local_device, getWin32PathType(u8, "/\\?\\x"));
2658 try std.testing.expectEqual(.local_device, getWin32PathType(u8, "\\\\?\\x"));
2659 // local device paths require a path separator after the root, otherwise it is considered a UNC path
2660 try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "\\\\?x"));
2661 try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "//.x"));
2662
2663 try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "//"));
2664 try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "\\\\x"));
2665 try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "//x"));
2666
2667 try std.testing.expectEqual(.rooted, getWin32PathType(u8, "\\x"));
2668 try std.testing.expectEqual(.rooted, getWin32PathType(u8, "/"));
2669
2670 try std.testing.expectEqual(.drive_relative, getWin32PathType(u8, "x:"));
2671 try std.testing.expectEqual(.drive_relative, getWin32PathType(u8, "x:abc"));
2672 try std.testing.expectEqual(.drive_relative, getWin32PathType(u8, "x:a/b/c"));
2673
2674 try std.testing.expectEqual(.drive_absolute, getWin32PathType(u8, "x:\\"));
2675 try std.testing.expectEqual(.drive_absolute, getWin32PathType(u8, "x:\\abc"));
2676 try std.testing.expectEqual(.drive_absolute, getWin32PathType(u8, "x:/a/b/c"));
2677
2678 // Non-ASCII code point that is encoded as one WTF-16 code unit is considered a valid drive letter
2679 try std.testing.expectEqual(.drive_absolute, getWin32PathType(u8, "€:\\"));
2680 try std.testing.expectEqual(.drive_absolute, getWin32PathType(u16, std.unicode.wtf8ToWtf16LeStringLiteral("€:\\")));
2681 try std.testing.expectEqual(.drive_relative, getWin32PathType(u8, "€:"));
2682 try std.testing.expectEqual(.drive_relative, getWin32PathType(u16, std.unicode.wtf8ToWtf16LeStringLiteral("€:")));
2683 // But code points that are encoded as two WTF-16 code units are not
2684 try std.testing.expectEqual(.relative, getWin32PathType(u8, "\u{10000}:\\"));
2685 try std.testing.expectEqual(.relative, getWin32PathType(u16, std.unicode.wtf8ToWtf16LeStringLiteral("\u{10000}:\\")));
2686}
lib/std/os/windows.zig+7-128
...@@ -2927,7 +2927,7 @@ pub fn CreateSymbolicLink(...@@ -2927,7 +2927,7 @@ pub fn CreateSymbolicLink(
2927 // Already an NT path, no need to do anything to it2927 // Already an NT path, no need to do anything to it
2928 break :target_path target_path;2928 break :target_path target_path;
2929 } else {2929 } else {
2930 switch (getWin32PathType(u16, target_path)) {2930 switch (std.fs.path.getWin32PathType(u16, target_path)) {
2931 // Rooted paths need to avoid getting put through wToPrefixedFileW2931 // Rooted paths need to avoid getting put through wToPrefixedFileW
2932 // (and they are treated as relative in this context)2932 // (and they are treated as relative in this context)
2933 // Note: It seems that rooted paths in symbolic links are relative to2933 // Note: It seems that rooted paths in symbolic links are relative to
...@@ -4235,7 +4235,7 @@ pub const RemoveDotDirsError = error{TooManyParentDirs};...@@ -4235,7 +4235,7 @@ pub const RemoveDotDirsError = error{TooManyParentDirs};
4235/// 2) all repeating back slashes have been collapsed4235/// 2) all repeating back slashes have been collapsed
4236/// 3) the path is a relative one (does not start with a back slash)4236/// 3) the path is a relative one (does not start with a back slash)
4237pub fn removeDotDirsSanitized(comptime T: type, path: []T) RemoveDotDirsError!usize {4237pub fn removeDotDirsSanitized(comptime T: type, path: []T) RemoveDotDirsError!usize {
4238 std.debug.assert(path.len == 0 or path[0] != '\\');4238 assert(path.len == 0 or path[0] != '\\');
42394239
4240 var write_idx: usize = 0;4240 var write_idx: usize = 0;
4241 var read_idx: usize = 0;4241 var read_idx: usize = 0;
...@@ -4251,7 +4251,7 @@ pub fn removeDotDirsSanitized(comptime T: type, path: []T) RemoveDotDirsError!us...@@ -4251,7 +4251,7 @@ pub fn removeDotDirsSanitized(comptime T: type, path: []T) RemoveDotDirsError!us
4251 }4251 }
4252 if (after_dot == '.' and (read_idx + 2 == path.len or path[read_idx + 2] == '\\')) {4252 if (after_dot == '.' and (read_idx + 2 == path.len or path[read_idx + 2] == '\\')) {
4253 if (write_idx == 0) return error.TooManyParentDirs;4253 if (write_idx == 0) return error.TooManyParentDirs;
4254 std.debug.assert(write_idx >= 2);4254 assert(write_idx >= 2);
4255 write_idx -= 1;4255 write_idx -= 1;
4256 while (true) {4256 while (true) {
4257 write_idx -= 1;4257 write_idx -= 1;
...@@ -4353,7 +4353,7 @@ pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) Wtf16ToPrefixedFileWE...@@ -4353,7 +4353,7 @@ pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) Wtf16ToPrefixedFileWE
4353 path_space.data[path_space.len] = 0;4353 path_space.data[path_space.len] = 0;
4354 return path_space;4354 return path_space;
4355 } else {4355 } else {
4356 const path_type = getWin32PathType(u16, path);4356 const path_type = std.fs.path.getWin32PathType(u16, path);
4357 var path_space: PathSpace = undefined;4357 var path_space: PathSpace = undefined;
4358 if (path_type == .local_device) {4358 if (path_type == .local_device) {
4359 switch (getLocalDevicePathType(u16, path)) {4359 switch (getLocalDevicePathType(u16, path)) {
...@@ -4491,8 +4491,8 @@ pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) Wtf16ToPrefixedFileWE...@@ -4491,8 +4491,8 @@ pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) Wtf16ToPrefixedFileWE
4491 if (path_type == .unc_absolute) {4491 if (path_type == .unc_absolute) {
4492 // Now add in the UNC, the `C` should overwrite the first `\` of the4492 // Now add in the UNC, the `C` should overwrite the first `\` of the
4493 // FullPathName, ultimately resulting in `\??\UNC\<the rest of the path>`4493 // FullPathName, ultimately resulting in `\??\UNC\<the rest of the path>`
4494 std.debug.assert(path_space.data[path_buf_offset] == '\\');4494 assert(path_space.data[path_buf_offset] == '\\');
4495 std.debug.assert(path_space.data[path_buf_offset + 1] == '\\');4495 assert(path_space.data[path_buf_offset + 1] == '\\');
4496 const unc = [_]u16{ 'U', 'N', 'C' };4496 const unc = [_]u16{ 'U', 'N', 'C' };
4497 path_space.data[nt_prefix.len..][0..unc.len].* = unc;4497 path_space.data[nt_prefix.len..][0..unc.len].* = unc;
4498 }4498 }
...@@ -4500,127 +4500,6 @@ pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) Wtf16ToPrefixedFileWE...@@ -4500,127 +4500,6 @@ pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) Wtf16ToPrefixedFileWE
4500 }4500 }
4501}4501}
45024502
4503/// Similar to `RTL_PATH_TYPE`, but without the `UNKNOWN` path type.
4504pub const Win32PathType = enum {
4505 /// `\\server\share\foo`
4506 unc_absolute,
4507 /// `C:\foo`
4508 drive_absolute,
4509 /// `C:foo`
4510 drive_relative,
4511 /// `\foo`
4512 rooted,
4513 /// `foo`
4514 relative,
4515 /// `\\.\foo`, `\\?\foo`
4516 local_device,
4517 /// `\\.`, `\\?`
4518 root_local_device,
4519};
4520
4521/// Get the path type of a Win32 namespace path.
4522/// Similar to `RtlDetermineDosPathNameType_U`.
4523/// If `T` is `u16`, then `path` should be encoded as WTF-16LE.
4524pub fn getWin32PathType(comptime T: type, path: []const T) Win32PathType {
4525 if (path.len < 1) return .relative;
4526
4527 const windows_path = std.fs.path.PathType.windows;
4528 if (windows_path.isSep(T, path[0])) {
4529 // \x
4530 if (path.len < 2 or !windows_path.isSep(T, path[1])) return .rooted;
4531 // \\. or \\?
4532 if (path.len > 2 and (path[2] == mem.nativeToLittle(T, '.') or path[2] == mem.nativeToLittle(T, '?'))) {
4533 // exactly \\. or \\? with nothing trailing
4534 if (path.len == 3) return .root_local_device;
4535 // \\.\x or \\?\x
4536 if (windows_path.isSep(T, path[3])) return .local_device;
4537 }
4538 // \\x
4539 return .unc_absolute;
4540 } else {
4541 // Some choice has to be made about how non-ASCII code points as drive-letters are handled, since
4542 // path[0] is a different size for WTF-16 vs WTF-8, leading to a potential mismatch in classification
4543 // for a WTF-8 path and its WTF-16 equivalent. For example, `€:\` encoded in WTF-16 is three code
4544 // units `<0x20AC>:\` whereas `€:\` encoded as WTF-8 is 6 code units `<0xE2><0x82><0xAC>:\` so
4545 // checking path[0], path[1] and path[2] would not behave the same between WTF-8/WTF-16.
4546 //
4547 // `RtlDetermineDosPathNameType_U` exclusively deals with WTF-16 and considers
4548 // `€:\` a drive-absolute path, but code points that take two WTF-16 code units to encode get
4549 // classified as a relative path (e.g. with U+20000 as the drive-letter that'd be encoded
4550 // in WTF-16 as `<0xD840><0xDC00>:\` and be considered a relative path).
4551 //
4552 // The choice made here is to emulate the behavior of `RtlDetermineDosPathNameType_U` for both
4553 // WTF-16 and WTF-8. This is because, while unlikely and not supported by the Disk Manager GUI,
4554 // drive letters are not actually restricted to A-Z. Using `SetVolumeMountPointW` will allow you
4555 // to set any byte value as a drive letter, and going through `IOCTL_MOUNTMGR_CREATE_POINT` will
4556 // allow you to set any WTF-16 code unit as a drive letter.
4557 //
4558 // Non-A-Z drive letters don't interact well with most of Windows, but certain things do work, e.g.
4559 // `cd /D €:\` will work, filesystem functions still work, etc.
4560 //
4561 // The unfortunate part of this is that this makes handling WTF-8 more complicated as we can't
4562 // just check path[0], path[1], path[2].
4563 const colon_i: usize = switch (T) {
4564 u8 => i: {
4565 const code_point_len = std.unicode.utf8ByteSequenceLength(path[0]) catch return .relative;
4566 // Conveniently, 4-byte sequences in WTF-8 have the same starting code point
4567 // as 2-code-unit sequences in WTF-16.
4568 if (code_point_len > 3) return .relative;
4569 break :i code_point_len;
4570 },
4571 u16 => 1,
4572 else => @compileError("unsupported type: " ++ @typeName(T)),
4573 };
4574 // x
4575 if (path.len < colon_i + 1 or path[colon_i] != mem.nativeToLittle(T, ':')) return .relative;
4576 // x:\
4577 if (path.len > colon_i + 1 and windows_path.isSep(T, path[colon_i + 1])) return .drive_absolute;
4578 // x:
4579 return .drive_relative;
4580 }
4581}
4582
4583test getWin32PathType {
4584 try std.testing.expectEqual(.relative, getWin32PathType(u8, ""));
4585 try std.testing.expectEqual(.relative, getWin32PathType(u8, "x"));
4586 try std.testing.expectEqual(.relative, getWin32PathType(u8, "x\\"));
4587
4588 try std.testing.expectEqual(.root_local_device, getWin32PathType(u8, "//."));
4589 try std.testing.expectEqual(.root_local_device, getWin32PathType(u8, "/\\?"));
4590 try std.testing.expectEqual(.root_local_device, getWin32PathType(u8, "\\\\?"));
4591
4592 try std.testing.expectEqual(.local_device, getWin32PathType(u8, "//./x"));
4593 try std.testing.expectEqual(.local_device, getWin32PathType(u8, "/\\?\\x"));
4594 try std.testing.expectEqual(.local_device, getWin32PathType(u8, "\\\\?\\x"));
4595 // local device paths require a path separator after the root, otherwise it is considered a UNC path
4596 try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "\\\\?x"));
4597 try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "//.x"));
4598
4599 try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "//"));
4600 try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "\\\\x"));
4601 try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "//x"));
4602
4603 try std.testing.expectEqual(.rooted, getWin32PathType(u8, "\\x"));
4604 try std.testing.expectEqual(.rooted, getWin32PathType(u8, "/"));
4605
4606 try std.testing.expectEqual(.drive_relative, getWin32PathType(u8, "x:"));
4607 try std.testing.expectEqual(.drive_relative, getWin32PathType(u8, "x:abc"));
4608 try std.testing.expectEqual(.drive_relative, getWin32PathType(u8, "x:a/b/c"));
4609
4610 try std.testing.expectEqual(.drive_absolute, getWin32PathType(u8, "x:\\"));
4611 try std.testing.expectEqual(.drive_absolute, getWin32PathType(u8, "x:\\abc"));
4612 try std.testing.expectEqual(.drive_absolute, getWin32PathType(u8, "x:/a/b/c"));
4613
4614 // Non-ASCII code point that is encoded as one WTF-16 code unit is considered a valid drive letter
4615 try std.testing.expectEqual(.drive_absolute, getWin32PathType(u8, "€:\\"));
4616 try std.testing.expectEqual(.drive_absolute, getWin32PathType(u16, std.unicode.wtf8ToWtf16LeStringLiteral("€:\\")));
4617 try std.testing.expectEqual(.drive_relative, getWin32PathType(u8, "€:"));
4618 try std.testing.expectEqual(.drive_relative, getWin32PathType(u16, std.unicode.wtf8ToWtf16LeStringLiteral("€:")));
4619 // But code points that are encoded as two WTF-16 code units are not
4620 try std.testing.expectEqual(.relative, getWin32PathType(u8, "\u{10000}:\\"));
4621 try std.testing.expectEqual(.relative, getWin32PathType(u16, std.unicode.wtf8ToWtf16LeStringLiteral("\u{10000}:\\")));
4622}
4623
4624/// Returns true if the path starts with `\??\`, which is indicative of an NT path4503/// Returns true if the path starts with `\??\`, which is indicative of an NT path
4625/// but is not enough to fully distinguish between NT paths and Win32 paths, as4504/// but is not enough to fully distinguish between NT paths and Win32 paths, as
4626/// `\??\` is not actually a distinct prefix but rather the path to a special virtual4505/// `\??\` is not actually a distinct prefix but rather the path to a special virtual
...@@ -4663,7 +4542,7 @@ const LocalDevicePathType = enum {...@@ -4663,7 +4542,7 @@ const LocalDevicePathType = enum {
4663/// Asserts `path` is of type `Win32PathType.local_device`.4542/// Asserts `path` is of type `Win32PathType.local_device`.
4664fn getLocalDevicePathType(comptime T: type, path: []const T) LocalDevicePathType {4543fn getLocalDevicePathType(comptime T: type, path: []const T) LocalDevicePathType {
4665 if (std.debug.runtime_safety) {4544 if (std.debug.runtime_safety) {
4666 std.debug.assert(getWin32PathType(T, path) == .local_device);4545 assert(std.fs.path.getWin32PathType(T, path) == .local_device);
4667 }4546 }
46684547
4669 const backslash = mem.nativeToLittle(T, '\\');4548 const backslash = mem.nativeToLittle(T, '\\');
lib/std/process/Args.zig+2-1
...@@ -12,6 +12,7 @@ vector: Vector,...@@ -12,6 +12,7 @@ vector: Vector,
1212
13pub const Vector = switch (native_os) {13pub const Vector = switch (native_os) {
14 .windows => []const u16, // WTF-16 encoded14 .windows => []const u16, // WTF-16 encoded
15 .freestanding, .other => void,
15 else => []const [*:0]const u8,16 else => []const [*:0]const u8,
16};17};
1718
...@@ -57,7 +58,7 @@ pub const Iterator = struct {...@@ -57,7 +58,7 @@ pub const Iterator = struct {
57 /// Returned slice is pointing to the iterator's internal buffer.58 /// Returned slice is pointing to the iterator's internal buffer.
58 /// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).59 /// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
59 /// On other platforms, the result is an opaque sequence of bytes with no particular encoding.60 /// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
60 pub fn next(it: *Iterator) ?([:0]const u8) {61 pub fn next(it: *Iterator) ?[:0]const u8 {
61 return it.inner.next();62 return it.inner.next();
62 }63 }
6364
lib/std/process/Child.zig+1-1
...@@ -21,7 +21,7 @@ pub const Id = switch (native_os) {...@@ -21,7 +21,7 @@ pub const Id = switch (native_os) {
21/// On Windows this is the hProcess.21/// On Windows this is the hProcess.
22/// On POSIX this is the pid.22/// On POSIX this is the pid.
23id: ?Id,23id: ?Id,
24thread_handle: if (native_os == .windows) std.os.windows.HANDLE else void = {},24thread_handle: if (native_os == .windows) std.os.windows.HANDLE else void,
25/// The writing end of the child process's standard input pipe.25/// The writing end of the child process's standard input pipe.
26/// Usage requires `process.SpawnOptions.StdIo.pipe`.26/// Usage requires `process.SpawnOptions.StdIo.pipe`.
27stdin: ?File,27stdin: ?File,
lib/std/process/Environ.zig+9-7
...@@ -12,11 +12,6 @@ const posix = std.posix;...@@ -12,11 +12,6 @@ const posix = std.posix;
12const mem = std.mem;12const mem = std.mem;
1313
14/// Unmodified, unprocessed data provided by the operating system.14/// Unmodified, unprocessed data provided by the operating system.
15///
16/// On Windows this might point to memory in the PEB.
17///
18/// On WASI without libc, this is void because the environment has to be
19/// queried and heap-allocated at runtime.
20block: Block,15block: Block,
2116
22pub const empty: Environ = .{17pub const empty: Environ = .{
...@@ -26,12 +21,19 @@ pub const empty: Environ = .{...@@ -26,12 +21,19 @@ pub const empty: Environ = .{
26 },21 },
27};22};
2823
24/// On WASI without libc, this is `void` because the environment has to be
25/// queried and heap-allocated at runtime.
26///
27/// On Windows, the memory pointed at by the PEB changes when the environment
28/// is modified, so a long-lived pointer cannot be used. Therefore, on this
29/// operating system `void` is also used.
29pub const Block = switch (native_os) {30pub const Block = switch (native_os) {
30 .windows => [*:0]const u16,31 .windows => void,
31 .wasi => switch (builtin.link_libc) {32 .wasi => switch (builtin.link_libc) {
32 false => void,33 false => void,
33 true => [:null]const ?[*:0]const u8,34 true => [:null]const ?[*:0]const u8,
34 },35 },
36 .freestanding, .other => void,
35 else => [:null]const ?[*:0]const u8,37 else => [:null]const ?[*:0]const u8,
36};38};
3739
...@@ -345,7 +347,7 @@ pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map {...@@ -345,7 +347,7 @@ pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map {
345 errdefer result.deinit();347 errdefer result.deinit();
346348
347 if (native_os == .windows) {349 if (native_os == .windows) {
348 const ptr = env.block;350 const ptr = std.os.windows.peb().ProcessParameters.Environment;
349351
350 var i: usize = 0;352 var i: usize = 0;
351 while (ptr[i] != 0) {353 while (ptr[i] != 0) {
lib/std/start.zig+52-109
...@@ -11,118 +11,63 @@ const native_os = builtin.os.tag;...@@ -11,118 +11,63 @@ const native_os = builtin.os.tag;
1111
12const start_sym_name = if (native_arch.isMIPS()) "__start" else "_start";12const start_sym_name = if (native_arch.isMIPS()) "__start" else "_start";
1313
14// The self-hosted compiler is not fully capable of handling all of this start.zig file.
15// Until then, we have simplified logic here for self-hosted. TODO remove this once
16// self-hosted is capable enough to handle all of the real start.zig logic.
17pub const simplified_logic = switch (builtin.zig_backend) {
18 .stage2_aarch64,
19 .stage2_arm,
20 .stage2_powerpc,
21 .stage2_sparc64,
22 .stage2_spirv,
23 .stage2_x86,
24 => true,
25 else => false,
26};
27
28comptime {14comptime {
29 // No matter what, we import the root file, so that any export, test, comptime15 // No matter what, we import the root file, so that any export, test, comptime
30 // decls there get run.16 // decls there get run.
31 _ = root;17 _ = root;
3218
33 if (simplified_logic) {19 if (builtin.output_mode == .Lib and builtin.link_mode == .dynamic) {
34 if (builtin.output_mode == .Exe) {20 if (native_os == .windows and !@hasDecl(root, "_DllMainCRTStartup")) {
35 if ((builtin.link_libc or builtin.object_format == .c) and @hasDecl(root, "main")) {21 @export(&_DllMainCRTStartup, .{ .name = "_DllMainCRTStartup" });
36 if (!@typeInfo(@TypeOf(root.main)).@"fn".calling_convention.eql(.c)) {
37 @export(&main2, .{ .name = "main" });
38 }
39 } else if (builtin.os.tag == .windows) {
40 if (!@hasDecl(root, "wWinMainCRTStartup") and !@hasDecl(root, "mainCRTStartup")) {
41 @export(&wWinMainCRTStartup2, .{ .name = "wWinMainCRTStartup" });
42 }
43 } else if (builtin.os.tag == .opencl or builtin.os.tag == .vulkan) {
44 if (@hasDecl(root, "main"))
45 @export(&spirvMain2, .{ .name = "main" });
46 } else {
47 if (!@hasDecl(root, "_start")) {
48 @export(&_start2, .{ .name = "_start" });
49 }
50 }
51 }22 }
52 } else {23 } else if (builtin.output_mode == .Exe or @hasDecl(root, "main")) {
53 if (builtin.output_mode == .Lib and builtin.link_mode == .dynamic) {24 if (builtin.link_libc and @hasDecl(root, "main")) {
54 if (native_os == .windows and !@hasDecl(root, "_DllMainCRTStartup")) {25 if (native_arch.isWasm()) {
55 @export(&_DllMainCRTStartup, .{ .name = "_DllMainCRTStartup" });26 @export(&mainWithoutEnv, .{ .name = "__main_argc_argv" });
27 } else if (!@typeInfo(@TypeOf(root.main)).@"fn".calling_convention.eql(.c)) {
28 @export(&main, .{ .name = "main" });
56 }29 }
57 } else if (builtin.output_mode == .Exe or @hasDecl(root, "main")) {30 } else if (native_os == .windows and builtin.link_libc and @hasDecl(root, "wWinMain")) {
58 if (builtin.link_libc and @hasDecl(root, "main")) {31 if (!@typeInfo(@TypeOf(root.wWinMain)).@"fn".calling_convention.eql(.c)) {
59 if (native_arch.isWasm()) {32 @export(&wWinMain, .{ .name = "wWinMain" });
60 @export(&mainWithoutEnv, .{ .name = "__main_argc_argv" });33 }
61 } else if (!@typeInfo(@TypeOf(root.main)).@"fn".calling_convention.eql(.c)) {34 } else if (native_os == .windows) {
62 @export(&main, .{ .name = "main" });35 if (!@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup") and
63 }36 !@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup"))
64 } else if (native_os == .windows and builtin.link_libc and @hasDecl(root, "wWinMain")) {37 {
65 if (!@typeInfo(@TypeOf(root.wWinMain)).@"fn".calling_convention.eql(.c)) {38 @export(&WinStartup, .{ .name = "wWinMainCRTStartup" });
66 @export(&wWinMain, .{ .name = "wWinMain" });39 } else if (@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup") and
67 }40 !@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup"))
68 } else if (native_os == .windows) {41 {
69 if (!@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup") and42 @compileError("WinMain not supported; declare wWinMain or main instead");
70 !@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup"))43 } else if (@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup") and
71 {44 !@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup"))
72 @export(&WinStartup, .{ .name = "wWinMainCRTStartup" });45 {
73 } else if (@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup") and46 @export(&wWinMainCRTStartup, .{ .name = "wWinMainCRTStartup" });
74 !@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup"))47 }
75 {48 } else if (native_os == .uefi) {
76 @compileError("WinMain not supported; declare wWinMain or main instead");49 if (!@hasDecl(root, "EfiMain")) @export(&EfiMain, .{ .name = "EfiMain" });
77 } else if (@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup") and50 } else if (native_os == .wasi) {
78 !@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup"))51 const wasm_start_sym = switch (builtin.wasi_exec_model) {
79 {52 .reactor => "_initialize",
80 @export(&wWinMainCRTStartup, .{ .name = "wWinMainCRTStartup" });53 .command => "_start",
81 }54 };
82 } else if (native_os == .uefi) {55 if (!@hasDecl(root, wasm_start_sym) and @hasDecl(root, "main")) {
83 if (!@hasDecl(root, "EfiMain")) @export(&EfiMain, .{ .name = "EfiMain" });
84 } else if (native_os == .wasi) {
85 const wasm_start_sym = switch (builtin.wasi_exec_model) {
86 .reactor => "_initialize",
87 .command => "_start",
88 };
89 if (!@hasDecl(root, wasm_start_sym) and @hasDecl(root, "main")) {
90 // Only call main when defined. For WebAssembly it's allowed to pass `-fno-entry` in which
91 // case it's not required to provide an entrypoint such as main.
92 @export(&wasi_start, .{ .name = wasm_start_sym });
93 }
94 } else if (native_arch.isWasm() and native_os == .freestanding) {
95 // Only call main when defined. For WebAssembly it's allowed to pass `-fno-entry` in which56 // Only call main when defined. For WebAssembly it's allowed to pass `-fno-entry` in which
96 // case it's not required to provide an entrypoint such as main.57 // case it's not required to provide an entrypoint such as main.
97 if (!@hasDecl(root, start_sym_name) and @hasDecl(root, "main")) @export(&wasm_freestanding_start, .{ .name = start_sym_name });58 @export(&wasi_start, .{ .name = wasm_start_sym });
98 } else switch (native_os) {
99 .other, .freestanding, .@"3ds", .vita => {},
100 else => if (!@hasDecl(root, start_sym_name)) @export(&_start, .{ .name = start_sym_name }),
101 }59 }
60 } else if (native_arch.isWasm() and native_os == .freestanding) {
61 // Only call main when defined. For WebAssembly it's allowed to pass `-fno-entry` in which
62 // case it's not required to provide an entrypoint such as main.
63 if (!@hasDecl(root, start_sym_name) and @hasDecl(root, "main")) @export(&wasm_freestanding_start, .{ .name = start_sym_name });
64 } else switch (native_os) {
65 .other, .freestanding, .@"3ds", .vita => {},
66 else => if (!@hasDecl(root, start_sym_name)) @export(&_start, .{ .name = start_sym_name }),
102 }67 }
103 }68 }
104}69}
10570
106// Simplified start code for stage2 until it supports more language features ///
107
108fn main2() callconv(.c) c_int {
109 return callMain();
110}
111
112fn _start2() callconv(.withStackAlign(.c, 1)) noreturn {
113 std.process.exit(callMain());
114}
115
116fn spirvMain2() callconv(.kernel) void {
117 root.main();
118}
119
120fn wWinMainCRTStartup2() callconv(.c) noreturn {
121 std.process.exit(callMain());
122}
123
124////////////////////////////////////////////////////////////////////////////////
125
126fn _DllMainCRTStartup(71fn _DllMainCRTStartup(
127 hinstDLL: std.os.windows.HINSTANCE,72 hinstDLL: std.os.windows.HINSTANCE,
128 fdwReason: std.os.windows.DWORD,73 fdwReason: std.os.windows.DWORD,
...@@ -142,15 +87,15 @@ fn _DllMainCRTStartup(...@@ -142,15 +87,15 @@ fn _DllMainCRTStartup(
142fn wasm_freestanding_start() callconv(.c) void {87fn wasm_freestanding_start() callconv(.c) void {
143 // This is marked inline because for some reason LLVM in88 // This is marked inline because for some reason LLVM in
144 // release mode fails to inline it, and we want fewer call frames in stack traces.89 // release mode fails to inline it, and we want fewer call frames in stack traces.
145 _ = @call(.always_inline, callMain, .{});90 _ = @call(.always_inline, callMain, .{ {}, {} });
146}91}
14792
148fn wasi_start() callconv(.c) void {93fn wasi_start() callconv(.c) void {
149 // The function call is marked inline because for some reason LLVM in94 // The function call is marked inline because for some reason LLVM in
150 // release mode fails to inline it, and we want fewer call frames in stack traces.95 // release mode fails to inline it, and we want fewer call frames in stack traces.
151 switch (builtin.wasi_exec_model) {96 switch (builtin.wasi_exec_model) {
152 .reactor => _ = @call(.always_inline, callMain, .{}),97 .reactor => _ = @call(.always_inline, callMain, .{ {}, {} }),
153 .command => std.os.wasi.proc_exit(@call(.always_inline, callMain, .{})),98 .command => std.os.wasi.proc_exit(@call(.always_inline, callMain, .{ {}, {} })),
154 }99 }
155}100}
156101
...@@ -524,13 +469,10 @@ fn WinStartup() callconv(.withStackAlign(.c, 1)) noreturn {...@@ -524,13 +469,10 @@ fn WinStartup() callconv(.withStackAlign(.c, 1)) noreturn {
524469
525 std.debug.maybeEnableSegfaultHandler();470 std.debug.maybeEnableSegfaultHandler();
526471
527 const peb = std.os.windows.peb();
528 const cmd_line = std.os.windows.peb().ProcessParameters.CommandLine;472 const cmd_line = std.os.windows.peb().ProcessParameters.CommandLine;
473 const cmd_line_w = cmd_line.Buffer.?[0..@divExact(cmd_line.Length, 2)];
529474
530 std.os.windows.ntdll.RtlExitUserProcess(callMain(475 std.os.windows.ntdll.RtlExitUserProcess(callMain(cmd_line_w, {}));
531 cmd_line.Buffer.?[0..@divExact(cmd_line.Length, 2)],
532 peb.ProcessParameters.Environment,
533 ));
534}476}
535477
536fn wWinMainCRTStartup() callconv(.withStackAlign(.c, 1)) noreturn {478fn wWinMainCRTStartup() callconv(.withStackAlign(.c, 1)) noreturn {
...@@ -637,6 +579,7 @@ fn posixCallMainAndExit(argc_argv_ptr: [*]usize) callconv(.c) noreturn {...@@ -637,6 +579,7 @@ fn posixCallMainAndExit(argc_argv_ptr: [*]usize) callconv(.c) noreturn {
637}579}
638580
639fn expandStackSize(phdrs: []elf.Phdr) void {581fn expandStackSize(phdrs: []elf.Phdr) void {
582 @disableInstrumentation();
640 for (phdrs) |*phdr| {583 for (phdrs) |*phdr| {
641 switch (phdr.p_type) {584 switch (phdr.p_type) {
642 elf.PT_GNU_STACK => {585 elf.PT_GNU_STACK => {
...@@ -674,7 +617,7 @@ fn expandStackSize(phdrs: []elf.Phdr) void {...@@ -674,7 +617,7 @@ fn expandStackSize(phdrs: []elf.Phdr) void {
674inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [:null]?[*:0]u8) u8 {617inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [:null]?[*:0]u8) u8 {
675 if (std.Options.debug_threaded_io) |t| {618 if (std.Options.debug_threaded_io) |t| {
676 if (@sizeOf(std.Io.Threaded.Argv0) != 0) t.argv0.value = argv[0];619 if (@sizeOf(std.Io.Threaded.Argv0) != 0) t.argv0.value = argv[0];
677 t.environ = .{ .block = envp };620 t.environ = .{ .process_environ = .{ .block = envp } };
678 }621 }
679 std.debug.maybeEnableSegfaultHandler();622 std.debug.maybeEnableSegfaultHandler();
680 return callMain(argv[0..argc], envp);623 return callMain(argv[0..argc], envp);
...@@ -735,8 +678,8 @@ inline fn callMain(args: std.process.Args.Vector, environ: std.process.Environ.B...@@ -735,8 +678,8 @@ inline fn callMain(args: std.process.Args.Vector, environ: std.process.Environ.B
735 defer arena_allocator.deinit();678 defer arena_allocator.deinit();
736679
737 var threaded: std.Io.Threaded = .init(gpa, .{680 var threaded: std.Io.Threaded = .init(gpa, .{
738 .argv0 = if (@sizeOf(std.Io.Threaded.Argv0) != 0) .{ .value = args[0] } else .{},681 .argv0 = .init(.{ .value = args }),
739 .environ = .{ .block = environ },682 .environ = .{ .process_environ = .{ .block = environ } },
740 });683 });
741 defer threaded.deinit();684 defer threaded.deinit();
742685