Merge branch 'shawnl-path_max'

This does a proof of concept of changing most file system APIs to not
require an allocator and remove the possibility of failure via
OutOfMemory.

This also does most of the work of #534.
This commit is contained in:
Andrew Kelley 2018-08-21 21:02:01 -04:00
commit 3d780cf2ef
25 changed files with 795 additions and 567 deletions

View File

@ -19,7 +19,7 @@ pub fn build(b: *Builder) !void {
var docgen_cmd = b.addCommand(null, b.env_map, [][]const u8{ var docgen_cmd = b.addCommand(null, b.env_map, [][]const u8{
docgen_exe.getOutputPath(), docgen_exe.getOutputPath(),
rel_zig_exe, rel_zig_exe,
"doc/langref.html.in", "doc" ++ os.path.sep_str ++ "langref.html.in",
os.path.join(b.allocator, b.cache_root, "langref.html") catch unreachable, os.path.join(b.allocator, b.cache_root, "langref.html") catch unreachable,
}); });
docgen_cmd.step.dependOn(&docgen_exe.step); docgen_cmd.step.dependOn(&docgen_exe.step);

View File

@ -34,10 +34,10 @@ pub fn main() !void {
const out_file_name = try (args_it.next(allocator) orelse @panic("expected output arg")); const out_file_name = try (args_it.next(allocator) orelse @panic("expected output arg"));
defer allocator.free(out_file_name); defer allocator.free(out_file_name);
var in_file = try os.File.openRead(allocator, in_file_name); var in_file = try os.File.openRead(in_file_name);
defer in_file.close(); defer in_file.close();
var out_file = try os.File.openWrite(allocator, out_file_name); var out_file = try os.File.openWrite(out_file_name);
defer out_file.close(); defer out_file.close();
var file_in_stream = io.FileInStream.init(&in_file); var file_in_stream = io.FileInStream.init(&in_file);
@ -738,7 +738,7 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var
try out.print("<pre><code class=\"zig\">{}</code></pre>", escaped_source); try out.print("<pre><code class=\"zig\">{}</code></pre>", escaped_source);
const name_plus_ext = try std.fmt.allocPrint(allocator, "{}.zig", code.name); const name_plus_ext = try std.fmt.allocPrint(allocator, "{}.zig", code.name);
const tmp_source_file_name = try os.path.join(allocator, tmp_dir_name, name_plus_ext); const tmp_source_file_name = try os.path.join(allocator, tmp_dir_name, name_plus_ext);
try io.writeFile(allocator, tmp_source_file_name, trimmed_raw_source); try io.writeFile(tmp_source_file_name, trimmed_raw_source);
switch (code.id) { switch (code.id) {
Code.Id.Exe => |expected_outcome| { Code.Id.Exe => |expected_outcome| {

View File

@ -20,7 +20,7 @@ pub fn main() !void {
} else if (arg[0] == '-') { } else if (arg[0] == '-') {
return usage(exe); return usage(exe);
} else { } else {
var file = os.File.openRead(allocator, arg) catch |err| { var file = os.File.openRead(arg) catch |err| {
warn("Unable to open file: {}\n", @errorName(err)); warn("Unable to open file: {}\n", @errorName(err));
return err; return err;
}; };

View File

@ -257,8 +257,6 @@ pub const Compilation = struct {
pub const BuildError = error{ pub const BuildError = error{
OutOfMemory, OutOfMemory,
EndOfStream, EndOfStream,
BadFd,
Io,
IsDir, IsDir,
Unexpected, Unexpected,
SystemResources, SystemResources,
@ -273,7 +271,6 @@ pub const Compilation = struct {
NameTooLong, NameTooLong,
SystemFdQuotaExceeded, SystemFdQuotaExceeded,
NoDevice, NoDevice,
PathNotFound,
NoSpaceLeft, NoSpaceLeft,
NotDir, NotDir,
FileSystem, FileSystem,
@ -302,6 +299,7 @@ pub const Compilation = struct {
UnsupportedLinkArchitecture, UnsupportedLinkArchitecture,
UserResourceLimitReached, UserResourceLimitReached,
InvalidUtf8, InvalidUtf8,
BadPathName,
}; };
pub const Event = union(enum) { pub const Event = union(enum) {
@ -961,7 +959,7 @@ pub const Compilation = struct {
if (self.root_src_path) |root_src_path| { if (self.root_src_path) |root_src_path| {
const root_scope = blk: { const root_scope = blk: {
// TODO async/await os.path.real // TODO async/await os.path.real
const root_src_real_path = os.path.real(self.gpa(), root_src_path) catch |err| { const root_src_real_path = os.path.realAlloc(self.gpa(), root_src_path) catch |err| {
try self.addCompileErrorCli(root_src_path, "unable to open: {}", @errorName(err)); try self.addCompileErrorCli(root_src_path, "unable to open: {}", @errorName(err));
return; return;
}; };

View File

@ -235,7 +235,7 @@ pub const Msg = struct {
const allocator = msg.getAllocator(); const allocator = msg.getAllocator();
const tree = msg.getTree(); const tree = msg.getTree();
const cwd = try os.getCwd(allocator); const cwd = try os.getCwdAlloc(allocator);
defer allocator.free(cwd); defer allocator.free(cwd);
const relpath = try os.path.relative(allocator, cwd, msg.realpath); const relpath = try os.path.relative(allocator, cwd, msg.realpath);

View File

@ -14,7 +14,7 @@ pub fn testZigInstallPrefix(allocator: *mem.Allocator, test_path: []const u8) ![
const test_index_file = try os.path.join(allocator, test_zig_dir, "std", "index.zig"); const test_index_file = try os.path.join(allocator, test_zig_dir, "std", "index.zig");
defer allocator.free(test_index_file); defer allocator.free(test_index_file);
var file = try os.File.openRead(allocator, test_index_file); var file = try os.File.openRead(test_index_file);
file.close(); file.close();
return test_zig_dir; return test_zig_dir;
@ -22,7 +22,7 @@ pub fn testZigInstallPrefix(allocator: *mem.Allocator, test_path: []const u8) ![
/// Caller must free result /// Caller must free result
pub fn findZigLibDir(allocator: *mem.Allocator) ![]u8 { pub fn findZigLibDir(allocator: *mem.Allocator) ![]u8 {
const self_exe_path = try os.selfExeDirPath(allocator); const self_exe_path = try os.selfExeDirPathAlloc(allocator);
defer allocator.free(self_exe_path); defer allocator.free(self_exe_path);
var cur_path: []const u8 = self_exe_path; var cur_path: []const u8 = self_exe_path;

View File

@ -233,7 +233,7 @@ pub const LibCInstallation = struct {
const stdlib_path = try std.os.path.join(loop.allocator, search_path, "stdlib.h"); const stdlib_path = try std.os.path.join(loop.allocator, search_path, "stdlib.h");
defer loop.allocator.free(stdlib_path); defer loop.allocator.free(stdlib_path);
if (try fileExists(loop.allocator, stdlib_path)) { if (try fileExists(stdlib_path)) {
self.include_dir = try std.mem.dupe(loop.allocator, u8, search_path); self.include_dir = try std.mem.dupe(loop.allocator, u8, search_path);
return; return;
} }
@ -257,7 +257,7 @@ pub const LibCInstallation = struct {
const stdlib_path = try std.os.path.join(loop.allocator, result_buf.toSliceConst(), "stdlib.h"); const stdlib_path = try std.os.path.join(loop.allocator, result_buf.toSliceConst(), "stdlib.h");
defer loop.allocator.free(stdlib_path); defer loop.allocator.free(stdlib_path);
if (try fileExists(loop.allocator, stdlib_path)) { if (try fileExists(stdlib_path)) {
self.include_dir = result_buf.toOwnedSlice(); self.include_dir = result_buf.toOwnedSlice();
return; return;
} }
@ -285,7 +285,7 @@ pub const LibCInstallation = struct {
} }
const ucrt_lib_path = try std.os.path.join(loop.allocator, result_buf.toSliceConst(), "ucrt.lib"); const ucrt_lib_path = try std.os.path.join(loop.allocator, result_buf.toSliceConst(), "ucrt.lib");
defer loop.allocator.free(ucrt_lib_path); defer loop.allocator.free(ucrt_lib_path);
if (try fileExists(loop.allocator, ucrt_lib_path)) { if (try fileExists(ucrt_lib_path)) {
self.lib_dir = result_buf.toOwnedSlice(); self.lib_dir = result_buf.toOwnedSlice();
return; return;
} }
@ -360,7 +360,7 @@ pub const LibCInstallation = struct {
} }
const kernel32_path = try std.os.path.join(loop.allocator, result_buf.toSliceConst(), "kernel32.lib"); const kernel32_path = try std.os.path.join(loop.allocator, result_buf.toSliceConst(), "kernel32.lib");
defer loop.allocator.free(kernel32_path); defer loop.allocator.free(kernel32_path);
if (try fileExists(loop.allocator, kernel32_path)) { if (try fileExists(kernel32_path)) {
self.kernel32_lib_dir = result_buf.toOwnedSlice(); self.kernel32_lib_dir = result_buf.toOwnedSlice();
return; return;
} }
@ -449,12 +449,11 @@ fn fillSearch(search_buf: *[2]Search, sdk: *c.ZigWindowsSDK) []Search {
return search_buf[0..search_end]; return search_buf[0..search_end];
} }
fn fileExists(allocator: *std.mem.Allocator, path: []const u8) !bool { fn fileExists(path: []const u8) !bool {
if (std.os.File.access(allocator, path)) |_| { if (std.os.File.access(path)) |_| {
return true; return true;
} else |err| switch (err) { } else |err| switch (err) {
error.NotFound, error.PermissionDenied => return false, error.FileNotFound, error.PermissionDenied => return false,
error.OutOfMemory => return error.OutOfMemory,
else => return error.FileSystem, else => return error.FileSystem,
} }
} }

View File

@ -94,7 +94,7 @@ pub const TestContext = struct {
} }
// TODO async I/O // TODO async I/O
try std.io.writeFile(allocator, file1_path, source); try std.io.writeFile(file1_path, source);
var comp = try Compilation.create( var comp = try Compilation.create(
&self.zig_compiler, &self.zig_compiler,
@ -128,7 +128,7 @@ pub const TestContext = struct {
} }
// TODO async I/O // TODO async I/O
try std.io.writeFile(allocator, file1_path, source); try std.io.writeFile(file1_path, source);
var comp = try Compilation.create( var comp = try Compilation.create(
&self.zig_compiler, &self.zig_compiler,

View File

@ -267,7 +267,7 @@ pub const Builder = struct {
if (self.verbose) { if (self.verbose) {
warn("rm {}\n", installed_file); warn("rm {}\n", installed_file);
} }
_ = os.deleteFile(self.allocator, installed_file); _ = os.deleteFile(installed_file);
} }
// TODO remove empty directories // TODO remove empty directories
@ -1182,7 +1182,7 @@ pub const LibExeObjStep = struct {
if (self.build_options_contents.len() > 0) { if (self.build_options_contents.len() > 0) {
const build_options_file = try os.path.join(builder.allocator, builder.cache_root, builder.fmt("{}_build_options.zig", self.name)); const build_options_file = try os.path.join(builder.allocator, builder.cache_root, builder.fmt("{}_build_options.zig", self.name));
try std.io.writeFile(builder.allocator, build_options_file, self.build_options_contents.toSliceConst()); try std.io.writeFile(build_options_file, self.build_options_contents.toSliceConst());
try zig_args.append("--pkg-begin"); try zig_args.append("--pkg-begin");
try zig_args.append("build_options"); try zig_args.append("build_options");
try zig_args.append(builder.pathFromRoot(build_options_file)); try zig_args.append(builder.pathFromRoot(build_options_file));
@ -1491,11 +1491,14 @@ pub const LibExeObjStep = struct {
} }
if (!is_darwin) { if (!is_darwin) {
const rpath_arg = builder.fmt("-Wl,-rpath,{}", os.path.real(builder.allocator, builder.pathFromRoot(builder.cache_root)) catch unreachable); const rpath_arg = builder.fmt("-Wl,-rpath,{}", try os.path.realAlloc(
builder.allocator,
builder.pathFromRoot(builder.cache_root),
));
defer builder.allocator.free(rpath_arg); defer builder.allocator.free(rpath_arg);
cc_args.append(rpath_arg) catch unreachable; try cc_args.append(rpath_arg);
cc_args.append("-rdynamic") catch unreachable; try cc_args.append("-rdynamic");
} }
for (self.full_path_libs.toSliceConst()) |full_path_lib| { for (self.full_path_libs.toSliceConst()) |full_path_lib| {
@ -1566,11 +1569,14 @@ pub const LibExeObjStep = struct {
cc_args.append("-o") catch unreachable; cc_args.append("-o") catch unreachable;
cc_args.append(output_path) catch unreachable; cc_args.append(output_path) catch unreachable;
const rpath_arg = builder.fmt("-Wl,-rpath,{}", os.path.real(builder.allocator, builder.pathFromRoot(builder.cache_root)) catch unreachable); const rpath_arg = builder.fmt("-Wl,-rpath,{}", try os.path.realAlloc(
builder.allocator,
builder.pathFromRoot(builder.cache_root),
));
defer builder.allocator.free(rpath_arg); defer builder.allocator.free(rpath_arg);
cc_args.append(rpath_arg) catch unreachable; try cc_args.append(rpath_arg);
cc_args.append("-rdynamic") catch unreachable; try cc_args.append("-rdynamic");
{ {
var it = self.link_libs.iterator(); var it = self.link_libs.iterator();
@ -1917,7 +1923,7 @@ pub const WriteFileStep = struct {
warn("unable to make path {}: {}\n", full_path_dir, @errorName(err)); warn("unable to make path {}: {}\n", full_path_dir, @errorName(err));
return err; return err;
}; };
io.writeFile(self.builder.allocator, full_path, self.data) catch |err| { io.writeFile(full_path, self.data) catch |err| {
warn("unable to write {}: {}\n", full_path, @errorName(err)); warn("unable to write {}: {}\n", full_path, @errorName(err));
return err; return err;
}; };

View File

@ -9,10 +9,9 @@ pub const line_sep = switch (builtin.os) {
else => "\n", else => "\n",
}; };
/// Deprecated, use mem.len
pub fn len(ptr: [*]const u8) usize { pub fn len(ptr: [*]const u8) usize {
var count: usize = 0; return mem.len(u8, ptr);
while (ptr[count] != 0) : (count += 1) {}
return count;
} }
pub fn cmp(a: [*]const u8, b: [*]const u8) i8 { pub fn cmp(a: [*]const u8, b: [*]const u8) i8 {
@ -27,12 +26,14 @@ pub fn cmp(a: [*]const u8, b: [*]const u8) i8 {
} }
} }
/// Deprecated, use mem.toSliceConst
pub fn toSliceConst(str: [*]const u8) []const u8 { pub fn toSliceConst(str: [*]const u8) []const u8 {
return str[0..len(str)]; return mem.toSliceConst(u8, str);
} }
/// Deprecated, use mem.toSlice
pub fn toSlice(str: [*]u8) []u8 { pub fn toSlice(str: [*]u8) []u8 {
return str[0..len(str)]; return mem.toSlice(u8, str);
} }
test "cstr fns" { test "cstr fns" {

View File

@ -255,7 +255,7 @@ pub fn printSourceAtAddress(debug_info: *ElfStackTrace, out_stream: var, address
address, address,
compile_unit_name, compile_unit_name,
); );
if (printLineFromFile(debug_info.allocator(), out_stream, line_info)) { if (printLineFromFile(out_stream, line_info)) {
if (line_info.column == 0) { if (line_info.column == 0) {
try out_stream.write("\n"); try out_stream.write("\n");
} else { } else {
@ -340,8 +340,8 @@ pub fn openSelfDebugInfo(allocator: *mem.Allocator) !*ElfStackTrace {
} }
} }
fn printLineFromFile(allocator: *mem.Allocator, out_stream: var, line_info: *const LineInfo) !void { fn printLineFromFile(out_stream: var, line_info: *const LineInfo) !void {
var f = try os.File.openRead(allocator, line_info.file_name); var f = try os.File.openRead(line_info.file_name);
defer f.close(); defer f.close();
// TODO fstat and make sure that the file has the correct size // TODO fstat and make sure that the file has the correct size

View File

@ -78,8 +78,7 @@ pub async fn pwritev(loop: *Loop, fd: os.FileHandle, data: []const []const u8, o
builtin.Os.macosx, builtin.Os.macosx,
builtin.Os.linux, builtin.Os.linux,
=> return await (async pwritevPosix(loop, fd, data, offset) catch unreachable), => return await (async pwritevPosix(loop, fd, data, offset) catch unreachable),
builtin.Os.windows, builtin.Os.windows => return await (async pwritevWindows(loop, fd, data, offset) catch unreachable),
=> return await (async pwritevWindows(loop, fd, data, offset) catch unreachable),
else => @compileError("Unsupported OS"), else => @compileError("Unsupported OS"),
} }
} }
@ -147,7 +146,6 @@ pub async fn pwriteWindows(loop: *Loop, fd: os.FileHandle, data: []const u8, off
} }
} }
/// data - just the inner references - must live until pwritev promise completes. /// data - just the inner references - must live until pwritev promise completes.
pub async fn pwritevPosix(loop: *Loop, fd: os.FileHandle, data: []const []const u8, offset: usize) !void { pub async fn pwritevPosix(loop: *Loop, fd: os.FileHandle, data: []const []const u8, offset: usize) !void {
// workaround for https://github.com/ziglang/zig/issues/1194 // workaround for https://github.com/ziglang/zig/issues/1194
@ -203,8 +201,7 @@ pub async fn preadv(loop: *Loop, fd: os.FileHandle, data: []const []u8, offset:
builtin.Os.macosx, builtin.Os.macosx,
builtin.Os.linux, builtin.Os.linux,
=> return await (async preadvPosix(loop, fd, data, offset) catch unreachable), => return await (async preadvPosix(loop, fd, data, offset) catch unreachable),
builtin.Os.windows, builtin.Os.windows => return await (async preadvWindows(loop, fd, data, offset) catch unreachable),
=> return await (async preadvWindows(loop, fd, data, offset) catch unreachable),
else => @compileError("Unsupported OS"), else => @compileError("Unsupported OS"),
} }
} }
@ -222,7 +219,7 @@ pub async fn preadvWindows(loop: *Loop, fd: os.FileHandle, data: []const []u8, o
var inner_off: usize = 0; var inner_off: usize = 0;
while (true) { while (true) {
const v = data_copy[iov_i]; const v = data_copy[iov_i];
const amt_read = try await (async preadWindows(loop, fd, v[inner_off .. v.len-inner_off], offset + off) catch unreachable); const amt_read = try await (async preadWindows(loop, fd, v[inner_off .. v.len - inner_off], offset + off) catch unreachable);
off += amt_read; off += amt_read;
inner_off += amt_read; inner_off += amt_read;
if (inner_off == v.len) { if (inner_off == v.len) {
@ -340,8 +337,7 @@ pub async fn openPosix(
resume @handle(); resume @handle();
} }
const path_with_null = try std.cstr.addNullByte(loop.allocator, path); const path_c = try std.os.toPosixPath(path);
defer loop.allocator.free(path_with_null);
var req_node = RequestNode{ var req_node = RequestNode{
.prev = null, .prev = null,
@ -349,7 +345,7 @@ pub async fn openPosix(
.data = Request{ .data = Request{
.msg = Request.Msg{ .msg = Request.Msg{
.Open = Request.Msg.Open{ .Open = Request.Msg.Open{
.path = path_with_null[0..path.len], .path = path_c[0..path.len],
.flags = flags, .flags = flags,
.mode = mode, .mode = mode,
.result = undefined, .result = undefined,
@ -382,7 +378,6 @@ pub async fn openRead(loop: *Loop, path: []const u8) os.File.OpenError!os.FileHa
}, },
builtin.Os.windows => return os.windowsOpen( builtin.Os.windows => return os.windowsOpen(
loop.allocator,
path, path,
windows.GENERIC_READ, windows.GENERIC_READ,
windows.FILE_SHARE_READ, windows.FILE_SHARE_READ,
@ -409,9 +404,7 @@ pub async fn openWriteMode(loop: *Loop, path: []const u8, mode: os.File.Mode) os
const flags = posix.O_LARGEFILE | posix.O_WRONLY | posix.O_CREAT | posix.O_CLOEXEC | posix.O_TRUNC; const flags = posix.O_LARGEFILE | posix.O_WRONLY | posix.O_CREAT | posix.O_CLOEXEC | posix.O_TRUNC;
return await (async openPosix(loop, path, flags, os.File.default_mode) catch unreachable); return await (async openPosix(loop, path, flags, os.File.default_mode) catch unreachable);
}, },
builtin.Os.windows, builtin.Os.windows => return os.windowsOpen(
=> return os.windowsOpen(
loop.allocator,
path, path,
windows.GENERIC_WRITE, windows.GENERIC_WRITE,
windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE, windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
@ -435,9 +428,8 @@ pub async fn openReadWrite(
}, },
builtin.Os.windows => return os.windowsOpen( builtin.Os.windows => return os.windowsOpen(
loop.allocator,
path, path,
windows.GENERIC_WRITE|windows.GENERIC_READ, windows.GENERIC_WRITE | windows.GENERIC_READ,
windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE, windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
windows.OPEN_ALWAYS, windows.OPEN_ALWAYS,
windows.FILE_ATTRIBUTE_NORMAL | windows.FILE_FLAG_OVERLAPPED, windows.FILE_ATTRIBUTE_NORMAL | windows.FILE_FLAG_OVERLAPPED,
@ -513,8 +505,7 @@ pub const CloseOperation = struct {
self.loop.allocator.destroy(self); self.loop.allocator.destroy(self);
} }
}, },
builtin.Os.windows, builtin.Os.windows => {
=> {
if (self.os_data.handle) |handle| { if (self.os_data.handle) |handle| {
os.close(handle); os.close(handle);
} }
@ -532,8 +523,7 @@ pub const CloseOperation = struct {
self.os_data.close_req_node.data.msg.Close.fd = handle; self.os_data.close_req_node.data.msg.Close.fd = handle;
self.os_data.have_fd = true; self.os_data.have_fd = true;
}, },
builtin.Os.windows, builtin.Os.windows => {
=> {
self.os_data.handle = handle; self.os_data.handle = handle;
}, },
else => @compileError("Unsupported OS"), else => @compileError("Unsupported OS"),
@ -548,8 +538,7 @@ pub const CloseOperation = struct {
=> { => {
self.os_data.have_fd = false; self.os_data.have_fd = false;
}, },
builtin.Os.windows, builtin.Os.windows => {
=> {
self.os_data.handle = null; self.os_data.handle = null;
}, },
else => @compileError("Unsupported OS"), else => @compileError("Unsupported OS"),
@ -564,8 +553,7 @@ pub const CloseOperation = struct {
assert(self.os_data.have_fd); assert(self.os_data.have_fd);
return self.os_data.close_req_node.data.msg.Close.fd; return self.os_data.close_req_node.data.msg.Close.fd;
}, },
builtin.Os.windows, builtin.Os.windows => {
=> {
return self.os_data.handle.?; return self.os_data.handle.?;
}, },
else => @compileError("Unsupported OS"), else => @compileError("Unsupported OS"),
@ -585,15 +573,13 @@ pub async fn writeFileMode(loop: *Loop, path: []const u8, contents: []const u8,
builtin.Os.linux, builtin.Os.linux,
builtin.Os.macosx, builtin.Os.macosx,
=> return await (async writeFileModeThread(loop, path, contents, mode) catch unreachable), => return await (async writeFileModeThread(loop, path, contents, mode) catch unreachable),
builtin.Os.windows, builtin.Os.windows => return await (async writeFileWindows(loop, path, contents) catch unreachable),
=> return await (async writeFileWindows(loop, path, contents) catch unreachable),
else => @compileError("Unsupported OS"), else => @compileError("Unsupported OS"),
} }
} }
async fn writeFileWindows(loop: *Loop, path: []const u8, contents: []const u8) !void { async fn writeFileWindows(loop: *Loop, path: []const u8, contents: []const u8) !void {
const handle = try os.windowsOpen( const handle = try os.windowsOpen(
loop.allocator,
path, path,
windows.GENERIC_WRITE, windows.GENERIC_WRITE,
windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE, windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
@ -1004,7 +990,7 @@ pub fn Watch(comptime V: type) type {
const basename_utf16le_null = try std.unicode.utf8ToUtf16LeWithNull(self.channel.loop.allocator, basename); const basename_utf16le_null = try std.unicode.utf8ToUtf16LeWithNull(self.channel.loop.allocator, basename);
var basename_utf16le_null_consumed = false; var basename_utf16le_null_consumed = false;
defer if (!basename_utf16le_null_consumed) self.channel.loop.allocator.free(basename_utf16le_null); defer if (!basename_utf16le_null_consumed) self.channel.loop.allocator.free(basename_utf16le_null);
const basename_utf16le_no_null = basename_utf16le_null[0..basename_utf16le_null.len-1]; const basename_utf16le_no_null = basename_utf16le_null[0 .. basename_utf16le_null.len - 1];
const dir_handle = windows.CreateFileW( const dir_handle = windows.CreateFileW(
dirname_utf16le.ptr, dirname_utf16le.ptr,
@ -1018,9 +1004,8 @@ pub fn Watch(comptime V: type) type {
if (dir_handle == windows.INVALID_HANDLE_VALUE) { if (dir_handle == windows.INVALID_HANDLE_VALUE) {
const err = windows.GetLastError(); const err = windows.GetLastError();
switch (err) { switch (err) {
windows.ERROR.FILE_NOT_FOUND, windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound,
windows.ERROR.PATH_NOT_FOUND, windows.ERROR.PATH_NOT_FOUND => return error.FileNotFound,
=> return error.PathNotFound,
else => return os.unexpectedErrorWindows(err), else => return os.unexpectedErrorWindows(err),
} }
} }
@ -1106,7 +1091,10 @@ pub fn Watch(comptime V: type) type {
// TODO handle this error not in the channel but in the setup // TODO handle this error not in the channel but in the setup
_ = os.windowsCreateIoCompletionPort( _ = os.windowsCreateIoCompletionPort(
dir_handle, self.channel.loop.os_data.io_port, completion_key, undefined, dir_handle,
self.channel.loop.os_data.io_port,
completion_key,
undefined,
) catch |err| { ) catch |err| {
await (async self.channel.put(err) catch unreachable); await (async self.channel.put(err) catch unreachable);
return; return;
@ -1126,10 +1114,10 @@ pub fn Watch(comptime V: type) type {
&event_buf, &event_buf,
@intCast(windows.DWORD, event_buf.len), @intCast(windows.DWORD, event_buf.len),
windows.FALSE, // watch subtree windows.FALSE, // watch subtree
windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME | windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME |
windows.FILE_NOTIFY_CHANGE_ATTRIBUTES | windows.FILE_NOTIFY_CHANGE_SIZE | windows.FILE_NOTIFY_CHANGE_ATTRIBUTES | windows.FILE_NOTIFY_CHANGE_SIZE |
windows.FILE_NOTIFY_CHANGE_LAST_WRITE | windows.FILE_NOTIFY_CHANGE_LAST_ACCESS | windows.FILE_NOTIFY_CHANGE_LAST_WRITE | windows.FILE_NOTIFY_CHANGE_LAST_ACCESS |
windows.FILE_NOTIFY_CHANGE_CREATION | windows.FILE_NOTIFY_CHANGE_SECURITY, windows.FILE_NOTIFY_CHANGE_CREATION | windows.FILE_NOTIFY_CHANGE_SECURITY,
null, // number of bytes transferred (unused for async) null, // number of bytes transferred (unused for async)
&overlapped, &overlapped,
null, // completion routine - unused because we use IOCP null, // completion routine - unused because we use IOCP
@ -1156,7 +1144,7 @@ pub fn Watch(comptime V: type) type {
else => null, else => null,
}; };
if (emit) |id| { if (emit) |id| {
const basename_utf16le = ([*]u16)(&ev.FileName)[0..ev.FileNameLength/2]; const basename_utf16le = ([*]u16)(&ev.FileName)[0 .. ev.FileNameLength / 2];
const user_value = blk: { const user_value = blk: {
const held = await (async dir.table_lock.acquire() catch unreachable); const held = await (async dir.table_lock.acquire() catch unreachable);
defer held.release(); defer held.release();

View File

@ -254,9 +254,8 @@ pub fn OutStream(comptime WriteError: type) type {
}; };
} }
/// `path` needs to be copied in memory to add a null terminating byte, hence the allocator. pub fn writeFile(path: []const u8, data: []const u8) !void {
pub fn writeFile(allocator: *mem.Allocator, path: []const u8, data: []const u8) !void { var file = try File.openWrite(path);
var file = try File.openWrite(allocator, path);
defer file.close(); defer file.close();
try file.write(data); try file.write(data);
} }
@ -268,7 +267,7 @@ pub fn readFileAlloc(allocator: *mem.Allocator, path: []const u8) ![]u8 {
/// On success, caller owns returned buffer. /// On success, caller owns returned buffer.
pub fn readFileAllocAligned(allocator: *mem.Allocator, path: []const u8, comptime A: u29) ![]align(A) u8 { pub fn readFileAllocAligned(allocator: *mem.Allocator, path: []const u8, comptime A: u29) ![]align(A) u8 {
var file = try File.openRead(allocator, path); var file = try File.openRead(path);
defer file.close(); defer file.close();
const size = try file.getEndPos(); const size = try file.getEndPos();

View File

@ -16,7 +16,7 @@ test "write a file, read it, then delete it" {
prng.random.bytes(data[0..]); prng.random.bytes(data[0..]);
const tmp_file_name = "temp_test_file.txt"; const tmp_file_name = "temp_test_file.txt";
{ {
var file = try os.File.openWrite(allocator, tmp_file_name); var file = try os.File.openWrite(tmp_file_name);
defer file.close(); defer file.close();
var file_out_stream = io.FileOutStream.init(&file); var file_out_stream = io.FileOutStream.init(&file);
@ -28,7 +28,7 @@ test "write a file, read it, then delete it" {
try buf_stream.flush(); try buf_stream.flush();
} }
{ {
var file = try os.File.openRead(allocator, tmp_file_name); var file = try os.File.openRead(tmp_file_name);
defer file.close(); defer file.close();
const file_size = try file.getEndPos(); const file_size = try file.getEndPos();
@ -45,7 +45,7 @@ test "write a file, read it, then delete it" {
assert(mem.eql(u8, contents["begin".len .. contents.len - "end".len], data)); assert(mem.eql(u8, contents["begin".len .. contents.len - "end".len], data));
assert(mem.eql(u8, contents[contents.len - "end".len ..], "end")); assert(mem.eql(u8, contents[contents.len - "end".len ..], "end"));
} }
try os.deleteFile(allocator, tmp_file_name); try os.deleteFile(tmp_file_name);
} }
test "BufferOutStream" { test "BufferOutStream" {
@ -63,7 +63,7 @@ test "BufferOutStream" {
} }
test "SliceInStream" { test "SliceInStream" {
const bytes = []const u8 { 1, 2, 3, 4, 5, 6, 7 }; const bytes = []const u8{ 1, 2, 3, 4, 5, 6, 7 };
var ss = io.SliceInStream.init(bytes); var ss = io.SliceInStream.init(bytes);
var dest: [4]u8 = undefined; var dest: [4]u8 = undefined;
@ -81,7 +81,7 @@ test "SliceInStream" {
} }
test "PeekStream" { test "PeekStream" {
const bytes = []const u8 { 1, 2, 3, 4, 5, 6, 7, 8 }; const bytes = []const u8{ 1, 2, 3, 4, 5, 6, 7, 8 };
var ss = io.SliceInStream.init(bytes); var ss = io.SliceInStream.init(bytes);
var ps = io.PeekStream(2, io.SliceInStream.Error).init(&ss.stream); var ps = io.PeekStream(2, io.SliceInStream.Error).init(&ss.stream);

View File

@ -179,8 +179,8 @@ pub fn secureZero(comptime T: type, s: []T) void {
// NOTE: We do not use a volatile slice cast here since LLVM cannot // NOTE: We do not use a volatile slice cast here since LLVM cannot
// see that it can be replaced by a memset. // see that it can be replaced by a memset.
const ptr = @ptrCast([*]volatile u8, s.ptr); const ptr = @ptrCast([*]volatile u8, s.ptr);
const len = s.len * @sizeOf(T); const length = s.len * @sizeOf(T);
@memset(ptr, 0, len); @memset(ptr, 0, length);
} }
test "mem.secureZero" { test "mem.secureZero" {
@ -252,6 +252,20 @@ pub fn eql(comptime T: type, a: []const T, b: []const T) bool {
return true; return true;
} }
pub fn len(comptime T: type, ptr: [*]const T) usize {
var count: usize = 0;
while (ptr[count] != 0) : (count += 1) {}
return count;
}
pub fn toSliceConst(comptime T: type, ptr: [*]const T) []const T {
return ptr[0..len(T, ptr)];
}
pub fn toSlice(comptime T: type, ptr: [*]T) []T {
return ptr[0..len(T, ptr)];
}
/// Returns true if all elements in a slice are equal to the scalar value provided /// Returns true if all elements in a slice are equal to the scalar value provided
pub fn allEqual(comptime T: type, slice: []const T, scalar: T) bool { pub fn allEqual(comptime T: type, slice: []const T, scalar: T) bool {
for (slice) |item| { for (slice) |item| {
@ -809,3 +823,4 @@ pub fn endianSwap(comptime T: type, x: T) T {
test "std.mem.endianSwap" { test "std.mem.endianSwap" {
assert(endianSwap(u32, 0xDEADBEEF) == 0xEFBEADDE); assert(endianSwap(u32, 0xDEADBEEF) == 0xEFBEADDE);
} }

View File

@ -349,14 +349,7 @@ pub const ChildProcess = struct {
}; };
const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore); const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore);
const dev_null_fd = if (any_ignore) blk: { const dev_null_fd = if (any_ignore) try os.posixOpenC(c"/dev/null", posix.O_RDWR, 0) else undefined;
const dev_null_path = "/dev/null";
var fixed_buffer_mem: [dev_null_path.len + 1]u8 = undefined;
var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]);
break :blk try os.posixOpen(&fixed_allocator.allocator, "/dev/null", posix.O_RDWR, 0);
} else blk: {
break :blk undefined;
};
defer { defer {
if (any_ignore) os.close(dev_null_fd); if (any_ignore) os.close(dev_null_fd);
} }
@ -453,10 +446,7 @@ pub const ChildProcess = struct {
const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore); const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore);
const nul_handle = if (any_ignore) blk: { const nul_handle = if (any_ignore) blk: {
const nul_file_path = "NUL"; break :blk try os.windowsOpen("NUL", windows.GENERIC_READ, windows.FILE_SHARE_READ, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL);
var fixed_buffer_mem: [nul_file_path.len + 1]u8 = undefined;
var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]);
break :blk try os.windowsOpen(&fixed_allocator.allocator, "NUL", windows.GENERIC_READ, windows.FILE_SHARE_READ, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL);
} else blk: { } else blk: {
break :blk undefined; break :blk undefined;
}; };

View File

@ -7,6 +7,7 @@ const assert = std.debug.assert;
const posix = os.posix; const posix = os.posix;
const windows = os.windows; const windows = os.windows;
const Os = builtin.Os; const Os = builtin.Os;
const windows_util = @import("windows/util.zig");
const is_posix = builtin.os != builtin.Os.windows; const is_posix = builtin.os != builtin.Os.windows;
const is_windows = builtin.os == builtin.Os.windows; const is_windows = builtin.os == builtin.Os.windows;
@ -27,16 +28,27 @@ pub const File = struct {
pub const OpenError = os.WindowsOpenError || os.PosixOpenError; pub const OpenError = os.WindowsOpenError || os.PosixOpenError;
/// `path` needs to be copied in memory to add a null terminating byte, hence the allocator. /// `openRead` except with a null terminated path
/// Call close to clean up. pub fn openReadC(path: [*]const u8) OpenError!File {
pub fn openRead(allocator: *mem.Allocator, path: []const u8) OpenError!File {
if (is_posix) { if (is_posix) {
const flags = posix.O_LARGEFILE | posix.O_RDONLY; const flags = posix.O_LARGEFILE | posix.O_RDONLY;
const fd = try os.posixOpen(allocator, path, flags, 0); const fd = try os.posixOpenC(path, flags, 0);
return openHandle(fd); return openHandle(fd);
} else if (is_windows) { }
if (is_windows) {
return openRead(mem.toSliceConst(u8, path));
}
@compileError("Unsupported OS");
}
/// Call close to clean up.
pub fn openRead(path: []const u8) OpenError!File {
if (is_posix) {
const path_c = try os.toPosixPath(path);
return openReadC(&path_c);
}
if (is_windows) {
const handle = try os.windowsOpen( const handle = try os.windowsOpen(
allocator,
path, path,
windows.GENERIC_READ, windows.GENERIC_READ,
windows.FILE_SHARE_READ, windows.FILE_SHARE_READ,
@ -44,28 +56,25 @@ pub const File = struct {
windows.FILE_ATTRIBUTE_NORMAL, windows.FILE_ATTRIBUTE_NORMAL,
); );
return openHandle(handle); return openHandle(handle);
} else {
@compileError("TODO implement openRead for this OS");
} }
@compileError("Unsupported OS");
} }
/// Calls `openWriteMode` with os.File.default_mode for the mode. /// Calls `openWriteMode` with os.File.default_mode for the mode.
pub fn openWrite(allocator: *mem.Allocator, path: []const u8) OpenError!File { pub fn openWrite(path: []const u8) OpenError!File {
return openWriteMode(allocator, path, os.File.default_mode); return openWriteMode(path, os.File.default_mode);
} }
/// If the path does not exist it will be created. /// If the path does not exist it will be created.
/// If a file already exists in the destination it will be truncated. /// If a file already exists in the destination it will be truncated.
/// `path` needs to be copied in memory to add a null terminating byte, hence the allocator.
/// Call close to clean up. /// Call close to clean up.
pub fn openWriteMode(allocator: *mem.Allocator, path: []const u8, file_mode: Mode) OpenError!File { pub fn openWriteMode(path: []const u8, file_mode: Mode) OpenError!File {
if (is_posix) { if (is_posix) {
const flags = posix.O_LARGEFILE | posix.O_WRONLY | posix.O_CREAT | posix.O_CLOEXEC | posix.O_TRUNC; const flags = posix.O_LARGEFILE | posix.O_WRONLY | posix.O_CREAT | posix.O_CLOEXEC | posix.O_TRUNC;
const fd = try os.posixOpen(allocator, path, flags, file_mode); const fd = try os.posixOpen(path, flags, file_mode);
return openHandle(fd); return openHandle(fd);
} else if (is_windows) { } else if (is_windows) {
const handle = try os.windowsOpen( const handle = try os.windowsOpen(
allocator,
path, path,
windows.GENERIC_WRITE, windows.GENERIC_WRITE,
windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE, windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
@ -80,16 +89,14 @@ pub const File = struct {
/// If the path does not exist it will be created. /// If the path does not exist it will be created.
/// If a file already exists in the destination this returns OpenError.PathAlreadyExists /// If a file already exists in the destination this returns OpenError.PathAlreadyExists
/// `path` needs to be copied in memory to add a null terminating byte, hence the allocator.
/// Call close to clean up. /// Call close to clean up.
pub fn openWriteNoClobber(allocator: *mem.Allocator, path: []const u8, file_mode: Mode) OpenError!File { pub fn openWriteNoClobber(path: []const u8, file_mode: Mode) OpenError!File {
if (is_posix) { if (is_posix) {
const flags = posix.O_LARGEFILE | posix.O_WRONLY | posix.O_CREAT | posix.O_CLOEXEC | posix.O_EXCL; const flags = posix.O_LARGEFILE | posix.O_WRONLY | posix.O_CREAT | posix.O_CLOEXEC | posix.O_EXCL;
const fd = try os.posixOpen(allocator, path, flags, file_mode); const fd = try os.posixOpen(path, flags, file_mode);
return openHandle(fd); return openHandle(fd);
} else if (is_windows) { } else if (is_windows) {
const handle = try os.windowsOpen( const handle = try os.windowsOpen(
allocator,
path, path,
windows.GENERIC_WRITE, windows.GENERIC_WRITE,
windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE, windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
@ -108,23 +115,43 @@ pub const File = struct {
pub const AccessError = error{ pub const AccessError = error{
PermissionDenied, PermissionDenied,
NotFound, FileNotFound,
NameTooLong, NameTooLong,
BadMode, InputOutput,
BadPathName,
Io,
SystemResources, SystemResources,
OutOfMemory, BadPathName,
/// On Windows, file paths must be valid Unicode.
InvalidUtf8,
Unexpected, Unexpected,
}; };
pub fn access(allocator: *mem.Allocator, path: []const u8) AccessError!void { /// Call from Windows-specific code if you already have a UTF-16LE encoded, null terminated string.
const path_with_null = try std.cstr.addNullByte(allocator, path); /// Otherwise use `access` or `accessC`.
defer allocator.free(path_with_null); pub fn accessW(path: [*]const u16) AccessError!void {
if (os.windows.GetFileAttributesW(path) != os.windows.INVALID_FILE_ATTRIBUTES) {
return;
}
const err = windows.GetLastError();
switch (err) {
windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound,
windows.ERROR.PATH_NOT_FOUND => return error.FileNotFound,
windows.ERROR.ACCESS_DENIED => return error.PermissionDenied,
else => return os.unexpectedErrorWindows(err),
}
}
/// Call if you have a UTF-8 encoded, null-terminated string.
/// Otherwise use `access` or `accessW`.
pub fn accessC(path: [*]const u8) AccessError!void {
if (is_windows) {
const path_w = try windows_util.cStrToPrefixedFileW(path);
return accessW(&path_w);
}
if (is_posix) { if (is_posix) {
const result = posix.access(path_with_null.ptr, posix.F_OK); const result = posix.access(path, posix.F_OK);
const err = posix.getErrno(result); const err = posix.getErrno(result);
switch (err) { switch (err) {
0 => return, 0 => return,
@ -132,32 +159,33 @@ pub const File = struct {
posix.EROFS => return error.PermissionDenied, posix.EROFS => return error.PermissionDenied,
posix.ELOOP => return error.PermissionDenied, posix.ELOOP => return error.PermissionDenied,
posix.ETXTBSY => return error.PermissionDenied, posix.ETXTBSY => return error.PermissionDenied,
posix.ENOTDIR => return error.NotFound, posix.ENOTDIR => return error.FileNotFound,
posix.ENOENT => return error.NotFound, posix.ENOENT => return error.FileNotFound,
posix.ENAMETOOLONG => return error.NameTooLong, posix.ENAMETOOLONG => return error.NameTooLong,
posix.EINVAL => unreachable, posix.EINVAL => unreachable,
posix.EFAULT => return error.BadPathName, posix.EFAULT => unreachable,
posix.EIO => return error.Io, posix.EIO => return error.InputOutput,
posix.ENOMEM => return error.SystemResources, posix.ENOMEM => return error.SystemResources,
else => return os.unexpectedErrorPosix(err), else => return os.unexpectedErrorPosix(err),
} }
} else if (is_windows) {
if (os.windows.GetFileAttributesA(path_with_null.ptr) != os.windows.INVALID_FILE_ATTRIBUTES) {
return;
}
const err = windows.GetLastError();
switch (err) {
windows.ERROR.FILE_NOT_FOUND,
windows.ERROR.PATH_NOT_FOUND,
=> return error.NotFound,
windows.ERROR.ACCESS_DENIED => return error.PermissionDenied,
else => return os.unexpectedErrorWindows(err),
}
} else {
@compileError("TODO implement access for this OS");
} }
@compileError("Unsupported OS");
}
pub fn access(path: []const u8) AccessError!void {
if (is_windows) {
const path_w = try windows_util.sliceToPrefixedFileW(path);
return accessW(&path_w);
}
if (is_posix) {
var path_with_null: [posix.PATH_MAX]u8 = undefined;
if (path.len >= posix.PATH_MAX) return error.NameTooLong;
mem.copy(u8, path_with_null[0..], path);
path_with_null[path.len] = 0;
return accessC(&path_with_null);
}
@compileError("Unsupported OS");
} }
/// Upon success, the stream is in an uninitialized state. To continue using it, /// Upon success, the stream is in an uninitialized state. To continue using it,
@ -179,7 +207,9 @@ pub const File = struct {
const err = posix.getErrno(result); const err = posix.getErrno(result);
if (err > 0) { if (err > 0) {
return switch (err) { return switch (err) {
posix.EBADF => error.BadFd, // We do not make this an error code because if you get EBADF it's always a bug,
// since the fd could have been reused.
posix.EBADF => unreachable,
posix.EINVAL => error.Unseekable, posix.EINVAL => error.Unseekable,
posix.EOVERFLOW => error.Unseekable, posix.EOVERFLOW => error.Unseekable,
posix.ESPIPE => error.Unseekable, posix.ESPIPE => error.Unseekable,
@ -192,7 +222,7 @@ pub const File = struct {
if (windows.SetFilePointerEx(self.handle, amount, null, windows.FILE_CURRENT) == 0) { if (windows.SetFilePointerEx(self.handle, amount, null, windows.FILE_CURRENT) == 0) {
const err = windows.GetLastError(); const err = windows.GetLastError();
return switch (err) { return switch (err) {
windows.ERROR.INVALID_PARAMETER => error.BadFd, windows.ERROR.INVALID_PARAMETER => unreachable,
else => os.unexpectedErrorWindows(err), else => os.unexpectedErrorWindows(err),
}; };
} }
@ -209,7 +239,9 @@ pub const File = struct {
const err = posix.getErrno(result); const err = posix.getErrno(result);
if (err > 0) { if (err > 0) {
return switch (err) { return switch (err) {
posix.EBADF => error.BadFd, // We do not make this an error code because if you get EBADF it's always a bug,
// since the fd could have been reused.
posix.EBADF => unreachable,
posix.EINVAL => error.Unseekable, posix.EINVAL => error.Unseekable,
posix.EOVERFLOW => error.Unseekable, posix.EOVERFLOW => error.Unseekable,
posix.ESPIPE => error.Unseekable, posix.ESPIPE => error.Unseekable,
@ -223,7 +255,7 @@ pub const File = struct {
if (windows.SetFilePointerEx(self.handle, ipos, null, windows.FILE_BEGIN) == 0) { if (windows.SetFilePointerEx(self.handle, ipos, null, windows.FILE_BEGIN) == 0) {
const err = windows.GetLastError(); const err = windows.GetLastError();
return switch (err) { return switch (err) {
windows.ERROR.INVALID_PARAMETER => error.BadFd, windows.ERROR.INVALID_PARAMETER => unreachable,
else => os.unexpectedErrorWindows(err), else => os.unexpectedErrorWindows(err),
}; };
} }
@ -239,7 +271,9 @@ pub const File = struct {
const err = posix.getErrno(result); const err = posix.getErrno(result);
if (err > 0) { if (err > 0) {
return switch (err) { return switch (err) {
posix.EBADF => error.BadFd, // We do not make this an error code because if you get EBADF it's always a bug,
// since the fd could have been reused.
posix.EBADF => unreachable,
posix.EINVAL => error.Unseekable, posix.EINVAL => error.Unseekable,
posix.EOVERFLOW => error.Unseekable, posix.EOVERFLOW => error.Unseekable,
posix.ESPIPE => error.Unseekable, posix.ESPIPE => error.Unseekable,
@ -254,7 +288,7 @@ pub const File = struct {
if (windows.SetFilePointerEx(self.handle, 0, &pos, windows.FILE_CURRENT) == 0) { if (windows.SetFilePointerEx(self.handle, 0, &pos, windows.FILE_CURRENT) == 0) {
const err = windows.GetLastError(); const err = windows.GetLastError();
return switch (err) { return switch (err) {
windows.ERROR.INVALID_PARAMETER => error.BadFd, windows.ERROR.INVALID_PARAMETER => unreachable,
else => os.unexpectedErrorWindows(err), else => os.unexpectedErrorWindows(err),
}; };
} }
@ -287,7 +321,6 @@ pub const File = struct {
} }
pub const ModeError = error{ pub const ModeError = error{
BadFd,
SystemResources, SystemResources,
Unexpected, Unexpected,
}; };
@ -298,7 +331,9 @@ pub const File = struct {
const err = posix.getErrno(posix.fstat(self.handle, &stat)); const err = posix.getErrno(posix.fstat(self.handle, &stat));
if (err > 0) { if (err > 0) {
return switch (err) { return switch (err) {
posix.EBADF => error.BadFd, // We do not make this an error code because if you get EBADF it's always a bug,
// since the fd could have been reused.
posix.EBADF => unreachable,
posix.ENOMEM => error.SystemResources, posix.ENOMEM => error.SystemResources,
else => os.unexpectedErrorPosix(err), else => os.unexpectedErrorPosix(err),
}; };

View File

@ -10,6 +10,7 @@ pub const GetAppDataDirError = error{
}; };
/// Caller owns returned memory. /// Caller owns returned memory.
/// TODO determine if we can remove the allocator requirement
pub fn getAppDataDir(allocator: *mem.Allocator, appname: []const u8) GetAppDataDirError![]u8 { pub fn getAppDataDir(allocator: *mem.Allocator, appname: []const u8) GetAppDataDirError![]u8 {
switch (builtin.os) { switch (builtin.os) {
builtin.Os.windows => { builtin.Os.windows => {
@ -22,7 +23,7 @@ pub fn getAppDataDir(allocator: *mem.Allocator, appname: []const u8) GetAppDataD
)) { )) {
os.windows.S_OK => { os.windows.S_OK => {
defer os.windows.CoTaskMemFree(@ptrCast(*c_void, dir_path_ptr)); defer os.windows.CoTaskMemFree(@ptrCast(*c_void, dir_path_ptr));
const global_dir = unicode.utf16leToUtf8(allocator, utf16lePtrSlice(dir_path_ptr)) catch |err| switch (err) { const global_dir = unicode.utf16leToUtf8Alloc(allocator, utf16lePtrSlice(dir_path_ptr)) catch |err| switch (err) {
error.UnexpectedSecondSurrogateHalf => return error.AppDataDirUnavailable, error.UnexpectedSecondSurrogateHalf => return error.AppDataDirUnavailable,
error.ExpectedSecondSurrogateHalf => return error.AppDataDirUnavailable, error.ExpectedSecondSurrogateHalf => return error.AppDataDirUnavailable,
error.DanglingSurrogateHalf => return error.AppDataDirUnavailable, error.DanglingSurrogateHalf => return error.AppDataDirUnavailable,

View File

@ -39,6 +39,15 @@ pub const File = @import("file.zig").File;
pub const time = @import("time.zig"); pub const time = @import("time.zig");
pub const page_size = 4 * 1024; pub const page_size = 4 * 1024;
pub const MAX_PATH_BYTES = switch (builtin.os) {
Os.linux, Os.macosx, Os.ios => posix.PATH_MAX,
// Each UTF-16LE character may be expanded to 3 UTF-8 bytes.
// If it would require 4 UTF-8 bytes, then there would be a surrogate
// pair in the UTF-16LE, and we (over)account 3 bytes for it that way.
// +1 for the null byte at the end, which can be encoded in 1 byte.
Os.windows => windows_util.PATH_MAX_WIDE * 3 + 1,
else => @compileError("Unsupported OS"),
};
pub const UserInfo = @import("get_user_id.zig").UserInfo; pub const UserInfo = @import("get_user_id.zig").UserInfo;
pub const getUserInfo = @import("get_user_id.zig").getUserInfo; pub const getUserInfo = @import("get_user_id.zig").getUserInfo;
@ -317,6 +326,8 @@ pub const PosixWriteError = error{
NoSpaceLeft, NoSpaceLeft,
AccessDenied, AccessDenied,
BrokenPipe, BrokenPipe,
/// See https://github.com/ziglang/zig/issues/1396
Unexpected, Unexpected,
}; };
@ -417,7 +428,6 @@ pub fn posix_pwritev(fd: i32, iov: [*]const posix.iovec_const, count: usize, off
} }
pub const PosixOpenError = error{ pub const PosixOpenError = error{
OutOfMemory,
AccessDenied, AccessDenied,
FileTooBig, FileTooBig,
IsDir, IsDir,
@ -426,22 +436,22 @@ pub const PosixOpenError = error{
NameTooLong, NameTooLong,
SystemFdQuotaExceeded, SystemFdQuotaExceeded,
NoDevice, NoDevice,
PathNotFound, FileNotFound,
SystemResources, SystemResources,
NoSpaceLeft, NoSpaceLeft,
NotDir, NotDir,
PathAlreadyExists, PathAlreadyExists,
/// See https://github.com/ziglang/zig/issues/1396
Unexpected, Unexpected,
}; };
/// ::file_path needs to be copied in memory to add a null terminating byte. /// ::file_path needs to be copied in memory to add a null terminating byte.
/// Calls POSIX open, keeps trying if it gets interrupted, and translates /// Calls POSIX open, keeps trying if it gets interrupted, and translates
/// the return value into zig errors. /// the return value into zig errors.
pub fn posixOpen(allocator: *Allocator, file_path: []const u8, flags: u32, perm: usize) PosixOpenError!i32 { pub fn posixOpen(file_path: []const u8, flags: u32, perm: usize) PosixOpenError!i32 {
const path_with_null = try cstr.addNullByte(allocator, file_path); const file_path_c = try toPosixPath(file_path);
defer allocator.free(path_with_null); return posixOpenC(&file_path_c, flags, perm);
return posixOpenC(path_with_null.ptr, flags, perm);
} }
// TODO https://github.com/ziglang/zig/issues/265 // TODO https://github.com/ziglang/zig/issues/265
@ -463,7 +473,7 @@ pub fn posixOpenC(file_path: [*]const u8, flags: u32, perm: usize) !i32 {
posix.ENAMETOOLONG => return PosixOpenError.NameTooLong, posix.ENAMETOOLONG => return PosixOpenError.NameTooLong,
posix.ENFILE => return PosixOpenError.SystemFdQuotaExceeded, posix.ENFILE => return PosixOpenError.SystemFdQuotaExceeded,
posix.ENODEV => return PosixOpenError.NoDevice, posix.ENODEV => return PosixOpenError.NoDevice,
posix.ENOENT => return PosixOpenError.PathNotFound, posix.ENOENT => return PosixOpenError.FileNotFound,
posix.ENOMEM => return PosixOpenError.SystemResources, posix.ENOMEM => return PosixOpenError.SystemResources,
posix.ENOSPC => return PosixOpenError.NoSpaceLeft, posix.ENOSPC => return PosixOpenError.NoSpaceLeft,
posix.ENOTDIR => return PosixOpenError.NotDir, posix.ENOTDIR => return PosixOpenError.NotDir,
@ -476,6 +486,16 @@ pub fn posixOpenC(file_path: [*]const u8, flags: u32, perm: usize) !i32 {
} }
} }
/// Used to convert a slice to a null terminated slice on the stack.
/// TODO well defined copy elision
pub fn toPosixPath(file_path: []const u8) ![posix.PATH_MAX]u8 {
var path_with_null: [posix.PATH_MAX]u8 = undefined;
if (file_path.len >= posix.PATH_MAX) return error.NameTooLong;
mem.copy(u8, path_with_null[0..], file_path);
path_with_null[file_path.len] = 0;
return path_with_null;
}
pub fn posixDup2(old_fd: i32, new_fd: i32) !void { pub fn posixDup2(old_fd: i32, new_fd: i32) !void {
while (true) { while (true) {
const err = posix.getErrno(posix.dup2(old_fd, new_fd)); const err = posix.getErrno(posix.dup2(old_fd, new_fd));
@ -591,6 +611,8 @@ pub const PosixExecveError = error{
FileNotFound, FileNotFound,
NotDir, NotDir,
FileBusy, FileBusy,
/// See https://github.com/ziglang/zig/issues/1396
Unexpected, Unexpected,
}; };
@ -719,43 +741,39 @@ pub fn getEnvVarOwned(allocator: *mem.Allocator, key: []const u8) GetEnvVarOwned
} }
/// Caller must free the returned memory. /// Caller must free the returned memory.
pub fn getCwd(allocator: *Allocator) ![]u8 { pub fn getCwdAlloc(allocator: *Allocator) ![]u8 {
var buf: [MAX_PATH_BYTES]u8 = undefined;
return mem.dupe(allocator, u8, try getCwd(&buf));
}
pub const GetCwdError = error{Unexpected};
/// The result is a slice of out_buffer.
pub fn getCwd(out_buffer: *[MAX_PATH_BYTES]u8) GetCwdError![]u8 {
switch (builtin.os) { switch (builtin.os) {
Os.windows => { Os.windows => {
var buf = try allocator.alloc(u8, 256); var utf16le_buf: [windows_util.PATH_MAX_WIDE]u16 = undefined;
errdefer allocator.free(buf); const casted_len = @intCast(windows.DWORD, utf16le_buf.len); // TODO shouldn't need this cast
const casted_ptr = ([*]u16)(&utf16le_buf); // TODO shouldn't need this cast
while (true) { const result = windows.GetCurrentDirectoryW(casted_len, casted_ptr);
const result = windows.GetCurrentDirectoryA(@intCast(windows.WORD, buf.len), buf.ptr); if (result == 0) {
const err = windows.GetLastError();
if (result == 0) { switch (err) {
const err = windows.GetLastError(); else => return unexpectedErrorWindows(err),
return switch (err) {
else => unexpectedErrorWindows(err),
};
} }
if (result > buf.len) {
buf = try allocator.realloc(u8, buf, result);
continue;
}
return allocator.shrink(u8, buf, result);
} }
assert(result <= utf16le_buf.len);
const utf16le_slice = utf16le_buf[0..result];
// Trust that Windows gives us valid UTF-16LE.
const end_index = std.unicode.utf16leToUtf8(out_buffer, utf16le_slice) catch unreachable;
return out_buffer[0..end_index];
}, },
else => { else => {
var buf = try allocator.alloc(u8, 1024); const err = posix.getErrno(posix.getcwd(out_buffer, out_buffer.len));
errdefer allocator.free(buf); switch (err) {
while (true) { 0 => return cstr.toSlice(out_buffer),
const err = posix.getErrno(posix.getcwd(buf.ptr, buf.len)); posix.ERANGE => unreachable,
if (err == posix.ERANGE) { else => return unexpectedErrorPosix(err),
buf = try allocator.realloc(u8, buf, buf.len * 2);
continue;
} else if (err > 0) {
return unexpectedErrorPosix(err);
}
return allocator.shrink(u8, buf, cstr.len(buf.ptr));
} }
}, },
} }
@ -763,7 +781,9 @@ pub fn getCwd(allocator: *Allocator) ![]u8 {
test "os.getCwd" { test "os.getCwd" {
// at least call it so it gets compiled // at least call it so it gets compiled
_ = getCwd(debug.global_allocator); _ = getCwdAlloc(debug.global_allocator);
var buf: [MAX_PATH_BYTES]u8 = undefined;
_ = getCwd(&buf);
} }
pub const SymLinkError = PosixSymLinkError || WindowsSymLinkError; pub const SymLinkError = PosixSymLinkError || WindowsSymLinkError;
@ -778,6 +798,8 @@ pub fn symLink(allocator: *Allocator, existing_path: []const u8, new_path: []con
pub const WindowsSymLinkError = error{ pub const WindowsSymLinkError = error{
OutOfMemory, OutOfMemory,
/// See https://github.com/ziglang/zig/issues/1396
Unexpected, Unexpected,
}; };
@ -808,6 +830,8 @@ pub const PosixSymLinkError = error{
NoSpaceLeft, NoSpaceLeft,
ReadOnlyFileSystem, ReadOnlyFileSystem,
NotDir, NotDir,
/// See https://github.com/ziglang/zig/issues/1396
Unexpected, Unexpected,
}; };
@ -866,7 +890,7 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path:
b64_fs_encoder.encode(tmp_path[dirname.len + 1 ..], rand_buf); b64_fs_encoder.encode(tmp_path[dirname.len + 1 ..], rand_buf);
if (symLink(allocator, existing_path, tmp_path)) { if (symLink(allocator, existing_path, tmp_path)) {
return rename(allocator, tmp_path, new_path); return rename(tmp_path, new_path);
} else |err| switch (err) { } else |err| switch (err) {
error.PathAlreadyExists => continue, error.PathAlreadyExists => continue,
else => return err, // TODO zig should know this set does not include PathAlreadyExists else => return err, // TODO zig should know this set does not include PathAlreadyExists
@ -885,70 +909,75 @@ pub const DeleteFileError = error{
NotDir, NotDir,
SystemResources, SystemResources,
ReadOnlyFileSystem, ReadOnlyFileSystem,
OutOfMemory,
/// On Windows, file paths must be valid Unicode.
InvalidUtf8,
/// On Windows, file paths cannot contain these characters:
/// '/', '*', '?', '"', '<', '>', '|'
BadPathName,
/// See https://github.com/ziglang/zig/issues/1396
Unexpected, Unexpected,
}; };
pub fn deleteFile(allocator: *Allocator, file_path: []const u8) DeleteFileError!void { pub fn deleteFile(file_path: []const u8) DeleteFileError!void {
if (builtin.os == Os.windows) { if (builtin.os == Os.windows) {
return deleteFileWindows(allocator, file_path); return deleteFileWindows(file_path);
} else { } else {
return deleteFilePosix(allocator, file_path); return deleteFilePosix(file_path);
} }
} }
pub fn deleteFileWindows(allocator: *Allocator, file_path: []const u8) !void { pub fn deleteFileWindows(file_path: []const u8) !void {
const buf = try allocator.alloc(u8, file_path.len + 1); const file_path_w = try windows_util.sliceToPrefixedFileW(file_path);
defer allocator.free(buf);
mem.copy(u8, buf, file_path); if (windows.DeleteFileW(&file_path_w) == 0) {
buf[file_path.len] = 0;
if (windows.DeleteFileA(buf.ptr) == 0) {
const err = windows.GetLastError(); const err = windows.GetLastError();
return switch (err) { switch (err) {
windows.ERROR.FILE_NOT_FOUND => error.FileNotFound, windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound,
windows.ERROR.ACCESS_DENIED => error.AccessDenied, windows.ERROR.ACCESS_DENIED => return error.AccessDenied,
windows.ERROR.FILENAME_EXCED_RANGE, windows.ERROR.INVALID_PARAMETER => error.NameTooLong, windows.ERROR.FILENAME_EXCED_RANGE => return error.NameTooLong,
else => unexpectedErrorWindows(err), windows.ERROR.INVALID_PARAMETER => return error.NameTooLong,
}; else => return unexpectedErrorWindows(err),
}
} }
} }
pub fn deleteFilePosix(allocator: *Allocator, file_path: []const u8) !void { pub fn deleteFilePosixC(file_path: [*]const u8) !void {
const buf = try allocator.alloc(u8, file_path.len + 1); const err = posix.getErrno(posix.unlink(file_path));
defer allocator.free(buf); switch (err) {
0 => return,
mem.copy(u8, buf, file_path); posix.EACCES => return error.AccessDenied,
buf[file_path.len] = 0; posix.EPERM => return error.AccessDenied,
posix.EBUSY => return error.FileBusy,
const err = posix.getErrno(posix.unlink(buf.ptr)); posix.EFAULT => unreachable,
if (err > 0) { posix.EINVAL => unreachable,
return switch (err) { posix.EIO => return error.FileSystem,
posix.EACCES, posix.EPERM => error.AccessDenied, posix.EISDIR => return error.IsDir,
posix.EBUSY => error.FileBusy, posix.ELOOP => return error.SymLinkLoop,
posix.EFAULT, posix.EINVAL => unreachable, posix.ENAMETOOLONG => return error.NameTooLong,
posix.EIO => error.FileSystem, posix.ENOENT => return error.FileNotFound,
posix.EISDIR => error.IsDir, posix.ENOTDIR => return error.NotDir,
posix.ELOOP => error.SymLinkLoop, posix.ENOMEM => return error.SystemResources,
posix.ENAMETOOLONG => error.NameTooLong, posix.EROFS => return error.ReadOnlyFileSystem,
posix.ENOENT => error.FileNotFound, else => return unexpectedErrorPosix(err),
posix.ENOTDIR => error.NotDir,
posix.ENOMEM => error.SystemResources,
posix.EROFS => error.ReadOnlyFileSystem,
else => unexpectedErrorPosix(err),
};
} }
} }
pub fn deleteFilePosix(file_path: []const u8) !void {
const file_path_c = try toPosixPath(file_path);
return deleteFilePosixC(&file_path_c);
}
/// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is /// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is
/// merged and readily available, /// merged and readily available,
/// there is a possibility of power loss or application termination leaving temporary files present /// there is a possibility of power loss or application termination leaving temporary files present
/// in the same directory as dest_path. /// in the same directory as dest_path.
/// Destination file will have the same mode as the source file. /// Destination file will have the same mode as the source file.
/// TODO investigate if this can work with no allocator
pub fn copyFile(allocator: *Allocator, source_path: []const u8, dest_path: []const u8) !void { pub fn copyFile(allocator: *Allocator, source_path: []const u8, dest_path: []const u8) !void {
var in_file = try os.File.openRead(allocator, source_path); var in_file = try os.File.openRead(source_path);
defer in_file.close(); defer in_file.close();
const mode = try in_file.mode(); const mode = try in_file.mode();
@ -969,8 +998,9 @@ pub fn copyFile(allocator: *Allocator, source_path: []const u8, dest_path: []con
/// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is /// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is
/// merged and readily available, /// merged and readily available,
/// there is a possibility of power loss or application termination leaving temporary files present /// there is a possibility of power loss or application termination leaving temporary files present
/// TODO investigate if this can work with no allocator
pub fn copyFileMode(allocator: *Allocator, source_path: []const u8, dest_path: []const u8, mode: File.Mode) !void { pub fn copyFileMode(allocator: *Allocator, source_path: []const u8, dest_path: []const u8, mode: File.Mode) !void {
var in_file = try os.File.openRead(allocator, source_path); var in_file = try os.File.openRead(source_path);
defer in_file.close(); defer in_file.close();
var atomic_file = try AtomicFile.init(allocator, dest_path, mode); var atomic_file = try AtomicFile.init(allocator, dest_path, mode);
@ -987,6 +1017,7 @@ pub fn copyFileMode(allocator: *Allocator, source_path: []const u8, dest_path: [
} }
pub const AtomicFile = struct { pub const AtomicFile = struct {
/// TODO investigate if we can make this work with no allocator
allocator: *Allocator, allocator: *Allocator,
file: os.File, file: os.File,
tmp_path: []u8, tmp_path: []u8,
@ -1014,7 +1045,7 @@ pub const AtomicFile = struct {
try getRandomBytes(rand_buf[0..]); try getRandomBytes(rand_buf[0..]);
b64_fs_encoder.encode(tmp_path[dirname_component_len..], rand_buf); b64_fs_encoder.encode(tmp_path[dirname_component_len..], rand_buf);
const file = os.File.openWriteNoClobber(allocator, tmp_path, mode) catch |err| switch (err) { const file = os.File.openWriteNoClobber(tmp_path, mode) catch |err| switch (err) {
error.PathAlreadyExists => continue, error.PathAlreadyExists => continue,
// TODO zig should figure out that this error set does not include PathAlreadyExists since // TODO zig should figure out that this error set does not include PathAlreadyExists since
// it is handled in the above switch // it is handled in the above switch
@ -1035,7 +1066,7 @@ pub const AtomicFile = struct {
pub fn deinit(self: *AtomicFile) void { pub fn deinit(self: *AtomicFile) void {
if (!self.finished) { if (!self.finished) {
self.file.close(); self.file.close();
deleteFile(self.allocator, self.tmp_path) catch {}; deleteFile(self.tmp_path) catch {};
self.allocator.free(self.tmp_path); self.allocator.free(self.tmp_path);
self.finished = true; self.finished = true;
} }
@ -1044,70 +1075,72 @@ pub const AtomicFile = struct {
pub fn finish(self: *AtomicFile) !void { pub fn finish(self: *AtomicFile) !void {
assert(!self.finished); assert(!self.finished);
self.file.close(); self.file.close();
try rename(self.allocator, self.tmp_path, self.dest_path); try rename(self.tmp_path, self.dest_path);
self.allocator.free(self.tmp_path); self.allocator.free(self.tmp_path);
self.finished = true; self.finished = true;
} }
}; };
pub fn rename(allocator: *Allocator, old_path: []const u8, new_path: []const u8) !void { pub fn renameC(old_path: [*]const u8, new_path: [*]const u8) !void {
const full_buf = try allocator.alloc(u8, old_path.len + new_path.len + 2); if (is_windows) {
defer allocator.free(full_buf); @compileError("TODO implement for windows");
} else {
const old_buf = full_buf; const err = posix.getErrno(posix.rename(old_path, new_path));
mem.copy(u8, old_buf, old_path); switch (err) {
old_buf[old_path.len] = 0; 0 => return,
posix.EACCES => return error.AccessDenied,
const new_buf = full_buf[old_path.len + 1 ..]; posix.EPERM => return error.AccessDenied,
mem.copy(u8, new_buf, new_path); posix.EBUSY => return error.FileBusy,
new_buf[new_path.len] = 0; posix.EDQUOT => return error.DiskQuota,
posix.EFAULT => unreachable,
posix.EINVAL => unreachable,
posix.EISDIR => return error.IsDir,
posix.ELOOP => return error.SymLinkLoop,
posix.EMLINK => return error.LinkQuotaExceeded,
posix.ENAMETOOLONG => return error.NameTooLong,
posix.ENOENT => return error.FileNotFound,
posix.ENOTDIR => return error.NotDir,
posix.ENOMEM => return error.SystemResources,
posix.ENOSPC => return error.NoSpaceLeft,
posix.EEXIST => return error.PathAlreadyExists,
posix.ENOTEMPTY => return error.PathAlreadyExists,
posix.EROFS => return error.ReadOnlyFileSystem,
posix.EXDEV => return error.RenameAcrossMountPoints,
else => return unexpectedErrorPosix(err),
}
}
}
pub fn rename(old_path: []const u8, new_path: []const u8) !void {
if (is_windows) { if (is_windows) {
const flags = windows.MOVEFILE_REPLACE_EXISTING | windows.MOVEFILE_WRITE_THROUGH; const flags = windows.MOVEFILE_REPLACE_EXISTING | windows.MOVEFILE_WRITE_THROUGH;
if (windows.MoveFileExA(old_buf.ptr, new_buf.ptr, flags) == 0) { const old_path_w = try windows_util.sliceToPrefixedFileW(old_path);
const new_path_w = try windows_util.sliceToPrefixedFileW(new_path);
if (windows.MoveFileExW(&old_path_w, &new_path_w, flags) == 0) {
const err = windows.GetLastError(); const err = windows.GetLastError();
return switch (err) { switch (err) {
else => unexpectedErrorWindows(err), else => return unexpectedErrorWindows(err),
}; }
} }
} else { } else {
const err = posix.getErrno(posix.rename(old_buf.ptr, new_buf.ptr)); const old_path_c = try toPosixPath(old_path);
if (err > 0) { const new_path_c = try toPosixPath(new_path);
return switch (err) { return renameC(&old_path_c, &new_path_c);
posix.EACCES, posix.EPERM => error.AccessDenied,
posix.EBUSY => error.FileBusy,
posix.EDQUOT => error.DiskQuota,
posix.EFAULT, posix.EINVAL => unreachable,
posix.EISDIR => error.IsDir,
posix.ELOOP => error.SymLinkLoop,
posix.EMLINK => error.LinkQuotaExceeded,
posix.ENAMETOOLONG => error.NameTooLong,
posix.ENOENT => error.FileNotFound,
posix.ENOTDIR => error.NotDir,
posix.ENOMEM => error.SystemResources,
posix.ENOSPC => error.NoSpaceLeft,
posix.EEXIST, posix.ENOTEMPTY => error.PathAlreadyExists,
posix.EROFS => error.ReadOnlyFileSystem,
posix.EXDEV => error.RenameAcrossMountPoints,
else => unexpectedErrorPosix(err),
};
}
} }
} }
pub fn makeDir(allocator: *Allocator, dir_path: []const u8) !void { pub fn makeDir(dir_path: []const u8) !void {
if (is_windows) { if (is_windows) {
return makeDirWindows(allocator, dir_path); return makeDirWindows(dir_path);
} else { } else {
return makeDirPosix(allocator, dir_path); return makeDirPosix(dir_path);
} }
} }
pub fn makeDirWindows(allocator: *Allocator, dir_path: []const u8) !void { pub fn makeDirWindows(dir_path: []const u8) !void {
const path_buf = try cstr.addNullByte(allocator, dir_path); const dir_path_w = try windows_util.sliceToPrefixedFileW(dir_path);
defer allocator.free(path_buf);
if (windows.CreateDirectoryA(path_buf.ptr, null) == 0) { if (windows.CreateDirectoryW(&dir_path_w, null) == 0) {
const err = windows.GetLastError(); const err = windows.GetLastError();
return switch (err) { return switch (err) {
windows.ERROR.ALREADY_EXISTS => error.PathAlreadyExists, windows.ERROR.ALREADY_EXISTS => error.PathAlreadyExists,
@ -1117,39 +1150,42 @@ pub fn makeDirWindows(allocator: *Allocator, dir_path: []const u8) !void {
} }
} }
pub fn makeDirPosix(allocator: *Allocator, dir_path: []const u8) !void { pub fn makeDirPosixC(dir_path: [*]const u8) !void {
const path_buf = try cstr.addNullByte(allocator, dir_path); const err = posix.getErrno(posix.mkdir(dir_path, 0o755));
defer allocator.free(path_buf); switch (err) {
0 => return,
const err = posix.getErrno(posix.mkdir(path_buf.ptr, 0o755)); posix.EACCES => return error.AccessDenied,
if (err > 0) { posix.EPERM => return error.AccessDenied,
return switch (err) { posix.EDQUOT => return error.DiskQuota,
posix.EACCES, posix.EPERM => error.AccessDenied, posix.EEXIST => return error.PathAlreadyExists,
posix.EDQUOT => error.DiskQuota, posix.EFAULT => unreachable,
posix.EEXIST => error.PathAlreadyExists, posix.ELOOP => return error.SymLinkLoop,
posix.EFAULT => unreachable, posix.EMLINK => return error.LinkQuotaExceeded,
posix.ELOOP => error.SymLinkLoop, posix.ENAMETOOLONG => return error.NameTooLong,
posix.EMLINK => error.LinkQuotaExceeded, posix.ENOENT => return error.FileNotFound,
posix.ENAMETOOLONG => error.NameTooLong, posix.ENOMEM => return error.SystemResources,
posix.ENOENT => error.FileNotFound, posix.ENOSPC => return error.NoSpaceLeft,
posix.ENOMEM => error.SystemResources, posix.ENOTDIR => return error.NotDir,
posix.ENOSPC => error.NoSpaceLeft, posix.EROFS => return error.ReadOnlyFileSystem,
posix.ENOTDIR => error.NotDir, else => return unexpectedErrorPosix(err),
posix.EROFS => error.ReadOnlyFileSystem,
else => unexpectedErrorPosix(err),
};
} }
} }
pub fn makeDirPosix(dir_path: []const u8) !void {
const dir_path_c = try toPosixPath(dir_path);
return makeDirPosixC(&dir_path_c);
}
/// Calls makeDir recursively to make an entire path. Returns success if the path /// Calls makeDir recursively to make an entire path. Returns success if the path
/// already exists and is a directory. /// already exists and is a directory.
/// TODO determine if we can remove the allocator requirement from this function
pub fn makePath(allocator: *Allocator, full_path: []const u8) !void { pub fn makePath(allocator: *Allocator, full_path: []const u8) !void {
const resolved_path = try path.resolve(allocator, full_path); const resolved_path = try path.resolve(allocator, full_path);
defer allocator.free(resolved_path); defer allocator.free(resolved_path);
var end_index: usize = resolved_path.len; var end_index: usize = resolved_path.len;
while (true) { while (true) {
makeDir(allocator, resolved_path[0..end_index]) catch |err| switch (err) { makeDir(resolved_path[0..end_index]) catch |err| switch (err) {
error.PathAlreadyExists => { error.PathAlreadyExists => {
// TODO stat the file and return an error if it's not a directory // TODO stat the file and return an error if it's not a directory
// this is important because otherwise a dangling symlink // this is important because otherwise a dangling symlink
@ -1187,6 +1223,7 @@ pub const DeleteDirError = error{
ReadOnlyFileSystem, ReadOnlyFileSystem,
OutOfMemory, OutOfMemory,
/// See https://github.com/ziglang/zig/issues/1396
Unexpected, Unexpected,
}; };
@ -1245,7 +1282,6 @@ const DeleteTreeError = error{
NameTooLong, NameTooLong,
SystemFdQuotaExceeded, SystemFdQuotaExceeded,
NoDevice, NoDevice,
PathNotFound,
SystemResources, SystemResources,
NoSpaceLeft, NoSpaceLeft,
PathAlreadyExists, PathAlreadyExists,
@ -1255,20 +1291,30 @@ const DeleteTreeError = error{
FileSystem, FileSystem,
FileBusy, FileBusy,
DirNotEmpty, DirNotEmpty,
/// On Windows, file paths must be valid Unicode.
InvalidUtf8,
/// On Windows, file paths cannot contain these characters:
/// '/', '*', '?', '"', '<', '>', '|'
BadPathName,
/// See https://github.com/ziglang/zig/issues/1396
Unexpected, Unexpected,
}; };
/// TODO determine if we can remove the allocator requirement
pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError!void { pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError!void {
start_over: while (true) { start_over: while (true) {
var got_access_denied = false; var got_access_denied = false;
// First, try deleting the item as a file. This way we don't follow sym links. // First, try deleting the item as a file. This way we don't follow sym links.
if (deleteFile(allocator, full_path)) { if (deleteFile(full_path)) {
return; return;
} else |err| switch (err) { } else |err| switch (err) {
error.FileNotFound => return, error.FileNotFound => return,
error.IsDir => {}, error.IsDir => {},
error.AccessDenied => got_access_denied = true, error.AccessDenied => got_access_denied = true,
error.OutOfMemory,
error.SymLinkLoop, error.SymLinkLoop,
error.NameTooLong, error.NameTooLong,
error.SystemResources, error.SystemResources,
@ -1276,6 +1322,8 @@ pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError!
error.NotDir, error.NotDir,
error.FileSystem, error.FileSystem,
error.FileBusy, error.FileBusy,
error.InvalidUtf8,
error.BadPathName,
error.Unexpected, error.Unexpected,
=> return err, => return err,
} }
@ -1297,7 +1345,7 @@ pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError!
error.NameTooLong, error.NameTooLong,
error.SystemFdQuotaExceeded, error.SystemFdQuotaExceeded,
error.NoDevice, error.NoDevice,
error.PathNotFound, error.FileNotFound,
error.SystemResources, error.SystemResources,
error.NoSpaceLeft, error.NoSpaceLeft,
error.PathAlreadyExists, error.PathAlreadyExists,
@ -1367,7 +1415,7 @@ pub const Dir = struct {
}; };
pub const OpenError = error{ pub const OpenError = error{
PathNotFound, FileNotFound,
NotDir, NotDir,
AccessDenied, AccessDenied,
FileTooBig, FileTooBig,
@ -1382,9 +1430,11 @@ pub const Dir = struct {
PathAlreadyExists, PathAlreadyExists,
OutOfMemory, OutOfMemory,
/// See https://github.com/ziglang/zig/issues/1396
Unexpected, Unexpected,
}; };
/// TODO remove the allocator requirement from this API
pub fn open(allocator: *Allocator, dir_path: []const u8) OpenError!Dir { pub fn open(allocator: *Allocator, dir_path: []const u8) OpenError!Dir {
return Dir{ return Dir{
.allocator = allocator, .allocator = allocator,
@ -1400,7 +1450,6 @@ pub const Dir = struct {
}, },
Os.macosx, Os.ios => Handle{ Os.macosx, Os.ios => Handle{
.fd = try posixOpen( .fd = try posixOpen(
allocator,
dir_path, dir_path,
posix.O_RDONLY | posix.O_NONBLOCK | posix.O_DIRECTORY | posix.O_CLOEXEC, posix.O_RDONLY | posix.O_NONBLOCK | posix.O_DIRECTORY | posix.O_CLOEXEC,
0, 0,
@ -1412,7 +1461,6 @@ pub const Dir = struct {
}, },
Os.linux => Handle{ Os.linux => Handle{
.fd = try posixOpen( .fd = try posixOpen(
allocator,
dir_path, dir_path,
posix.O_RDONLY | posix.O_DIRECTORY | posix.O_CLOEXEC, posix.O_RDONLY | posix.O_DIRECTORY | posix.O_CLOEXEC,
0, 0,
@ -1609,39 +1657,32 @@ pub fn changeCurDir(allocator: *Allocator, dir_path: []const u8) !void {
} }
/// Read value of a symbolic link. /// Read value of a symbolic link.
pub fn readLink(allocator: *Allocator, pathname: []const u8) ![]u8 { /// The return value is a slice of out_buffer.
const path_buf = try allocator.alloc(u8, pathname.len + 1); pub fn readLinkC(out_buffer: *[posix.PATH_MAX]u8, pathname: [*]const u8) ![]u8 {
defer allocator.free(path_buf); const rc = posix.readlink(pathname, out_buffer, out_buffer.len);
const err = posix.getErrno(rc);
mem.copy(u8, path_buf, pathname); switch (err) {
path_buf[pathname.len] = 0; 0 => return out_buffer[0..rc],
posix.EACCES => return error.AccessDenied,
var result_buf = try allocator.alloc(u8, 1024); posix.EFAULT => unreachable,
errdefer allocator.free(result_buf); posix.EINVAL => unreachable,
while (true) { posix.EIO => return error.FileSystem,
const ret_val = posix.readlink(path_buf.ptr, result_buf.ptr, result_buf.len); posix.ELOOP => return error.SymLinkLoop,
const err = posix.getErrno(ret_val); posix.ENAMETOOLONG => unreachable, // out_buffer is at least PATH_MAX
if (err > 0) { posix.ENOENT => return error.FileNotFound,
return switch (err) { posix.ENOMEM => return error.SystemResources,
posix.EACCES => error.AccessDenied, posix.ENOTDIR => return error.NotDir,
posix.EFAULT, posix.EINVAL => unreachable, else => return unexpectedErrorPosix(err),
posix.EIO => error.FileSystem,
posix.ELOOP => error.SymLinkLoop,
posix.ENAMETOOLONG => error.NameTooLong,
posix.ENOENT => error.FileNotFound,
posix.ENOMEM => error.SystemResources,
posix.ENOTDIR => error.NotDir,
else => unexpectedErrorPosix(err),
};
}
if (ret_val == result_buf.len) {
result_buf = try allocator.realloc(u8, result_buf, result_buf.len * 2);
continue;
}
return allocator.shrink(u8, result_buf, ret_val);
} }
} }
/// Read value of a symbolic link.
/// The return value is a slice of out_buffer.
pub fn readLink(out_buffer: *[posix.PATH_MAX]u8, file_path: []const u8) ![]u8 {
const file_path_c = try toPosixPath(file_path);
return readLinkC(out_buffer, &file_path_c);
}
pub fn posix_setuid(uid: u32) !void { pub fn posix_setuid(uid: u32) !void {
const err = posix.getErrno(posix.setuid(uid)); const err = posix.getErrno(posix.setuid(uid));
if (err == 0) return; if (err == 0) return;
@ -1688,6 +1729,8 @@ pub fn posix_setregid(rgid: u32, egid: u32) !void {
pub const WindowsGetStdHandleErrs = error{ pub const WindowsGetStdHandleErrs = error{
NoStdHandles, NoStdHandles,
/// See https://github.com/ziglang/zig/issues/1396
Unexpected, Unexpected,
}; };
@ -2015,7 +2058,7 @@ pub fn unexpectedErrorPosix(errno: usize) UnexpectedError {
/// Call this when you made a windows DLL call or something that does SetLastError /// Call this when you made a windows DLL call or something that does SetLastError
/// and you get an unexpected error. /// and you get an unexpected error.
pub fn unexpectedErrorWindows(err: windows.DWORD) UnexpectedError { pub fn unexpectedErrorWindows(err: windows.DWORD) UnexpectedError {
if (unexpected_error_tracing) { if (true) {
debug.warn("unexpected GetLastError(): {}\n", err); debug.warn("unexpected GetLastError(): {}\n", err);
debug.dumpCurrentStackTrace(null); debug.dumpCurrentStackTrace(null);
} }
@ -2024,17 +2067,12 @@ pub fn unexpectedErrorWindows(err: windows.DWORD) UnexpectedError {
pub fn openSelfExe() !os.File { pub fn openSelfExe() !os.File {
switch (builtin.os) { switch (builtin.os) {
Os.linux => { Os.linux => return os.File.openReadC(c"/proc/self/exe"),
const proc_file_path = "/proc/self/exe";
var fixed_buffer_mem: [proc_file_path.len + 1]u8 = undefined;
var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]);
return os.File.openRead(&fixed_allocator.allocator, proc_file_path);
},
Os.macosx, Os.ios => { Os.macosx, Os.ios => {
var fixed_buffer_mem: [darwin.PATH_MAX * 2]u8 = undefined; var buf: [MAX_PATH_BYTES]u8 = undefined;
var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]); const self_exe_path = try selfExePath(&buf);
const self_exe_path = try selfExePath(&fixed_allocator.allocator); buf[self_exe_path.len] = 0;
return os.File.openRead(&fixed_allocator.allocator, self_exe_path); return os.File.openReadC(self_exe_path.ptr);
}, },
else => @compileError("Unsupported OS"), else => @compileError("Unsupported OS"),
} }
@ -2043,7 +2081,7 @@ pub fn openSelfExe() !os.File {
test "openSelfExe" { test "openSelfExe" {
switch (builtin.os) { switch (builtin.os) {
Os.linux, Os.macosx, Os.ios => (try openSelfExe()).close(), Os.linux, Os.macosx, Os.ios => (try openSelfExe()).close(),
else => return, // Unsupported OS. else => return error.SkipZigTest, // Unsupported OS
} }
} }
@ -2052,69 +2090,68 @@ test "openSelfExe" {
/// If you only want an open file handle, use openSelfExe. /// If you only want an open file handle, use openSelfExe.
/// This function may return an error if the current executable /// This function may return an error if the current executable
/// was deleted after spawning. /// was deleted after spawning.
/// Caller owns returned memory. /// Returned value is a slice of out_buffer.
pub fn selfExePath(allocator: *mem.Allocator) ![]u8 { ///
/// On Linux, depends on procfs being mounted. If the currently executing binary has
/// been deleted, the file path looks something like `/a/b/c/exe (deleted)`.
/// TODO make the return type of this a null terminated pointer
pub fn selfExePath(out_buffer: *[MAX_PATH_BYTES]u8) ![]u8 {
switch (builtin.os) { switch (builtin.os) {
Os.linux => { Os.linux => return readLink(out_buffer, "/proc/self/exe"),
// If the currently executing binary has been deleted,
// the file path looks something like `/a/b/c/exe (deleted)`
return readLink(allocator, "/proc/self/exe");
},
Os.windows => { Os.windows => {
var out_path = try Buffer.initSize(allocator, 0xff); var utf16le_buf: [windows_util.PATH_MAX_WIDE]u16 = undefined;
errdefer out_path.deinit(); const casted_len = @intCast(windows.DWORD, utf16le_buf.len); // TODO shouldn't need this cast
while (true) { const rc = windows.GetModuleFileNameW(null, &utf16le_buf, casted_len);
const dword_len = try math.cast(windows.DWORD, out_path.len()); assert(rc <= utf16le_buf.len);
const copied_amt = windows.GetModuleFileNameA(null, out_path.ptr(), dword_len); if (rc == 0) {
if (copied_amt <= 0) { const err = windows.GetLastError();
const err = windows.GetLastError(); switch (err) {
return switch (err) { else => return unexpectedErrorWindows(err),
else => unexpectedErrorWindows(err),
};
} }
if (copied_amt < out_path.len()) {
out_path.shrink(copied_amt);
return out_path.toOwnedSlice();
}
const new_len = (out_path.len() << 1) | 0b1;
try out_path.resize(new_len);
} }
const utf16le_slice = utf16le_buf[0..rc];
// Trust that Windows gives us valid UTF-16LE.
const end_index = std.unicode.utf16leToUtf8(out_buffer, utf16le_slice) catch unreachable;
return out_buffer[0..end_index];
}, },
Os.macosx, Os.ios => { Os.macosx, Os.ios => {
var u32_len: u32 = 0; var u32_len: u32 = @intCast(u32, out_buffer.len); // TODO shouldn't need this cast
const ret1 = c._NSGetExecutablePath(undefined, &u32_len); const rc = c._NSGetExecutablePath(out_buffer, &u32_len);
assert(ret1 != 0); if (rc != 0) return error.NameTooLong;
const bytes = try allocator.alloc(u8, u32_len); return mem.toSlice(u8, out_buffer);
errdefer allocator.free(bytes);
const ret2 = c._NSGetExecutablePath(bytes.ptr, &u32_len);
assert(ret2 == 0);
return bytes;
}, },
else => @compileError("Unsupported OS"), else => @compileError("Unsupported OS"),
} }
} }
/// Get the directory path that contains the current executable. /// `selfExeDirPath` except allocates the result on the heap.
/// Caller owns returned memory. /// Caller owns returned memory.
pub fn selfExeDirPath(allocator: *mem.Allocator) ![]u8 { pub fn selfExeDirPathAlloc(allocator: *Allocator) ![]u8 {
var buf: [MAX_PATH_BYTES]u8 = undefined;
return mem.dupe(allocator, u8, try selfExeDirPath(&buf));
}
/// Get the directory path that contains the current executable.
/// Returned value is a slice of out_buffer.
pub fn selfExeDirPath(out_buffer: *[MAX_PATH_BYTES]u8) ![]const u8 {
switch (builtin.os) { switch (builtin.os) {
Os.linux => { Os.linux => {
// If the currently executing binary has been deleted, // If the currently executing binary has been deleted,
// the file path looks something like `/a/b/c/exe (deleted)` // the file path looks something like `/a/b/c/exe (deleted)`
// This path cannot be opened, but it's valid for determining the directory // This path cannot be opened, but it's valid for determining the directory
// the executable was in when it was run. // the executable was in when it was run.
const full_exe_path = try readLink(allocator, "/proc/self/exe"); const full_exe_path = try readLinkC(out_buffer, c"/proc/self/exe");
errdefer allocator.free(full_exe_path); // Assume that /proc/self/exe has an absolute path, and therefore dirname
const dir = path.dirname(full_exe_path) orelse "."; // will not return null.
return allocator.shrink(u8, full_exe_path, dir.len); return path.dirname(full_exe_path).?;
}, },
Os.windows, Os.macosx, Os.ios => { Os.windows, Os.macosx, Os.ios => {
const self_exe_path = try selfExePath(allocator); const self_exe_path = try selfExePath(out_buffer);
errdefer allocator.free(self_exe_path); // Assume that the OS APIs return absolute paths, and therefore dirname
const dirname = os.path.dirname(self_exe_path) orelse "."; // will not return null.
return allocator.shrink(u8, self_exe_path, dirname.len); return path.dirname(self_exe_path).?;
}, },
else => @compileError("unimplemented: std.os.selfExeDirPath for " ++ @tagName(builtin.os)), else => @compileError("Unsupported OS"),
} }
} }
@ -2218,6 +2255,7 @@ pub const PosixBindError = error{
/// The socket inode would reside on a read-only filesystem. /// The socket inode would reside on a read-only filesystem.
ReadOnlyFileSystem, ReadOnlyFileSystem,
/// See https://github.com/ziglang/zig/issues/1396
Unexpected, Unexpected,
}; };
@ -2261,6 +2299,7 @@ const PosixListenError = error{
/// The socket is not of a type that supports the listen() operation. /// The socket is not of a type that supports the listen() operation.
OperationNotSupported, OperationNotSupported,
/// See https://github.com/ziglang/zig/issues/1396
Unexpected, Unexpected,
}; };
@ -2314,6 +2353,7 @@ pub const PosixAcceptError = error{
/// Firewall rules forbid connection. /// Firewall rules forbid connection.
BlockedByFirewall, BlockedByFirewall,
/// See https://github.com/ziglang/zig/issues/1396
Unexpected, Unexpected,
}; };
@ -2359,6 +2399,7 @@ pub const LinuxEpollCreateError = error{
/// There was insufficient memory to create the kernel object. /// There was insufficient memory to create the kernel object.
SystemResources, SystemResources,
/// See https://github.com/ziglang/zig/issues/1396
Unexpected, Unexpected,
}; };
@ -2413,6 +2454,7 @@ pub const LinuxEpollCtlError = error{
/// for example, a regular file or a directory. /// for example, a regular file or a directory.
FileDescriptorIncompatibleWithEpoll, FileDescriptorIncompatibleWithEpoll,
/// See https://github.com/ziglang/zig/issues/1396
Unexpected, Unexpected,
}; };
@ -2455,6 +2497,7 @@ pub const LinuxEventFdError = error{
ProcessFdQuotaExceeded, ProcessFdQuotaExceeded,
SystemFdQuotaExceeded, SystemFdQuotaExceeded,
/// See https://github.com/ziglang/zig/issues/1396
Unexpected, Unexpected,
}; };
@ -2477,6 +2520,7 @@ pub const PosixGetSockNameError = error{
/// Insufficient resources were available in the system to perform the operation. /// Insufficient resources were available in the system to perform the operation.
SystemResources, SystemResources,
/// See https://github.com/ziglang/zig/issues/1396
Unexpected, Unexpected,
}; };
@ -2530,6 +2574,7 @@ pub const PosixConnectError = error{
/// that for IP sockets the timeout may be very long when syncookies are enabled on the server. /// that for IP sockets the timeout may be very long when syncookies are enabled on the server.
ConnectionTimedOut, ConnectionTimedOut,
/// See https://github.com/ziglang/zig/issues/1396
Unexpected, Unexpected,
}; };
@ -2751,6 +2796,7 @@ pub const SpawnThreadError = error{
/// Not enough userland memory to spawn the thread. /// Not enough userland memory to spawn the thread.
OutOfMemory, OutOfMemory,
/// See https://github.com/ziglang/zig/issues/1396
Unexpected, Unexpected,
}; };
@ -2926,7 +2972,9 @@ pub fn posixFStat(fd: i32) !posix.Stat {
const err = posix.getErrno(posix.fstat(fd, &stat)); const err = posix.getErrno(posix.fstat(fd, &stat));
if (err > 0) { if (err > 0) {
return switch (err) { return switch (err) {
posix.EBADF => error.BadFd, // We do not make this an error code because if you get EBADF it's always a bug,
// since the fd could have been reused.
posix.EBADF => unreachable,
posix.ENOMEM => error.SystemResources, posix.ENOMEM => error.SystemResources,
else => os.unexpectedErrorPosix(err), else => os.unexpectedErrorPosix(err),
}; };
@ -2938,6 +2986,8 @@ pub fn posixFStat(fd: i32) !posix.Stat {
pub const CpuCountError = error{ pub const CpuCountError = error{
OutOfMemory, OutOfMemory,
PermissionDenied, PermissionDenied,
/// See https://github.com/ziglang/zig/issues/1396
Unexpected, Unexpected,
}; };
@ -3008,6 +3058,7 @@ pub const BsdKQueueError = error{
/// The system-wide limit on the total number of open files has been reached. /// The system-wide limit on the total number of open files has been reached.
SystemFdQuotaExceeded, SystemFdQuotaExceeded,
/// See https://github.com/ziglang/zig/issues/1396
Unexpected, Unexpected,
}; };

View File

@ -11,11 +11,14 @@ const math = std.math;
const posix = os.posix; const posix = os.posix;
const windows = os.windows; const windows = os.windows;
const cstr = std.cstr; const cstr = std.cstr;
const windows_util = @import("windows/util.zig");
pub const sep_windows = '\\'; pub const sep_windows = '\\';
pub const sep_posix = '/'; pub const sep_posix = '/';
pub const sep = if (is_windows) sep_windows else sep_posix; pub const sep = if (is_windows) sep_windows else sep_posix;
pub const sep_str = [1]u8{sep};
pub const delimiter_windows = ';'; pub const delimiter_windows = ';';
pub const delimiter_posix = ':'; pub const delimiter_posix = ':';
pub const delimiter = if (is_windows) delimiter_windows else delimiter_posix; pub const delimiter = if (is_windows) delimiter_windows else delimiter_posix;
@ -337,7 +340,7 @@ pub fn resolveSlice(allocator: *Allocator, paths: []const []const u8) ![]u8 {
pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 { pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 {
if (paths.len == 0) { if (paths.len == 0) {
assert(is_windows); // resolveWindows called on non windows can't use getCwd assert(is_windows); // resolveWindows called on non windows can't use getCwd
return os.getCwd(allocator); return os.getCwdAlloc(allocator);
} }
// determine which disk designator we will result with, if any // determine which disk designator we will result with, if any
@ -432,7 +435,7 @@ pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 {
}, },
WindowsPath.Kind.None => { WindowsPath.Kind.None => {
assert(is_windows); // resolveWindows called on non windows can't use getCwd assert(is_windows); // resolveWindows called on non windows can't use getCwd
const cwd = try os.getCwd(allocator); const cwd = try os.getCwdAlloc(allocator);
defer allocator.free(cwd); defer allocator.free(cwd);
const parsed_cwd = windowsParsePath(cwd); const parsed_cwd = windowsParsePath(cwd);
result = try allocator.alloc(u8, max_size + parsed_cwd.disk_designator.len + 1); result = try allocator.alloc(u8, max_size + parsed_cwd.disk_designator.len + 1);
@ -448,7 +451,7 @@ pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 {
} else { } else {
assert(is_windows); // resolveWindows called on non windows can't use getCwd assert(is_windows); // resolveWindows called on non windows can't use getCwd
// TODO call get cwd for the result_disk_designator instead of the global one // TODO call get cwd for the result_disk_designator instead of the global one
const cwd = try os.getCwd(allocator); const cwd = try os.getCwdAlloc(allocator);
defer allocator.free(cwd); defer allocator.free(cwd);
result = try allocator.alloc(u8, max_size + cwd.len + 1); result = try allocator.alloc(u8, max_size + cwd.len + 1);
@ -516,7 +519,7 @@ pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 {
pub fn resolvePosix(allocator: *Allocator, paths: []const []const u8) ![]u8 { pub fn resolvePosix(allocator: *Allocator, paths: []const []const u8) ![]u8 {
if (paths.len == 0) { if (paths.len == 0) {
assert(!is_windows); // resolvePosix called on windows can't use getCwd assert(!is_windows); // resolvePosix called on windows can't use getCwd
return os.getCwd(allocator); return os.getCwdAlloc(allocator);
} }
var first_index: usize = 0; var first_index: usize = 0;
@ -538,7 +541,7 @@ pub fn resolvePosix(allocator: *Allocator, paths: []const []const u8) ![]u8 {
result = try allocator.alloc(u8, max_size); result = try allocator.alloc(u8, max_size);
} else { } else {
assert(!is_windows); // resolvePosix called on windows can't use getCwd assert(!is_windows); // resolvePosix called on windows can't use getCwd
const cwd = try os.getCwd(allocator); const cwd = try os.getCwdAlloc(allocator);
defer allocator.free(cwd); defer allocator.free(cwd);
result = try allocator.alloc(u8, max_size + cwd.len + 1); result = try allocator.alloc(u8, max_size + cwd.len + 1);
mem.copy(u8, result, cwd); mem.copy(u8, result, cwd);
@ -573,11 +576,11 @@ pub fn resolvePosix(allocator: *Allocator, paths: []const []const u8) ![]u8 {
result_index += 1; result_index += 1;
} }
return result[0..result_index]; return allocator.shrink(u8, result, result_index);
} }
test "os.path.resolve" { test "os.path.resolve" {
const cwd = try os.getCwd(debug.global_allocator); const cwd = try os.getCwdAlloc(debug.global_allocator);
if (is_windows) { if (is_windows) {
if (windowsParsePath(cwd).kind == WindowsPath.Kind.Drive) { if (windowsParsePath(cwd).kind == WindowsPath.Kind.Drive) {
cwd[0] = asciiUpper(cwd[0]); cwd[0] = asciiUpper(cwd[0]);
@ -591,7 +594,7 @@ test "os.path.resolve" {
test "os.path.resolveWindows" { test "os.path.resolveWindows" {
if (is_windows) { if (is_windows) {
const cwd = try os.getCwd(debug.global_allocator); const cwd = try os.getCwdAlloc(debug.global_allocator);
const parsed_cwd = windowsParsePath(cwd); const parsed_cwd = windowsParsePath(cwd);
{ {
const result = testResolveWindows([][]const u8{ "/usr/local", "lib\\zig\\std\\array_list.zig" }); const result = testResolveWindows([][]const u8{ "/usr/local", "lib\\zig\\std\\array_list.zig" });
@ -1073,112 +1076,148 @@ fn testRelativeWindows(from: []const u8, to: []const u8, expected_output: []cons
assert(mem.eql(u8, result, expected_output)); assert(mem.eql(u8, result, expected_output));
} }
/// Return the canonicalized absolute pathname. pub const RealError = error{
/// Expands all symbolic links and resolves references to `.`, `..`, and FileNotFound,
/// extra `/` characters in ::pathname. AccessDenied,
/// Caller must deallocate result. NameTooLong,
pub fn real(allocator: *Allocator, pathname: []const u8) ![]u8 { NotSupported,
NotDir,
SymLinkLoop,
InputOutput,
FileTooBig,
IsDir,
ProcessFdQuotaExceeded,
SystemFdQuotaExceeded,
NoDevice,
SystemResources,
NoSpaceLeft,
FileSystem,
BadPathName,
/// On Windows, file paths must be valid Unicode.
InvalidUtf8,
/// TODO remove this possibility
PathAlreadyExists,
/// TODO remove this possibility
Unexpected,
};
/// Call from Windows-specific code if you already have a UTF-16LE encoded, null terminated string.
/// Otherwise use `real` or `realC`.
pub fn realW(out_buffer: *[os.MAX_PATH_BYTES]u8, pathname: [*]const u16) RealError![]u8 {
const h_file = windows.CreateFileW(
pathname,
windows.GENERIC_READ,
windows.FILE_SHARE_READ,
null,
windows.OPEN_EXISTING,
windows.FILE_ATTRIBUTE_NORMAL,
null,
);
if (h_file == windows.INVALID_HANDLE_VALUE) {
const err = windows.GetLastError();
switch (err) {
windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound,
windows.ERROR.ACCESS_DENIED => return error.AccessDenied,
windows.ERROR.FILENAME_EXCED_RANGE => return error.NameTooLong,
else => return os.unexpectedErrorWindows(err),
}
}
defer os.close(h_file);
var utf16le_buf: [windows_util.PATH_MAX_WIDE]u16 = undefined;
const casted_len = @intCast(windows.DWORD, utf16le_buf.len); // TODO shouldn't need this cast
const result = windows.GetFinalPathNameByHandleW(h_file, &utf16le_buf, casted_len, windows.VOLUME_NAME_DOS);
assert(result <= utf16le_buf.len);
if (result == 0) {
const err = windows.GetLastError();
switch (err) {
windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound,
windows.ERROR.PATH_NOT_FOUND => return error.FileNotFound,
windows.ERROR.NOT_ENOUGH_MEMORY => return error.SystemResources,
windows.ERROR.FILENAME_EXCED_RANGE => return error.NameTooLong,
windows.ERROR.INVALID_PARAMETER => unreachable,
else => return os.unexpectedErrorWindows(err),
}
}
const utf16le_slice = utf16le_buf[0..result];
// windows returns \\?\ prepended to the path
// we strip it because nobody wants \\?\ prepended to their path
const prefix = []u16{ '\\', '\\', '?', '\\' };
const start_index = if (mem.startsWith(u16, utf16le_slice, prefix)) prefix.len else 0;
// Trust that Windows gives us valid UTF-16LE.
const end_index = std.unicode.utf16leToUtf8(out_buffer, utf16le_slice[start_index..]) catch unreachable;
return out_buffer[0..end_index];
}
/// See `real`
/// Use this when you have a null terminated pointer path.
pub fn realC(out_buffer: *[os.MAX_PATH_BYTES]u8, pathname: [*]const u8) RealError![]u8 {
switch (builtin.os) { switch (builtin.os) {
Os.windows => { Os.windows => {
const pathname_buf = try allocator.alloc(u8, pathname.len + 1); const pathname_w = try windows_util.cStrToPrefixedFileW(pathname);
defer allocator.free(pathname_buf); return realW(out_buffer, pathname_w);
mem.copy(u8, pathname_buf, pathname);
pathname_buf[pathname.len] = 0;
const h_file = windows.CreateFileA(pathname_buf.ptr, windows.GENERIC_READ, windows.FILE_SHARE_READ, null, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL, null);
if (h_file == windows.INVALID_HANDLE_VALUE) {
const err = windows.GetLastError();
return switch (err) {
windows.ERROR.FILE_NOT_FOUND => error.FileNotFound,
windows.ERROR.ACCESS_DENIED => error.AccessDenied,
windows.ERROR.FILENAME_EXCED_RANGE => error.NameTooLong,
else => os.unexpectedErrorWindows(err),
};
}
defer os.close(h_file);
var buf = try allocator.alloc(u8, 256);
errdefer allocator.free(buf);
while (true) {
const buf_len = math.cast(windows.DWORD, buf.len) catch return error.NameTooLong;
const result = windows.GetFinalPathNameByHandleA(h_file, buf.ptr, buf_len, windows.VOLUME_NAME_DOS);
if (result == 0) {
const err = windows.GetLastError();
return switch (err) {
windows.ERROR.PATH_NOT_FOUND => error.FileNotFound,
windows.ERROR.NOT_ENOUGH_MEMORY => error.OutOfMemory,
windows.ERROR.INVALID_PARAMETER => unreachable,
else => os.unexpectedErrorWindows(err),
};
}
if (result > buf.len) {
buf = try allocator.realloc(u8, buf, result);
continue;
}
// windows returns \\?\ prepended to the path
// we strip it because nobody wants \\?\ prepended to their path
const final_len = x: {
if (result > 4 and mem.startsWith(u8, buf, "\\\\?\\")) {
var i: usize = 4;
while (i < result) : (i += 1) {
buf[i - 4] = buf[i];
}
break :x result - 4;
} else {
break :x result;
}
};
return allocator.shrink(u8, buf, final_len);
}
}, },
Os.macosx, Os.ios => { Os.macosx, Os.ios => {
// TODO instead of calling the libc function here, port the implementation // TODO instead of calling the libc function here, port the implementation to Zig
// to Zig, and then remove the NameTooLong error possibility. const err = posix.getErrno(posix.realpath(pathname, out_buffer));
const pathname_buf = try allocator.alloc(u8, pathname.len + 1); switch (err) {
defer allocator.free(pathname_buf); 0 => return mem.toSlice(u8, out_buffer),
posix.EINVAL => unreachable,
const result_buf = try allocator.alloc(u8, posix.PATH_MAX); posix.EBADF => unreachable,
errdefer allocator.free(result_buf); posix.EFAULT => unreachable,
posix.EACCES => return error.AccessDenied,
mem.copy(u8, pathname_buf, pathname); posix.ENOENT => return error.FileNotFound,
pathname_buf[pathname.len] = 0; posix.ENOTSUP => return error.NotSupported,
posix.ENOTDIR => return error.NotDir,
const err = posix.getErrno(posix.realpath(pathname_buf.ptr, result_buf.ptr)); posix.ENAMETOOLONG => return error.NameTooLong,
if (err > 0) { posix.ELOOP => return error.SymLinkLoop,
return switch (err) { posix.EIO => return error.InputOutput,
posix.EINVAL => unreachable, else => return os.unexpectedErrorPosix(err),
posix.EBADF => unreachable,
posix.EFAULT => unreachable,
posix.EACCES => error.AccessDenied,
posix.ENOENT => error.FileNotFound,
posix.ENOTSUP => error.NotSupported,
posix.ENOTDIR => error.NotDir,
posix.ENAMETOOLONG => error.NameTooLong,
posix.ELOOP => error.SymLinkLoop,
posix.EIO => error.InputOutput,
else => os.unexpectedErrorPosix(err),
};
} }
return allocator.shrink(u8, result_buf, cstr.len(result_buf.ptr));
}, },
Os.linux => { Os.linux => {
const fd = try os.posixOpen(allocator, pathname, posix.O_PATH | posix.O_NONBLOCK | posix.O_CLOEXEC, 0); const fd = try os.posixOpenC(pathname, posix.O_PATH | posix.O_NONBLOCK | posix.O_CLOEXEC, 0);
defer os.close(fd); defer os.close(fd);
var buf: ["/proc/self/fd/-2147483648".len]u8 = undefined; var buf: ["/proc/self/fd/-2147483648".len]u8 = undefined;
const proc_path = fmt.bufPrint(buf[0..], "/proc/self/fd/{}", fd) catch unreachable; const proc_path = fmt.bufPrint(buf[0..], "/proc/self/fd/{}\x00", fd) catch unreachable;
return os.readLink(allocator, proc_path); return os.readLinkC(out_buffer, proc_path.ptr);
}, },
else => @compileError("TODO implement os.path.real for " ++ @tagName(builtin.os)), else => @compileError("TODO implement os.path.real for " ++ @tagName(builtin.os)),
} }
} }
/// Return the canonicalized absolute pathname.
/// Expands all symbolic links and resolves references to `.`, `..`, and
/// extra `/` characters in ::pathname.
/// The return value is a slice of out_buffer, and not necessarily from the beginning.
pub fn real(out_buffer: *[os.MAX_PATH_BYTES]u8, pathname: []const u8) RealError![]u8 {
switch (builtin.os) {
Os.windows => {
const pathname_w = try windows_util.sliceToPrefixedFileW(pathname);
return realW(out_buffer, &pathname_w);
},
Os.macosx, Os.ios, Os.linux => {
const pathname_c = try os.toPosixPath(pathname);
return realC(out_buffer, &pathname_c);
},
else => @compileError("Unsupported OS"),
}
}
/// `real`, except caller must free the returned memory.
pub fn realAlloc(allocator: *Allocator, pathname: []const u8) ![]u8 {
var buf: [os.MAX_PATH_BYTES]u8 = undefined;
return mem.dupe(allocator, u8, try real(&buf, pathname));
}
test "os.path.real" { test "os.path.real" {
// at least call it so it gets compiled // at least call it so it gets compiled
_ = real(debug.global_allocator, "some_path"); var buf: [os.MAX_PATH_BYTES]u8 = undefined;
std.debug.assertError(real(&buf, "definitely_bogus_does_not_exist1234"), error.FileNotFound);
} }

View File

@ -10,27 +10,27 @@ const AtomicRmwOp = builtin.AtomicRmwOp;
const AtomicOrder = builtin.AtomicOrder; const AtomicOrder = builtin.AtomicOrder;
test "makePath, put some files in it, deleteTree" { test "makePath, put some files in it, deleteTree" {
try os.makePath(a, "os_test_tmp/b/c"); try os.makePath(a, "os_test_tmp" ++ os.path.sep_str ++ "b" ++ os.path.sep_str ++ "c");
try io.writeFile(a, "os_test_tmp/b/c/file.txt", "nonsense"); try io.writeFile("os_test_tmp" ++ os.path.sep_str ++ "b" ++ os.path.sep_str ++ "c" ++ os.path.sep_str ++ "file.txt", "nonsense");
try io.writeFile(a, "os_test_tmp/b/file2.txt", "blah"); try io.writeFile("os_test_tmp" ++ os.path.sep_str ++ "b" ++ os.path.sep_str ++ "file2.txt", "blah");
try os.deleteTree(a, "os_test_tmp"); try os.deleteTree(a, "os_test_tmp");
if (os.Dir.open(a, "os_test_tmp")) |dir| { if (os.Dir.open(a, "os_test_tmp")) |dir| {
@panic("expected error"); @panic("expected error");
} else |err| { } else |err| {
assert(err == error.PathNotFound); assert(err == error.FileNotFound);
} }
} }
test "access file" { test "access file" {
try os.makePath(a, "os_test_tmp"); try os.makePath(a, "os_test_tmp");
if (os.File.access(a, "os_test_tmp/file.txt")) |ok| { if (os.File.access("os_test_tmp" ++ os.path.sep_str ++ "file.txt")) |ok| {
@panic("expected error"); @panic("expected error");
} else |err| { } else |err| {
assert(err == error.NotFound); assert(err == error.FileNotFound);
} }
try io.writeFile(a, "os_test_tmp/file.txt", ""); try io.writeFile("os_test_tmp" ++ os.path.sep_str ++ "file.txt", "");
try os.File.access(a, "os_test_tmp/file.txt"); try os.File.access("os_test_tmp" ++ os.path.sep_str ++ "file.txt");
try os.deleteTree(a, "os_test_tmp"); try os.deleteTree(a, "os_test_tmp");
} }

View File

@ -1,14 +1,11 @@
use @import("index.zig"); use @import("index.zig");
pub extern "kernel32" stdcallcc fn CancelIoEx(hFile: HANDLE, lpOverlapped: LPOVERLAPPED) BOOL; pub extern "kernel32" stdcallcc fn CancelIoEx(hFile: HANDLE, lpOverlapped: LPOVERLAPPED) BOOL;
pub extern "kernel32" stdcallcc fn CloseHandle(hObject: HANDLE) BOOL; pub extern "kernel32" stdcallcc fn CloseHandle(hObject: HANDLE) BOOL;
pub extern "kernel32" stdcallcc fn CreateDirectoryA( pub extern "kernel32" stdcallcc fn CreateDirectoryA(lpPathName: [*]const u8, lpSecurityAttributes: ?*SECURITY_ATTRIBUTES) BOOL;
lpPathName: LPCSTR, pub extern "kernel32" stdcallcc fn CreateDirectoryW(lpPathName: [*]const u16, lpSecurityAttributes: ?*SECURITY_ATTRIBUTES) BOOL;
lpSecurityAttributes: ?*SECURITY_ATTRIBUTES,
) BOOL;
pub extern "kernel32" stdcallcc fn CreateFileA( pub extern "kernel32" stdcallcc fn CreateFileA(
lpFileName: [*]const u8, // TODO null terminated pointer type lpFileName: [*]const u8, // TODO null terminated pointer type
@ -60,7 +57,8 @@ pub extern "kernel32" stdcallcc fn CreateIoCompletionPort(FileHandle: HANDLE, Ex
pub extern "kernel32" stdcallcc fn CreateThread(lpThreadAttributes: ?LPSECURITY_ATTRIBUTES, dwStackSize: SIZE_T, lpStartAddress: LPTHREAD_START_ROUTINE, lpParameter: ?LPVOID, dwCreationFlags: DWORD, lpThreadId: ?LPDWORD) ?HANDLE; pub extern "kernel32" stdcallcc fn CreateThread(lpThreadAttributes: ?LPSECURITY_ATTRIBUTES, dwStackSize: SIZE_T, lpStartAddress: LPTHREAD_START_ROUTINE, lpParameter: ?LPVOID, dwCreationFlags: DWORD, lpThreadId: ?LPDWORD) ?HANDLE;
pub extern "kernel32" stdcallcc fn DeleteFileA(lpFileName: LPCSTR) BOOL; pub extern "kernel32" stdcallcc fn DeleteFileA(lpFileName: [*]const u8) BOOL;
pub extern "kernel32" stdcallcc fn DeleteFileW(lpFileName: [*]const u16) BOOL;
pub extern "kernel32" stdcallcc fn ExitProcess(exit_code: UINT) noreturn; pub extern "kernel32" stdcallcc fn ExitProcess(exit_code: UINT) noreturn;
@ -74,7 +72,8 @@ pub extern "kernel32" stdcallcc fn GetCommandLineA() LPSTR;
pub extern "kernel32" stdcallcc fn GetConsoleMode(in_hConsoleHandle: HANDLE, out_lpMode: *DWORD) BOOL; pub extern "kernel32" stdcallcc fn GetConsoleMode(in_hConsoleHandle: HANDLE, out_lpMode: *DWORD) BOOL;
pub extern "kernel32" stdcallcc fn GetCurrentDirectoryA(nBufferLength: WORD, lpBuffer: ?LPSTR) DWORD; pub extern "kernel32" stdcallcc fn GetCurrentDirectoryA(nBufferLength: DWORD, lpBuffer: ?[*]CHAR) DWORD;
pub extern "kernel32" stdcallcc fn GetCurrentDirectoryW(nBufferLength: DWORD, lpBuffer: ?[*]WCHAR) DWORD;
pub extern "kernel32" stdcallcc fn GetCurrentThread() HANDLE; pub extern "kernel32" stdcallcc fn GetCurrentThread() HANDLE;
pub extern "kernel32" stdcallcc fn GetCurrentThreadId() DWORD; pub extern "kernel32" stdcallcc fn GetCurrentThreadId() DWORD;
@ -87,9 +86,11 @@ pub extern "kernel32" stdcallcc fn GetExitCodeProcess(hProcess: HANDLE, lpExitCo
pub extern "kernel32" stdcallcc fn GetFileSizeEx(hFile: HANDLE, lpFileSize: *LARGE_INTEGER) BOOL; pub extern "kernel32" stdcallcc fn GetFileSizeEx(hFile: HANDLE, lpFileSize: *LARGE_INTEGER) BOOL;
pub extern "kernel32" stdcallcc fn GetFileAttributesA(lpFileName: LPCSTR) DWORD; pub extern "kernel32" stdcallcc fn GetFileAttributesA(lpFileName: [*]const CHAR) DWORD;
pub extern "kernel32" stdcallcc fn GetFileAttributesW(lpFileName: [*]const WCHAR) DWORD;
pub extern "kernel32" stdcallcc fn GetModuleFileNameA(hModule: ?HMODULE, lpFilename: LPSTR, nSize: DWORD) DWORD; pub extern "kernel32" stdcallcc fn GetModuleFileNameA(hModule: ?HMODULE, lpFilename: [*]u8, nSize: DWORD) DWORD;
pub extern "kernel32" stdcallcc fn GetModuleFileNameW(hModule: ?HMODULE, lpFilename: [*]u16, nSize: DWORD) DWORD;
pub extern "kernel32" stdcallcc fn GetLastError() DWORD; pub extern "kernel32" stdcallcc fn GetLastError() DWORD;
@ -107,6 +108,12 @@ pub extern "kernel32" stdcallcc fn GetFinalPathNameByHandleA(
dwFlags: DWORD, dwFlags: DWORD,
) DWORD; ) DWORD;
pub extern "kernel32" stdcallcc fn GetFinalPathNameByHandleW(
hFile: HANDLE,
lpszFilePath: [*]u16,
cchFilePath: DWORD,
dwFlags: DWORD,
) DWORD;
pub extern "kernel32" stdcallcc fn GetOverlappedResult(hFile: HANDLE, lpOverlapped: *OVERLAPPED, lpNumberOfBytesTransferred: *DWORD, bWait: BOOL) BOOL; pub extern "kernel32" stdcallcc fn GetOverlappedResult(hFile: HANDLE, lpOverlapped: *OVERLAPPED, lpNumberOfBytesTransferred: *DWORD, bWait: BOOL) BOOL;
@ -132,8 +139,14 @@ pub extern "kernel32" stdcallcc fn HeapFree(hHeap: HANDLE, dwFlags: DWORD, lpMem
pub extern "kernel32" stdcallcc fn HeapValidate(hHeap: HANDLE, dwFlags: DWORD, lpMem: ?*const c_void) BOOL; pub extern "kernel32" stdcallcc fn HeapValidate(hHeap: HANDLE, dwFlags: DWORD, lpMem: ?*const c_void) BOOL;
pub extern "kernel32" stdcallcc fn MoveFileExA( pub extern "kernel32" stdcallcc fn MoveFileExA(
lpExistingFileName: LPCSTR, lpExistingFileName: [*]const u8,
lpNewFileName: LPCSTR, lpNewFileName: [*]const u8,
dwFlags: DWORD,
) BOOL;
pub extern "kernel32" stdcallcc fn MoveFileExW(
lpExistingFileName: [*]const u16,
lpNewFileName: [*]const u16,
dwFlags: DWORD, dwFlags: DWORD,
) BOOL; ) BOOL;
@ -194,7 +207,6 @@ pub extern "kernel32" stdcallcc fn LoadLibraryA(lpLibFileName: LPCSTR) ?HMODULE;
pub extern "kernel32" stdcallcc fn FreeLibrary(hModule: HMODULE) BOOL; pub extern "kernel32" stdcallcc fn FreeLibrary(hModule: HMODULE) BOOL;
pub const FILE_NOTIFY_INFORMATION = extern struct { pub const FILE_NOTIFY_INFORMATION = extern struct {
NextEntryOffset: DWORD, NextEntryOffset: DWORD,
Action: DWORD, Action: DWORD,
@ -208,7 +220,7 @@ pub const FILE_ACTION_MODIFIED = 0x00000003;
pub const FILE_ACTION_RENAMED_OLD_NAME = 0x00000004; pub const FILE_ACTION_RENAMED_OLD_NAME = 0x00000004;
pub const FILE_ACTION_RENAMED_NEW_NAME = 0x00000005; pub const FILE_ACTION_RENAMED_NEW_NAME = 0x00000005;
pub const LPOVERLAPPED_COMPLETION_ROUTINE = ?extern fn(DWORD, DWORD, *OVERLAPPED) void; pub const LPOVERLAPPED_COMPLETION_ROUTINE = ?extern fn (DWORD, DWORD, *OVERLAPPED) void;
pub const FILE_LIST_DIRECTORY = 1; pub const FILE_LIST_DIRECTORY = 1;

View File

@ -7,9 +7,17 @@ const mem = std.mem;
const BufMap = std.BufMap; const BufMap = std.BufMap;
const cstr = std.cstr; const cstr = std.cstr;
// > The maximum path of 32,767 characters is approximate, because the "\\?\"
// > prefix may be expanded to a longer string by the system at run time, and
// > this expansion applies to the total length.
// from https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file#maximum-path-length-limitation
pub const PATH_MAX_WIDE = 32767;
pub const WaitError = error{ pub const WaitError = error{
WaitAbandoned, WaitAbandoned,
WaitTimeOut, WaitTimeOut,
/// See https://github.com/ziglang/zig/issues/1396
Unexpected, Unexpected,
}; };
@ -37,6 +45,8 @@ pub const WriteError = error{
SystemResources, SystemResources,
OperationAborted, OperationAborted,
BrokenPipe, BrokenPipe,
/// See https://github.com/ziglang/zig/issues/1396
Unexpected, Unexpected,
}; };
@ -86,37 +96,51 @@ pub fn windowsIsCygwinPty(handle: windows.HANDLE) bool {
pub const OpenError = error{ pub const OpenError = error{
SharingViolation, SharingViolation,
PathAlreadyExists, PathAlreadyExists,
/// When any of the path components can not be found or the file component can not
/// be found. Some operating systems distinguish between path components not found and
/// file components not found, but they are collapsed into FileNotFound to gain
/// consistency across operating systems.
FileNotFound, FileNotFound,
AccessDenied, AccessDenied,
PipeBusy, PipeBusy,
NameTooLong,
/// On Windows, file paths must be valid Unicode.
InvalidUtf8,
/// On Windows, file paths cannot contain these characters:
/// '/', '*', '?', '"', '<', '>', '|'
BadPathName,
/// See https://github.com/ziglang/zig/issues/1396
Unexpected, Unexpected,
OutOfMemory,
}; };
/// `file_path` needs to be copied in memory to add a null terminating byte, hence the allocator.
pub fn windowsOpen( pub fn windowsOpen(
allocator: *mem.Allocator,
file_path: []const u8, file_path: []const u8,
desired_access: windows.DWORD, desired_access: windows.DWORD,
share_mode: windows.DWORD, share_mode: windows.DWORD,
creation_disposition: windows.DWORD, creation_disposition: windows.DWORD,
flags_and_attrs: windows.DWORD, flags_and_attrs: windows.DWORD,
) OpenError!windows.HANDLE { ) OpenError!windows.HANDLE {
const path_with_null = try cstr.addNullByte(allocator, file_path); const file_path_w = try sliceToPrefixedFileW(file_path);
defer allocator.free(path_with_null);
const result = windows.CreateFileA(path_with_null.ptr, desired_access, share_mode, null, creation_disposition, flags_and_attrs, null); const result = windows.CreateFileW(&file_path_w, desired_access, share_mode, null, creation_disposition, flags_and_attrs, null);
if (result == windows.INVALID_HANDLE_VALUE) { if (result == windows.INVALID_HANDLE_VALUE) {
const err = windows.GetLastError(); const err = windows.GetLastError();
return switch (err) { switch (err) {
windows.ERROR.SHARING_VIOLATION => OpenError.SharingViolation, windows.ERROR.SHARING_VIOLATION => return OpenError.SharingViolation,
windows.ERROR.ALREADY_EXISTS, windows.ERROR.FILE_EXISTS => OpenError.PathAlreadyExists, windows.ERROR.ALREADY_EXISTS => return OpenError.PathAlreadyExists,
windows.ERROR.FILE_NOT_FOUND => OpenError.FileNotFound, windows.ERROR.FILE_EXISTS => return OpenError.PathAlreadyExists,
windows.ERROR.ACCESS_DENIED => OpenError.AccessDenied, windows.ERROR.FILE_NOT_FOUND => return OpenError.FileNotFound,
windows.ERROR.PIPE_BUSY => OpenError.PipeBusy, windows.ERROR.PATH_NOT_FOUND => return OpenError.FileNotFound,
else => os.unexpectedErrorWindows(err), windows.ERROR.ACCESS_DENIED => return OpenError.AccessDenied,
}; windows.ERROR.PIPE_BUSY => return OpenError.PipeBusy,
else => return os.unexpectedErrorWindows(err),
}
} }
return result; return result;
@ -192,9 +216,8 @@ pub fn windowsFindFirstFile(
if (handle == windows.INVALID_HANDLE_VALUE) { if (handle == windows.INVALID_HANDLE_VALUE) {
const err = windows.GetLastError(); const err = windows.GetLastError();
switch (err) { switch (err) {
windows.ERROR.FILE_NOT_FOUND, windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound,
windows.ERROR.PATH_NOT_FOUND, windows.ERROR.PATH_NOT_FOUND => return error.FileNotFound,
=> return error.PathNotFound,
else => return os.unexpectedErrorWindows(err), else => return os.unexpectedErrorWindows(err),
} }
} }
@ -238,7 +261,7 @@ pub fn windowsPostQueuedCompletionStatus(completion_port: windows.HANDLE, bytes_
} }
} }
pub const WindowsWaitResult = enum{ pub const WindowsWaitResult = enum {
Normal, Normal,
Aborted, Aborted,
Cancelled, Cancelled,
@ -254,8 +277,39 @@ pub fn windowsGetQueuedCompletionStatus(completion_port: windows.HANDLE, bytes_t
if (std.debug.runtime_safety) { if (std.debug.runtime_safety) {
std.debug.panic("unexpected error: {}\n", err); std.debug.panic("unexpected error: {}\n", err);
} }
} },
} }
} }
return WindowsWaitResult.Normal; return WindowsWaitResult.Normal;
} }
pub fn cStrToPrefixedFileW(s: [*]const u8) ![PATH_MAX_WIDE + 1]u16 {
return sliceToPrefixedFileW(mem.toSliceConst(u8, s));
}
pub fn sliceToPrefixedFileW(s: []const u8) ![PATH_MAX_WIDE + 1]u16 {
// TODO well defined copy elision
var result: [PATH_MAX_WIDE + 1]u16 = undefined;
// > File I/O functions in the Windows API convert "/" to "\" as part of
// > converting the name to an NT-style name, except when using the "\\?\"
// > prefix as detailed in the following sections.
// from https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file#maximum-path-length-limitation
// Because we want the larger maximum path length for absolute paths, we
// disallow forward slashes in zig std lib file functions on Windows.
for (s) |byte|
switch (byte) {
'/', '*', '?', '"', '<', '>', '|' => return error.BadPathName,
else => {},
};
const start_index = if (mem.startsWith(u8, s, "\\\\") or !os.path.isAbsolute(s)) 0 else blk: {
const prefix = []u16{ '\\', '\\', '?', '\\' };
mem.copy(u16, result[0..], prefix);
break :blk prefix.len;
};
const end_index = start_index + try std.unicode.utf8ToUtf16Le(result[start_index..], s);
assert(end_index <= result.len);
if (end_index == result.len) return error.NameTooLong;
result[end_index] = 0;
return result;
}

View File

@ -218,7 +218,6 @@ const Utf8Iterator = struct {
} }
const cp_len = utf8ByteSequenceLength(it.bytes[it.i]) catch unreachable; const cp_len = utf8ByteSequenceLength(it.bytes[it.i]) catch unreachable;
it.i += cp_len; it.i += cp_len;
return it.bytes[it.i - cp_len .. it.i]; return it.bytes[it.i - cp_len .. it.i];
} }
@ -236,6 +235,38 @@ const Utf8Iterator = struct {
} }
}; };
pub const Utf16LeIterator = struct {
bytes: []const u8,
i: usize,
pub fn init(s: []const u16) Utf16LeIterator {
return Utf16LeIterator{
.bytes = @sliceToBytes(s),
.i = 0,
};
}
pub fn nextCodepoint(it: *Utf16LeIterator) !?u32 {
assert(it.i <= it.bytes.len);
if (it.i == it.bytes.len) return null;
const c0: u32 = mem.readIntLE(u16, it.bytes[it.i .. it.i + 2]);
if (c0 & ~u32(0x03ff) == 0xd800) {
// surrogate pair
it.i += 2;
if (it.i >= it.bytes.len) return error.DanglingSurrogateHalf;
const c1: u32 = mem.readIntLE(u16, it.bytes[it.i .. it.i + 2]);
if (c1 & ~u32(0x03ff) != 0xdc00) return error.ExpectedSecondSurrogateHalf;
it.i += 2;
return 0x10000 + (((c0 & 0x03ff) << 10) | (c1 & 0x03ff));
} else if (c0 & ~u32(0x03ff) == 0xdc00) {
return error.UnexpectedSecondSurrogateHalf;
} else {
it.i += 2;
return c0;
}
}
};
test "utf8 encode" { test "utf8 encode" {
comptime testUtf8Encode() catch unreachable; comptime testUtf8Encode() catch unreachable;
try testUtf8Encode(); try testUtf8Encode();
@ -446,42 +477,34 @@ fn testDecode(bytes: []const u8) !u32 {
return utf8Decode(bytes); return utf8Decode(bytes);
} }
// TODO: make this API on top of a non-allocating Utf16LeView /// Caller must free returned memory.
pub fn utf16leToUtf8(allocator: *mem.Allocator, utf16le: []const u16) ![]u8 { pub fn utf16leToUtf8Alloc(allocator: *mem.Allocator, utf16le: []const u16) ![]u8 {
var result = std.ArrayList(u8).init(allocator); var result = std.ArrayList(u8).init(allocator);
// optimistically guess that it will all be ascii. // optimistically guess that it will all be ascii.
try result.ensureCapacity(utf16le.len); try result.ensureCapacity(utf16le.len);
const utf16le_as_bytes = @sliceToBytes(utf16le);
var i: usize = 0;
var out_index: usize = 0; var out_index: usize = 0;
while (i < utf16le_as_bytes.len) : (i += 2) { var it = Utf16LeIterator.init(utf16le);
// decode while (try it.nextCodepoint()) |codepoint| {
const c0: u32 = mem.readIntLE(u16, utf16le_as_bytes[i..i + 2]);
var codepoint: u32 = undefined;
if (c0 & ~u32(0x03ff) == 0xd800) {
// surrogate pair
i += 2;
if (i >= utf16le_as_bytes.len) return error.DanglingSurrogateHalf;
const c1: u32 = mem.readIntLE(u16, utf16le_as_bytes[i..i + 2]);
if (c1 & ~u32(0x03ff) != 0xdc00) return error.ExpectedSecondSurrogateHalf;
codepoint = 0x10000 + (((c0 & 0x03ff) << 10) | (c1 & 0x03ff));
} else if (c0 & ~u32(0x03ff) == 0xdc00) {
return error.UnexpectedSecondSurrogateHalf;
} else {
codepoint = c0;
}
// encode
const utf8_len = utf8CodepointSequenceLength(codepoint) catch unreachable; const utf8_len = utf8CodepointSequenceLength(codepoint) catch unreachable;
try result.resize(result.len + utf8_len); try result.resize(result.len + utf8_len);
_ = utf8Encode(codepoint, result.items[out_index..]) catch unreachable; assert((utf8Encode(codepoint, result.items[out_index..]) catch unreachable) == utf8_len);
out_index += utf8_len; out_index += utf8_len;
} }
return result.toOwnedSlice(); return result.toOwnedSlice();
} }
/// Asserts that the output buffer is big enough.
/// Returns end byte index into utf8.
pub fn utf16leToUtf8(utf8: []u8, utf16le: []const u16) !usize {
var end_index: usize = 0;
var it = Utf16LeIterator.init(utf16le);
while (try it.nextCodepoint()) |codepoint| {
end_index += try utf8Encode(codepoint, utf8[end_index..]);
}
return end_index;
}
test "utf16leToUtf8" { test "utf16leToUtf8" {
var utf16le: [2]u16 = undefined; var utf16le: [2]u16 = undefined;
const utf16le_as_bytes = @sliceToBytes(utf16le[0..]); const utf16le_as_bytes = @sliceToBytes(utf16le[0..]);
@ -489,14 +512,14 @@ test "utf16leToUtf8" {
{ {
mem.writeInt(utf16le_as_bytes[0..], u16('A'), builtin.Endian.Little); mem.writeInt(utf16le_as_bytes[0..], u16('A'), builtin.Endian.Little);
mem.writeInt(utf16le_as_bytes[2..], u16('a'), builtin.Endian.Little); mem.writeInt(utf16le_as_bytes[2..], u16('a'), builtin.Endian.Little);
const utf8 = try utf16leToUtf8(std.debug.global_allocator, utf16le); const utf8 = try utf16leToUtf8Alloc(std.debug.global_allocator, utf16le);
assert(mem.eql(u8, utf8, "Aa")); assert(mem.eql(u8, utf8, "Aa"));
} }
{ {
mem.writeInt(utf16le_as_bytes[0..], u16(0x80), builtin.Endian.Little); mem.writeInt(utf16le_as_bytes[0..], u16(0x80), builtin.Endian.Little);
mem.writeInt(utf16le_as_bytes[2..], u16(0xffff), builtin.Endian.Little); mem.writeInt(utf16le_as_bytes[2..], u16(0xffff), builtin.Endian.Little);
const utf8 = try utf16leToUtf8(std.debug.global_allocator, utf16le); const utf8 = try utf16leToUtf8Alloc(std.debug.global_allocator, utf16le);
assert(mem.eql(u8, utf8, "\xc2\x80" ++ "\xef\xbf\xbf")); assert(mem.eql(u8, utf8, "\xc2\x80" ++ "\xef\xbf\xbf"));
} }
@ -504,7 +527,7 @@ test "utf16leToUtf8" {
// the values just outside the surrogate half range // the values just outside the surrogate half range
mem.writeInt(utf16le_as_bytes[0..], u16(0xd7ff), builtin.Endian.Little); mem.writeInt(utf16le_as_bytes[0..], u16(0xd7ff), builtin.Endian.Little);
mem.writeInt(utf16le_as_bytes[2..], u16(0xe000), builtin.Endian.Little); mem.writeInt(utf16le_as_bytes[2..], u16(0xe000), builtin.Endian.Little);
const utf8 = try utf16leToUtf8(std.debug.global_allocator, utf16le); const utf8 = try utf16leToUtf8Alloc(std.debug.global_allocator, utf16le);
assert(mem.eql(u8, utf8, "\xed\x9f\xbf" ++ "\xee\x80\x80")); assert(mem.eql(u8, utf8, "\xed\x9f\xbf" ++ "\xee\x80\x80"));
} }
@ -512,7 +535,7 @@ test "utf16leToUtf8" {
// smallest surrogate pair // smallest surrogate pair
mem.writeInt(utf16le_as_bytes[0..], u16(0xd800), builtin.Endian.Little); mem.writeInt(utf16le_as_bytes[0..], u16(0xd800), builtin.Endian.Little);
mem.writeInt(utf16le_as_bytes[2..], u16(0xdc00), builtin.Endian.Little); mem.writeInt(utf16le_as_bytes[2..], u16(0xdc00), builtin.Endian.Little);
const utf8 = try utf16leToUtf8(std.debug.global_allocator, utf16le); const utf8 = try utf16leToUtf8Alloc(std.debug.global_allocator, utf16le);
assert(mem.eql(u8, utf8, "\xf0\x90\x80\x80")); assert(mem.eql(u8, utf8, "\xf0\x90\x80\x80"));
} }
@ -520,14 +543,14 @@ test "utf16leToUtf8" {
// largest surrogate pair // largest surrogate pair
mem.writeInt(utf16le_as_bytes[0..], u16(0xdbff), builtin.Endian.Little); mem.writeInt(utf16le_as_bytes[0..], u16(0xdbff), builtin.Endian.Little);
mem.writeInt(utf16le_as_bytes[2..], u16(0xdfff), builtin.Endian.Little); mem.writeInt(utf16le_as_bytes[2..], u16(0xdfff), builtin.Endian.Little);
const utf8 = try utf16leToUtf8(std.debug.global_allocator, utf16le); const utf8 = try utf16leToUtf8Alloc(std.debug.global_allocator, utf16le);
assert(mem.eql(u8, utf8, "\xf4\x8f\xbf\xbf")); assert(mem.eql(u8, utf8, "\xf4\x8f\xbf\xbf"));
} }
{ {
mem.writeInt(utf16le_as_bytes[0..], u16(0xdbff), builtin.Endian.Little); mem.writeInt(utf16le_as_bytes[0..], u16(0xdbff), builtin.Endian.Little);
mem.writeInt(utf16le_as_bytes[2..], u16(0xdc00), builtin.Endian.Little); mem.writeInt(utf16le_as_bytes[2..], u16(0xdc00), builtin.Endian.Little);
const utf8 = try utf16leToUtf8(std.debug.global_allocator, utf16le); const utf8 = try utf16leToUtf8Alloc(std.debug.global_allocator, utf16le);
assert(mem.eql(u8, utf8, "\xf4\x8f\xb0\x80")); assert(mem.eql(u8, utf8, "\xf4\x8f\xb0\x80"));
} }
} }
@ -548,3 +571,20 @@ pub fn utf8ToUtf16LeWithNull(allocator: *mem.Allocator, utf8: []const u8) ![]u16
try result.append(0); try result.append(0);
return result.toOwnedSlice(); return result.toOwnedSlice();
} }
/// Returns index of next character. If exact fit, returned index equals output slice length.
/// If ran out of room, returned index equals output slice length + 1.
/// TODO support codepoints bigger than 16 bits
pub fn utf8ToUtf16Le(utf16le: []u16, utf8: []const u8) !usize {
const utf16le_as_bytes = @sliceToBytes(utf16le[0..]);
var end_index: usize = 0;
var it = (try Utf8View.init(utf8)).iterator();
while (it.nextCodepoint()) |codepoint| {
if (end_index == utf16le_as_bytes.len) return (end_index / 2) + 1;
// TODO surrogate pairs
mem.writeInt(utf16le_as_bytes[end_index..], @intCast(u16, codepoint), builtin.Endian.Little);
end_index += 2;
}
return end_index / 2;
}

View File

@ -1,5 +1,5 @@
const A = error{ const A = error{
PathNotFound, FileNotFound,
NotDir, NotDir,
}; };
const B = error{OutOfMemory}; const B = error{OutOfMemory};
@ -15,7 +15,7 @@ test "merge error sets" {
@panic("unexpected"); @panic("unexpected");
} else |err| switch (err) { } else |err| switch (err) {
error.OutOfMemory => @panic("unexpected"), error.OutOfMemory => @panic("unexpected"),
error.PathNotFound => @panic("unexpected"), error.FileNotFound => @panic("unexpected"),
error.NotDir => {}, error.NotDir => {},
} }
} }