aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorAndrew Kelley <andrew@ziglang.org>2019-10-21 23:54:29 -0400
committerAndrew Kelley <andrew@ziglang.org>2019-10-21 23:54:29 -0400
commite839250c5156d438f76e7b08e7053e9087fae77c (patch)
tree53550b0fc05c78c8594ed4c11c0f606cfc9b2437 /lib
parenta5cc758036720babeb13ec8cdec68b720c6af1eb (diff)
parent064377be9aaf409bf483f512354ef22f656bf213 (diff)
downloadzig-e839250c5156d438f76e7b08e7053e9087fae77c.tar.gz
zig-e839250c5156d438f76e7b08e7053e9087fae77c.zip
Merge branch 'stratact-no-dir-allocators'
closes #2885 closes #2886 closes #2888 closes #3249
Diffstat (limited to 'lib')
-rw-r--r--lib/std/build.zig4
-rw-r--r--lib/std/c.zig1
-rw-r--r--lib/std/event/fs.zig2
-rw-r--r--lib/std/fs.zig1036
-rw-r--r--lib/std/fs/file.zig1
-rw-r--r--lib/std/fs/path.zig19
-rw-r--r--lib/std/io.zig3
-rw-r--r--lib/std/os.zig149
-rw-r--r--lib/std/os/bits/darwin.zig14
-rw-r--r--lib/std/os/bits/freebsd.zig20
-rw-r--r--lib/std/os/bits/windows.zig3
-rw-r--r--lib/std/os/test.zig6
-rw-r--r--lib/std/os/windows.zig27
-rw-r--r--lib/std/os/windows/bits.zig148
-rw-r--r--lib/std/os/windows/kernel32.zig2
-rw-r--r--lib/std/os/windows/ntdll.zig25
-rw-r--r--lib/std/os/windows/status.zig8
-rw-r--r--lib/std/special/test_runner.zig7
18 files changed, 1074 insertions, 401 deletions
diff --git a/lib/std/build.zig b/lib/std/build.zig
index cb02c8f131..dd602ba922 100644
--- a/lib/std/build.zig
+++ b/lib/std/build.zig
@@ -331,7 +331,7 @@ pub const Builder = struct {
if (self.verbose) {
warn("rm {}\n", full_path);
}
- fs.deleteTree(self.allocator, full_path) catch {};
+ fs.deleteTree(full_path) catch {};
}
// TODO remove empty directories
@@ -2687,7 +2687,7 @@ pub const RemoveDirStep = struct {
const self = @fieldParentPtr(RemoveDirStep, "step", step);
const full_path = self.builder.pathFromRoot(self.dir_path);
- fs.deleteTree(self.builder.allocator, full_path) catch |err| {
+ fs.deleteTree(full_path) catch |err| {
warn("Unable to remove {}: {}\n", full_path, @errorName(err));
return err;
};
diff --git a/lib/std/c.zig b/lib/std/c.zig
index 9c7ec2f0e5..f591481d04 100644
--- a/lib/std/c.zig
+++ b/lib/std/c.zig
@@ -80,6 +80,7 @@ pub extern "c" fn mmap(addr: ?*align(page_size) c_void, len: usize, prot: c_uint
pub extern "c" fn munmap(addr: *align(page_size) c_void, len: usize) c_int;
pub extern "c" fn mprotect(addr: *align(page_size) c_void, len: usize, prot: c_uint) c_int;
pub extern "c" fn unlink(path: [*]const u8) c_int;
+pub extern "c" fn unlinkat(dirfd: fd_t, path: [*]const u8, flags: c_uint) c_int;
pub extern "c" fn getcwd(buf: [*]u8, size: usize) ?[*]u8;
pub extern "c" fn waitpid(pid: c_int, stat_loc: *c_uint, options: c_uint) c_int;
pub extern "c" fn fork() c_int;
diff --git a/lib/std/event/fs.zig b/lib/std/event/fs.zig
index 4490e1deae..81fd950e44 100644
--- a/lib/std/event/fs.zig
+++ b/lib/std/event/fs.zig
@@ -1312,7 +1312,7 @@ const test_tmp_dir = "std_event_fs_test";
//
// // TODO move this into event loop too
// try os.makePath(allocator, test_tmp_dir);
-// defer os.deleteTree(allocator, test_tmp_dir) catch {};
+// defer os.deleteTree(test_tmp_dir) catch {};
//
// var loop: Loop = undefined;
// try loop.initMultiThreaded(allocator);
diff --git a/lib/std/fs.zig b/lib/std/fs.zig
index 287c0316b7..3fdd175e77 100644
--- a/lib/std/fs.zig
+++ b/lib/std/fs.zig
@@ -335,444 +335,708 @@ pub fn deleteDirW(dir_path: [*]const u16) !void {
return os.rmdirW(dir_path);
}
-const DeleteTreeError = error{
- OutOfMemory,
- AccessDenied,
- FileTooBig,
- IsDir,
- SymLinkLoop,
- ProcessFdQuotaExceeded,
- NameTooLong,
- SystemFdQuotaExceeded,
- NoDevice,
- SystemResources,
- NoSpaceLeft,
- PathAlreadyExists,
- ReadOnlyFileSystem,
- NotDir,
- FileNotFound,
- FileSystem,
- FileBusy,
- DirNotEmpty,
- DeviceBusy,
-
- /// On Windows, file paths must be valid Unicode.
- InvalidUtf8,
-
- /// On Windows, file paths cannot contain these characters:
- /// '/', '*', '?', '"', '<', '>', '|'
- BadPathName,
-
- Unexpected,
-};
-
-/// Whether `full_path` describes a symlink, file, or directory, this function
-/// removes it. If it cannot be removed because it is a non-empty directory,
-/// this function recursively removes its entries and then tries again.
-/// TODO determine if we can remove the allocator requirement
-/// https://github.com/ziglang/zig/issues/2886
-pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError!void {
- start_over: while (true) {
- var got_access_denied = false;
- // First, try deleting the item as a file. This way we don't follow sym links.
- if (deleteFile(full_path)) {
- return;
- } else |err| switch (err) {
- error.FileNotFound => return,
- error.IsDir => {},
- error.AccessDenied => got_access_denied = true,
-
- error.InvalidUtf8,
- error.SymLinkLoop,
- error.NameTooLong,
- error.SystemResources,
- error.ReadOnlyFileSystem,
- error.NotDir,
- error.FileSystem,
- error.FileBusy,
- error.BadPathName,
- error.Unexpected,
- => return err,
- }
- {
- var dir = Dir.open(allocator, full_path) catch |err| switch (err) {
- error.NotDir => {
- if (got_access_denied) {
- return error.AccessDenied;
- }
- continue :start_over;
- },
-
- error.OutOfMemory,
- error.AccessDenied,
- error.FileTooBig,
- error.IsDir,
- error.SymLinkLoop,
- error.ProcessFdQuotaExceeded,
- error.NameTooLong,
- error.SystemFdQuotaExceeded,
- error.NoDevice,
- error.FileNotFound,
- error.SystemResources,
- error.NoSpaceLeft,
- error.PathAlreadyExists,
- error.Unexpected,
- error.InvalidUtf8,
- error.BadPathName,
- error.DeviceBusy,
- => return err,
- };
- defer dir.close();
-
- var full_entry_buf = std.ArrayList(u8).init(allocator);
- defer full_entry_buf.deinit();
-
- while (try dir.next()) |entry| {
- try full_entry_buf.resize(full_path.len + entry.name.len + 1);
- const full_entry_path = full_entry_buf.toSlice();
- mem.copy(u8, full_entry_path, full_path);
- full_entry_path[full_path.len] = path.sep;
- mem.copy(u8, full_entry_path[full_path.len + 1 ..], entry.name);
-
- try deleteTree(allocator, full_entry_path);
- }
- }
- return deleteDir(full_path);
+/// Removes a symlink, file, or directory.
+/// If `full_path` is relative, this is equivalent to `Dir.deleteTree` with the
+/// current working directory as the open directory handle.
+/// If `full_path` is absolute, this is equivalent to `Dir.deleteTree` with the
+/// base directory.
+pub fn deleteTree(full_path: []const u8) !void {
+ if (path.isAbsolute(full_path)) {
+ const dirname = path.dirname(full_path) orelse return error{
+ /// Attempt to remove the root file system path.
+ /// This error is unreachable if `full_path` is relative.
+ CannotDeleteRootDirectory,
+ }.CannotDeleteRootDirectory;
+
+ var dir = try Dir.open(dirname);
+ defer dir.close();
+
+ return dir.deleteTree(path.basename(full_path));
+ } else {
+ return Dir.cwd().deleteTree(full_path);
}
}
-/// TODO: separate this API into the one that opens directory handles to then subsequently open
-/// files, and into the one that reads files from an open directory handle.
pub const Dir = struct {
- handle: Handle,
- allocator: *Allocator,
+ fd: os.fd_t,
+
+ pub const Entry = struct {
+ name: []const u8,
+ kind: Kind,
- pub const Handle = switch (builtin.os) {
+ pub const Kind = enum {
+ BlockDevice,
+ CharacterDevice,
+ Directory,
+ NamedPipe,
+ SymLink,
+ File,
+ UnixDomainSocket,
+ Whiteout,
+ Unknown,
+ };
+ };
+
+ const IteratorError = error{AccessDenied} || os.UnexpectedError;
+
+ pub const Iterator = switch (builtin.os) {
.macosx, .ios, .freebsd, .netbsd => struct {
- fd: i32,
+ dir: Dir,
seek: i64,
- buf: []u8,
+ buf: [8192]u8, // TODO align(@alignOf(os.dirent)),
index: usize,
end_index: usize,
+
+ const Self = @This();
+
+ pub const Error = IteratorError;
+
+ /// Memory such as file names referenced in this returned entry becomes invalid
+ /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
+ pub fn next(self: *Self) Error!?Entry {
+ switch (builtin.os) {
+ .macosx, .ios => return self.nextDarwin(),
+ .freebsd, .netbsd => return self.nextBsd(),
+ else => @compileError("unimplemented"),
+ }
+ }
+
+ fn nextDarwin(self: *Self) !?Entry {
+ start_over: while (true) {
+ if (self.index >= self.end_index) {
+ const rc = os.system.__getdirentries64(
+ self.dir.fd,
+ &self.buf,
+ self.buf.len,
+ &self.seek,
+ );
+ if (rc == 0) return null;
+ if (rc < 0) {
+ switch (os.errno(rc)) {
+ os.EBADF => unreachable,
+ os.EFAULT => unreachable,
+ os.ENOTDIR => unreachable,
+ os.EINVAL => unreachable,
+ else => |err| return os.unexpectedErrno(err),
+ }
+ }
+ self.index = 0;
+ self.end_index = @intCast(usize, rc);
+ }
+ const darwin_entry = @ptrCast(*align(1) os.dirent, &self.buf[self.index]);
+ const next_index = self.index + darwin_entry.d_reclen;
+ self.index = next_index;
+
+ const name = @ptrCast([*]u8, &darwin_entry.d_name)[0..darwin_entry.d_namlen];
+
+ if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
+ continue :start_over;
+ }
+
+ const entry_kind = switch (darwin_entry.d_type) {
+ os.DT_BLK => Entry.Kind.BlockDevice,
+ os.DT_CHR => Entry.Kind.CharacterDevice,
+ os.DT_DIR => Entry.Kind.Directory,
+ os.DT_FIFO => Entry.Kind.NamedPipe,
+ os.DT_LNK => Entry.Kind.SymLink,
+ os.DT_REG => Entry.Kind.File,
+ os.DT_SOCK => Entry.Kind.UnixDomainSocket,
+ os.DT_WHT => Entry.Kind.Whiteout,
+ else => Entry.Kind.Unknown,
+ };
+ return Entry{
+ .name = name,
+ .kind = entry_kind,
+ };
+ }
+ }
+
+ fn nextBsd(self: *Self) !?Entry {
+ start_over: while (true) {
+ if (self.index >= self.end_index) {
+ const rc = os.system.getdirentries(
+ self.dir.fd,
+ self.buf[0..].ptr,
+ self.buf.len,
+ &self.seek,
+ );
+ switch (os.errno(rc)) {
+ 0 => {},
+ os.EBADF => unreachable,
+ os.EFAULT => unreachable,
+ os.ENOTDIR => unreachable,
+ os.EINVAL => unreachable,
+ else => |err| return os.unexpectedErrno(err),
+ }
+ if (rc == 0) return null;
+ self.index = 0;
+ self.end_index = @intCast(usize, rc);
+ }
+ const freebsd_entry = @ptrCast(*align(1) os.dirent, &self.buf[self.index]);
+ const next_index = self.index + freebsd_entry.d_reclen;
+ self.index = next_index;
+
+ const name = @ptrCast([*]u8, &freebsd_entry.d_name)[0..freebsd_entry.d_namlen];
+
+ if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
+ continue :start_over;
+ }
+
+ const entry_kind = switch (freebsd_entry.d_type) {
+ os.DT_BLK => Entry.Kind.BlockDevice,
+ os.DT_CHR => Entry.Kind.CharacterDevice,
+ os.DT_DIR => Entry.Kind.Directory,
+ os.DT_FIFO => Entry.Kind.NamedPipe,
+ os.DT_LNK => Entry.Kind.SymLink,
+ os.DT_REG => Entry.Kind.File,
+ os.DT_SOCK => Entry.Kind.UnixDomainSocket,
+ os.DT_WHT => Entry.Kind.Whiteout,
+ else => Entry.Kind.Unknown,
+ };
+ return Entry{
+ .name = name,
+ .kind = entry_kind,
+ };
+ }
+ }
},
.linux => struct {
- fd: i32,
- buf: []u8,
+ dir: Dir,
+ buf: [8192]u8, // TODO align(@alignOf(os.dirent64)),
index: usize,
end_index: usize,
+
+ const Self = @This();
+
+ pub const Error = IteratorError;
+
+ /// Memory such as file names referenced in this returned entry becomes invalid
+ /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
+ pub fn next(self: *Self) Error!?Entry {
+ start_over: while (true) {
+ if (self.index >= self.end_index) {
+ const rc = os.linux.getdents64(self.dir.fd, &self.buf, self.buf.len);
+ switch (os.linux.getErrno(rc)) {
+ 0 => {},
+ os.EBADF => unreachable,
+ os.EFAULT => unreachable,
+ os.ENOTDIR => unreachable,
+ os.EINVAL => unreachable,
+ else => |err| return os.unexpectedErrno(err),
+ }
+ if (rc == 0) return null;
+ self.index = 0;
+ self.end_index = rc;
+ }
+ const linux_entry = @ptrCast(*align(1) os.dirent64, &self.buf[self.index]);
+ const next_index = self.index + linux_entry.d_reclen;
+ self.index = next_index;
+
+ const name = mem.toSlice(u8, @ptrCast([*]u8, &linux_entry.d_name));
+
+ // skip . and .. entries
+ if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
+ continue :start_over;
+ }
+
+ const entry_kind = switch (linux_entry.d_type) {
+ os.DT_BLK => Entry.Kind.BlockDevice,
+ os.DT_CHR => Entry.Kind.CharacterDevice,
+ os.DT_DIR => Entry.Kind.Directory,
+ os.DT_FIFO => Entry.Kind.NamedPipe,
+ os.DT_LNK => Entry.Kind.SymLink,
+ os.DT_REG => Entry.Kind.File,
+ os.DT_SOCK => Entry.Kind.UnixDomainSocket,
+ else => Entry.Kind.Unknown,
+ };
+ return Entry{
+ .name = name,
+ .kind = entry_kind,
+ };
+ }
+ }
},
.windows => struct {
- handle: os.windows.HANDLE,
- find_file_data: os.windows.WIN32_FIND_DATAW,
+ dir: Dir,
+ buf: [8192]u8 align(@alignOf(os.windows.FILE_BOTH_DIR_INFORMATION)),
+ index: usize,
+ end_index: usize,
first: bool,
name_data: [256]u8,
+
+ const Self = @This();
+
+ pub const Error = IteratorError;
+
+ pub fn next(self: *Self) Error!?Entry {
+ start_over: while (true) {
+ const w = os.windows;
+ if (self.index >= self.end_index) {
+ var io: w.IO_STATUS_BLOCK = undefined;
+ const rc = w.ntdll.NtQueryDirectoryFile(
+ self.dir.fd,
+ null,
+ null,
+ null,
+ &io,
+ &self.buf,
+ self.buf.len,
+ .FileBothDirectoryInformation,
+ w.FALSE,
+ null,
+ if (self.first) w.BOOLEAN(w.TRUE) else w.BOOLEAN(w.FALSE),
+ );
+ self.first = false;
+ if (io.Information == 0) return null;
+ self.index = 0;
+ self.end_index = io.Information;
+ switch (rc) {
+ w.STATUS.SUCCESS => {},
+ w.STATUS.ACCESS_DENIED => return error.AccessDenied,
+ else => return w.unexpectedStatus(rc),
+ }
+ }
+
+ const aligned_ptr = @alignCast(@alignOf(w.FILE_BOTH_DIR_INFORMATION), &self.buf[self.index]);
+ const dir_info = @ptrCast(*w.FILE_BOTH_DIR_INFORMATION, aligned_ptr);
+ if (dir_info.NextEntryOffset != 0) {
+ self.index += dir_info.NextEntryOffset;
+ } else {
+ self.index = self.buf.len;
+ }
+
+ const name_utf16le = @ptrCast([*]u16, &dir_info.FileName)[0 .. dir_info.FileNameLength / 2];
+
+ if (mem.eql(u16, name_utf16le, [_]u16{'.'}) or mem.eql(u16, name_utf16le, [_]u16{ '.', '.' }))
+ continue;
+ // Trust that Windows gives us valid UTF-16LE
+ const name_utf8_len = std.unicode.utf16leToUtf8(self.name_data[0..], name_utf16le) catch unreachable;
+ const name_utf8 = self.name_data[0..name_utf8_len];
+ const kind = blk: {
+ const attrs = dir_info.FileAttributes;
+ if (attrs & w.FILE_ATTRIBUTE_DIRECTORY != 0) break :blk Entry.Kind.Directory;
+ if (attrs & w.FILE_ATTRIBUTE_REPARSE_POINT != 0) break :blk Entry.Kind.SymLink;
+ break :blk Entry.Kind.File;
+ };
+ return Entry{
+ .name = name_utf8,
+ .kind = kind,
+ };
+ }
+ }
},
else => @compileError("unimplemented"),
};
- pub const Entry = struct {
- name: []const u8,
- kind: Kind,
+ pub fn iterate(self: Dir) Iterator {
+ switch (builtin.os) {
+ .macosx, .ios, .freebsd, .netbsd => return Iterator{
+ .dir = self,
+ .seek = 0,
+ .index = 0,
+ .end_index = 0,
+ .buf = undefined,
+ },
+ .linux => return Iterator{
+ .dir = self,
+ .index = 0,
+ .end_index = 0,
+ .buf = undefined,
+ },
+ .windows => return Iterator{
+ .dir = self,
+ .index = 0,
+ .end_index = 0,
+ .first = true,
+ .buf = undefined,
+ .name_data = undefined,
+ },
+ else => @compileError("unimplemented"),
+ }
+ }
- pub const Kind = enum {
- BlockDevice,
- CharacterDevice,
- Directory,
- NamedPipe,
- SymLink,
- File,
- UnixDomainSocket,
- Whiteout,
- Unknown,
- };
- };
+ /// Returns an open handle to the current working directory.
+ /// Closing the returned `Dir` is checked illegal behavior.
+ /// On POSIX targets, this function is comptime-callable.
+ pub fn cwd() Dir {
+ if (os.windows.is_the_target) {
+ return Dir{ .fd = os.windows.peb().ProcessParameters.CurrentDirectory.Handle };
+ } else {
+ return Dir{ .fd = os.AT_FDCWD };
+ }
+ }
pub const OpenError = error{
FileNotFound,
NotDir,
AccessDenied,
- FileTooBig,
- IsDir,
SymLinkLoop,
ProcessFdQuotaExceeded,
NameTooLong,
SystemFdQuotaExceeded,
NoDevice,
SystemResources,
- NoSpaceLeft,
- PathAlreadyExists,
- OutOfMemory,
InvalidUtf8,
BadPathName,
DeviceBusy,
+ } || os.UnexpectedError;
- Unexpected,
- };
-
- /// Call close when done.
- /// TODO remove the allocator requirement from this API
- /// https://github.com/ziglang/zig/issues/2885
- pub fn open(allocator: *Allocator, dir_path: []const u8) OpenError!Dir {
- return Dir{
- .allocator = allocator,
- .handle = switch (builtin.os) {
- .windows => blk: {
- var find_file_data: os.windows.WIN32_FIND_DATAW = undefined;
- const handle = try os.windows.FindFirstFile(dir_path, &find_file_data);
- break :blk Handle{
- .handle = handle,
- .find_file_data = find_file_data, // TODO guaranteed copy elision
- .first = true,
- .name_data = undefined,
- };
- },
- .macosx, .ios, .freebsd, .netbsd => Handle{
- .fd = try os.open(dir_path, os.O_RDONLY | os.O_NONBLOCK | os.O_DIRECTORY | os.O_CLOEXEC, 0),
- .seek = 0,
- .index = 0,
- .end_index = 0,
- .buf = [_]u8{},
- },
- .linux => Handle{
- .fd = try os.open(dir_path, os.O_RDONLY | os.O_DIRECTORY | os.O_CLOEXEC, 0),
- .index = 0,
- .end_index = 0,
- .buf = [_]u8{},
- },
- else => @compileError("unimplemented"),
- },
- };
+ /// Call `close` to free the directory handle.
+ pub fn open(dir_path: []const u8) OpenError!Dir {
+ return cwd().openDir(dir_path);
}
- pub fn close(self: *Dir) void {
- if (os.windows.is_the_target) {
- return os.windows.FindClose(self.handle.handle);
- }
- self.allocator.free(self.handle.buf);
- os.close(self.handle.fd);
+ /// Same as `open` except the parameter is null-terminated.
+ pub fn openC(dir_path_c: [*]const u8) OpenError!Dir {
+ return cwd().openDirC(dir_path_c);
}
- /// Memory such as file names referenced in this returned entry becomes invalid
- /// with subsequent calls to next, as well as when this `Dir` is deinitialized.
- pub fn next(self: *Dir) !?Entry {
- switch (builtin.os) {
- .linux => return self.nextLinux(),
- .macosx, .ios => return self.nextDarwin(),
- .windows => return self.nextWindows(),
- .freebsd => return self.nextBsd(),
- .netbsd => return self.nextBsd(),
- else => @compileError("unimplemented"),
- }
+ pub fn close(self: *Dir) void {
+ os.close(self.fd);
+ self.* = undefined;
}
- pub fn openRead(self: Dir, file_path: []const u8) os.OpenError!File {
- const path_c = try os.toPosixPath(file_path);
+ /// Call `File.close` on the result when done.
+ pub fn openRead(self: Dir, sub_path: []const u8) File.OpenError!File {
+ const path_c = try os.toPosixPath(sub_path);
return self.openReadC(&path_c);
}
- pub fn openReadC(self: Dir, file_path: [*]const u8) OpenError!File {
+ /// Call `File.close` on the result when done.
+ pub fn openReadC(self: Dir, sub_path: [*]const u8) File.OpenError!File {
const flags = os.O_LARGEFILE | os.O_RDONLY;
- const fd = try os.openatC(self.handle.fd, file_path, flags, 0);
+ const fd = try os.openatC(self.fd, sub_path, flags, 0);
return File.openHandle(fd);
}
- fn nextDarwin(self: *Dir) !?Entry {
- start_over: while (true) {
- if (self.handle.index >= self.handle.end_index) {
- if (self.handle.buf.len == 0) {
- self.handle.buf = try self.allocator.alloc(u8, mem.page_size);
- }
-
- while (true) {
- const rc = os.system.__getdirentries64(
- self.handle.fd,
- self.handle.buf.ptr,
- self.handle.buf.len,
- &self.handle.seek,
- );
- if (rc == 0) return null;
- if (rc < 0) {
- switch (os.errno(rc)) {
- os.EBADF => unreachable,
- os.EFAULT => unreachable,
- os.ENOTDIR => unreachable,
- os.EINVAL => {
- self.handle.buf = try self.allocator.realloc(self.handle.buf, self.handle.buf.len * 2);
- continue;
- },
- else => |err| return os.unexpectedErrno(err),
- }
- }
- self.handle.index = 0;
- self.handle.end_index = @intCast(usize, rc);
- break;
- }
- }
- const darwin_entry = @ptrCast(*align(1) os.dirent, &self.handle.buf[self.handle.index]);
- const next_index = self.handle.index + darwin_entry.d_reclen;
- self.handle.index = next_index;
-
- const name = @ptrCast([*]u8, &darwin_entry.d_name)[0..darwin_entry.d_namlen];
+ /// Call `close` on the result when done.
+ pub fn openDir(self: Dir, sub_path: []const u8) OpenError!Dir {
+ if (os.windows.is_the_target) {
+ const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
+ return self.openDirW(&sub_path_w);
+ }
- if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
- continue :start_over;
- }
+ const sub_path_c = try os.toPosixPath(sub_path);
+ return self.openDirC(&sub_path_c);
+ }
- const entry_kind = switch (darwin_entry.d_type) {
- os.DT_BLK => Entry.Kind.BlockDevice,
- os.DT_CHR => Entry.Kind.CharacterDevice,
- os.DT_DIR => Entry.Kind.Directory,
- os.DT_FIFO => Entry.Kind.NamedPipe,
- os.DT_LNK => Entry.Kind.SymLink,
- os.DT_REG => Entry.Kind.File,
- os.DT_SOCK => Entry.Kind.UnixDomainSocket,
- os.DT_WHT => Entry.Kind.Whiteout,
- else => Entry.Kind.Unknown,
- };
- return Entry{
- .name = name,
- .kind = entry_kind,
- };
+ /// Same as `openDir` except the parameter is null-terminated.
+ pub fn openDirC(self: Dir, sub_path_c: [*]const u8) OpenError!Dir {
+ if (os.windows.is_the_target) {
+ const sub_path_w = try os.windows.cStrToPrefixedFileW(sub_path_c);
+ return self.openDirW(&sub_path_w);
}
+
+ const flags = os.O_RDONLY | os.O_DIRECTORY | os.O_CLOEXEC;
+ const fd = os.openatC(self.fd, sub_path_c, flags, 0) catch |err| switch (err) {
+ error.FileTooBig => unreachable, // can't happen for directories
+ error.IsDir => unreachable, // we're providing O_DIRECTORY
+ error.NoSpaceLeft => unreachable, // not providing O_CREAT
+ error.PathAlreadyExists => unreachable, // not providing O_CREAT
+ else => |e| return e,
+ };
+ return Dir{ .fd = fd };
}
- fn nextWindows(self: *Dir) !?Entry {
- while (true) {
- if (self.handle.first) {
- self.handle.first = false;
- } else {
- if (!try os.windows.FindNextFile(self.handle.handle, &self.handle.find_file_data))
- return null;
- }
- const name_utf16le = mem.toSlice(u16, self.handle.find_file_data.cFileName[0..].ptr);
- if (mem.eql(u16, name_utf16le, [_]u16{'.'}) or mem.eql(u16, name_utf16le, [_]u16{ '.', '.' }))
- continue;
- // Trust that Windows gives us valid UTF-16LE
- const name_utf8_len = std.unicode.utf16leToUtf8(self.handle.name_data[0..], name_utf16le) catch unreachable;
- const name_utf8 = self.handle.name_data[0..name_utf8_len];
- const kind = blk: {
- const attrs = self.handle.find_file_data.dwFileAttributes;
- if (attrs & os.windows.FILE_ATTRIBUTE_DIRECTORY != 0) break :blk Entry.Kind.Directory;
- if (attrs & os.windows.FILE_ATTRIBUTE_REPARSE_POINT != 0) break :blk Entry.Kind.SymLink;
- break :blk Entry.Kind.File;
- };
- return Entry{
- .name = name_utf8,
- .kind = kind,
- };
+ /// Same as `openDir` except the path parameter is UTF16LE, NT-prefixed.
+ /// This function is Windows-only.
+ pub fn openDirW(self: Dir, sub_path_w: [*]const u16) OpenError!Dir {
+ const w = os.windows;
+
+ var result = Dir{
+ .fd = undefined,
+ };
+
+ const path_len_bytes = @intCast(u16, mem.toSliceConst(u16, sub_path_w).len * 2);
+ var nt_name = w.UNICODE_STRING{
+ .Length = path_len_bytes,
+ .MaximumLength = path_len_bytes,
+ .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)),
+ };
+ var attr = w.OBJECT_ATTRIBUTES{
+ .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
+ .RootDirectory = if (path.isAbsoluteW(sub_path_w)) null else self.fd,
+ .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
+ .ObjectName = &nt_name,
+ .SecurityDescriptor = null,
+ .SecurityQualityOfService = null,
+ };
+ if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
+ // Windows does not recognize this, but it does work with empty string.
+ nt_name.Length = 0;
+ }
+ if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) {
+ // If you're looking to contribute to zig and fix this, see here for an example of how to
+ // implement this: https://git.midipix.org/ntapi/tree/src/fs/ntapi_tt_open_physical_parent_directory.c
+ @panic("TODO opening '..' with a relative directory handle is not yet implemented on Windows");
+ }
+ var io: w.IO_STATUS_BLOCK = undefined;
+ const rc = w.ntdll.NtCreateFile(
+ &result.fd,
+ w.GENERIC_READ | w.SYNCHRONIZE,
+ &attr,
+ &io,
+ null,
+ 0,
+ w.FILE_SHARE_READ | w.FILE_SHARE_WRITE,
+ w.FILE_OPEN,
+ w.FILE_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT | w.FILE_OPEN_FOR_BACKUP_INTENT,
+ null,
+ 0,
+ );
+ switch (rc) {
+ w.STATUS.SUCCESS => return result,
+ w.STATUS.OBJECT_NAME_INVALID => unreachable,
+ w.STATUS.OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
+ w.STATUS.OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
+ w.STATUS.INVALID_PARAMETER => unreachable,
+ else => return w.unexpectedStatus(rc),
}
}
- fn nextLinux(self: *Dir) !?Entry {
- start_over: while (true) {
- if (self.handle.index >= self.handle.end_index) {
- if (self.handle.buf.len == 0) {
- self.handle.buf = try self.allocator.alloc(u8, mem.page_size);
- }
+ pub const DeleteFileError = os.UnlinkError;
- while (true) {
- const rc = os.linux.getdents64(self.handle.fd, self.handle.buf.ptr, self.handle.buf.len);
- switch (os.linux.getErrno(rc)) {
- 0 => {},
- os.EBADF => unreachable,
- os.EFAULT => unreachable,
- os.ENOTDIR => unreachable,
- os.EINVAL => {
- self.handle.buf = try self.allocator.realloc(self.handle.buf, self.handle.buf.len * 2);
- continue;
- },
- else => |err| return os.unexpectedErrno(err),
- }
- if (rc == 0) return null;
- self.handle.index = 0;
- self.handle.end_index = rc;
- break;
- }
- }
- const linux_entry = @ptrCast(*align(1) os.dirent64, &self.handle.buf[self.handle.index]);
- const next_index = self.handle.index + linux_entry.d_reclen;
- self.handle.index = next_index;
+ /// Delete a file name and possibly the file it refers to, based on an open directory handle.
+ pub fn deleteFile(self: Dir, sub_path: []const u8) DeleteFileError!void {
+ const sub_path_c = try os.toPosixPath(sub_path);
+ return self.deleteFileC(&sub_path_c);
+ }
- const name = mem.toSlice(u8, @ptrCast([*]u8, &linux_entry.d_name));
+ /// Same as `deleteFile` except the parameter is null-terminated.
+ pub fn deleteFileC(self: Dir, sub_path_c: [*]const u8) DeleteFileError!void {
+ os.unlinkatC(self.fd, sub_path_c, 0) catch |err| switch (err) {
+ error.DirNotEmpty => unreachable, // not passing AT_REMOVEDIR
+ else => |e| return e,
+ };
+ }
- // skip . and .. entries
- if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
- continue :start_over;
- }
+ pub const DeleteDirError = error{
+ DirNotEmpty,
+ FileNotFound,
+ AccessDenied,
+ FileBusy,
+ FileSystem,
+ SymLinkLoop,
+ NameTooLong,
+ NotDir,
+ SystemResources,
+ ReadOnlyFileSystem,
+ InvalidUtf8,
+ BadPathName,
+ Unexpected,
+ };
- const entry_kind = switch (linux_entry.d_type) {
- os.DT_BLK => Entry.Kind.BlockDevice,
- os.DT_CHR => Entry.Kind.CharacterDevice,
- os.DT_DIR => Entry.Kind.Directory,
- os.DT_FIFO => Entry.Kind.NamedPipe,
- os.DT_LNK => Entry.Kind.SymLink,
- os.DT_REG => Entry.Kind.File,
- os.DT_SOCK => Entry.Kind.UnixDomainSocket,
- else => Entry.Kind.Unknown,
- };
- return Entry{
- .name = name,
- .kind = entry_kind,
- };
+ /// Returns `error.DirNotEmpty` if the directory is not empty.
+ /// To delete a directory recursively, see `deleteTree`.
+ pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void {
+ if (os.windows.is_the_target) {
+ const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
+ return self.deleteDirW(&sub_path_w);
}
+ const sub_path_c = try os.toPosixPath(sub_path);
+ return self.deleteDirC(&sub_path_c);
}
- fn nextBsd(self: *Dir) !?Entry {
- start_over: while (true) {
- if (self.handle.index >= self.handle.end_index) {
- if (self.handle.buf.len == 0) {
- self.handle.buf = try self.allocator.alloc(u8, mem.page_size);
- }
+ /// Same as `deleteDir` except the parameter is null-terminated.
+ pub fn deleteDirC(self: Dir, sub_path_c: [*]const u8) DeleteDirError!void {
+ os.unlinkatC(self.fd, sub_path_c, os.AT_REMOVEDIR) catch |err| switch (err) {
+ error.IsDir => unreachable, // not possible since we pass AT_REMOVEDIR
+ else => |e| return e,
+ };
+ }
- while (true) {
- const rc = os.system.getdirentries(
- self.handle.fd,
- self.handle.buf.ptr,
- self.handle.buf.len,
- &self.handle.seek,
- );
- switch (os.errno(rc)) {
- 0 => {},
- os.EBADF => unreachable,
- os.EFAULT => unreachable,
- os.ENOTDIR => unreachable,
- os.EINVAL => {
- self.handle.buf = try self.allocator.realloc(self.handle.buf, self.handle.buf.len * 2);
- continue;
- },
- else => |err| return os.unexpectedErrno(err),
- }
- if (rc == 0) return null;
- self.handle.index = 0;
- self.handle.end_index = @intCast(usize, rc);
- break;
- }
- }
- const freebsd_entry = @ptrCast(*align(1) os.dirent, &self.handle.buf[self.handle.index]);
- const next_index = self.handle.index + freebsd_entry.d_reclen;
- self.handle.index = next_index;
+ /// Same as `deleteDir` except the parameter is UTF16LE, NT prefixed.
+ /// This function is Windows-only.
+ pub fn deleteDirW(self: Dir, sub_path_w: [*]const u16) DeleteDirError!void {
+ os.unlinkatW(self.fd, sub_path_w, os.AT_REMOVEDIR) catch |err| switch (err) {
+ error.IsDir => unreachable, // not possible since we pass AT_REMOVEDIR
+ else => |e| return e,
+ };
+ }
- const name = @ptrCast([*]u8, &freebsd_entry.d_name)[0..freebsd_entry.d_namlen];
+ /// Read value of a symbolic link.
+ /// The return value is a slice of `buffer`, from index `0`.
+ pub fn readLink(self: Dir, sub_path: []const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 {
+ const sub_path_c = try os.toPosixPath(sub_path);
+ return self.readLinkC(&sub_path_c, buffer);
+ }
- if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
- continue :start_over;
+ /// Same as `readLink`, except the `pathname` parameter is null-terminated.
+ pub fn readLinkC(self: Dir, sub_path_c: [*]const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 {
+ return os.readlinkatC(self.fd, sub_path_c, buffer);
+ }
+
+ pub const DeleteTreeError = error{
+ AccessDenied,
+ FileTooBig,
+ SymLinkLoop,
+ ProcessFdQuotaExceeded,
+ NameTooLong,
+ SystemFdQuotaExceeded,
+ NoDevice,
+ SystemResources,
+ ReadOnlyFileSystem,
+ FileSystem,
+ FileBusy,
+ DeviceBusy,
+
+ /// One of the path components was not a directory.
+ /// This error is unreachable if `sub_path` does not contain a path separator.
+ NotDir,
+
+ /// On Windows, file paths must be valid Unicode.
+ InvalidUtf8,
+
+ /// On Windows, file paths cannot contain these characters:
+ /// '/', '*', '?', '"', '<', '>', '|'
+ BadPathName,
+ } || os.UnexpectedError;
+
+ /// Whether `full_path` describes a symlink, file, or directory, this function
+ /// removes it. If it cannot be removed because it is a non-empty directory,
+ /// this function recursively removes its entries and then tries again.
+ /// This operation is not atomic on most file systems.
+ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
+ start_over: while (true) {
+ var got_access_denied = false;
+ // First, try deleting the item as a file. This way we don't follow sym links.
+ if (self.deleteFile(sub_path)) {
+ return;
+ } else |err| switch (err) {
+ error.FileNotFound => return,
+ error.IsDir => {},
+ error.AccessDenied => got_access_denied = true,
+
+ error.InvalidUtf8,
+ error.SymLinkLoop,
+ error.NameTooLong,
+ error.SystemResources,
+ error.ReadOnlyFileSystem,
+ error.NotDir,
+ error.FileSystem,
+ error.FileBusy,
+ error.BadPathName,
+ error.Unexpected,
+ => |e| return e,
}
+ var dir = self.openDir(sub_path) catch |err| switch (err) {
+ error.NotDir => {
+ if (got_access_denied) {
+ return error.AccessDenied;
+ }
+ continue :start_over;
+ },
+ error.FileNotFound => {
+ // That's fine, we were trying to remove this directory anyway.
+ continue :start_over;
+ },
- const entry_kind = switch (freebsd_entry.d_type) {
- os.DT_BLK => Entry.Kind.BlockDevice,
- os.DT_CHR => Entry.Kind.CharacterDevice,
- os.DT_DIR => Entry.Kind.Directory,
- os.DT_FIFO => Entry.Kind.NamedPipe,
- os.DT_LNK => Entry.Kind.SymLink,
- os.DT_REG => Entry.Kind.File,
- os.DT_SOCK => Entry.Kind.UnixDomainSocket,
- os.DT_WHT => Entry.Kind.Whiteout,
- else => Entry.Kind.Unknown,
- };
- return Entry{
- .name = name,
- .kind = entry_kind,
+ error.AccessDenied,
+ error.SymLinkLoop,
+ error.ProcessFdQuotaExceeded,
+ error.NameTooLong,
+ error.SystemFdQuotaExceeded,
+ error.NoDevice,
+ error.SystemResources,
+ error.Unexpected,
+ error.InvalidUtf8,
+ error.BadPathName,
+ error.DeviceBusy,
+ => |e| return e,
};
+ var cleanup_dir_parent: ?Dir = null;
+ defer if (cleanup_dir_parent) |*d| d.close();
+
+ var cleanup_dir = true;
+ defer if (cleanup_dir) dir.close();
+
+ var dir_name_buf: [MAX_PATH_BYTES]u8 = undefined;
+ var dir_name: []const u8 = sub_path;
+ var parent_dir = self;
+
+ // Here we must avoid recursion, in order to provide O(1) memory guarantee of this function.
+ // Go through each entry and if it is not a directory, delete it. If it is a directory,
+ // open it, and close the original directory. Repeat. Then start the entire operation over.
+
+ scan_dir: while (true) {
+ var dir_it = dir.iterate();
+ while (try dir_it.next()) |entry| {
+ if (dir.deleteFile(entry.name)) {
+ continue;
+ } else |err| switch (err) {
+ error.FileNotFound => continue,
+
+ // Impossible because we do not pass any path separators.
+ error.NotDir => unreachable,
+
+ error.IsDir => {},
+ error.AccessDenied => got_access_denied = true,
+
+ error.InvalidUtf8,
+ error.SymLinkLoop,
+ error.NameTooLong,
+ error.SystemResources,
+ error.ReadOnlyFileSystem,
+ error.FileSystem,
+ error.FileBusy,
+ error.BadPathName,
+ error.Unexpected,
+ => |e| return e,
+ }
+
+ const new_dir = dir.openDir(entry.name) catch |err| switch (err) {
+ error.NotDir => {
+ if (got_access_denied) {
+ return error.AccessDenied;
+ }
+ continue :scan_dir;
+ },
+ error.FileNotFound => {
+ // That's fine, we were trying to remove this directory anyway.
+ continue :scan_dir;
+ },
+
+ error.AccessDenied,
+ error.SymLinkLoop,
+ error.ProcessFdQuotaExceeded,
+ error.NameTooLong,
+ error.SystemFdQuotaExceeded,
+ error.NoDevice,
+ error.SystemResources,
+ error.Unexpected,
+ error.InvalidUtf8,
+ error.BadPathName,
+ error.DeviceBusy,
+ => |e| return e,
+ };
+ if (cleanup_dir_parent) |*d| d.close();
+ cleanup_dir_parent = dir;
+ dir = new_dir;
+ mem.copy(u8, &dir_name_buf, entry.name);
+ dir_name = dir_name_buf[0..entry.name.len];
+ continue :scan_dir;
+ }
+ // Reached the end of the directory entries, which means we successfully deleted all of them.
+ // Now to remove the directory itself.
+ dir.close();
+ cleanup_dir = false;
+
+ if (cleanup_dir_parent) |d| {
+ d.deleteDir(dir_name) catch |err| switch (err) {
+ // These two things can happen due to file system race conditions.
+ error.FileNotFound, error.DirNotEmpty => continue :start_over,
+ else => |e| return e,
+ };
+ continue :start_over;
+ } else {
+ self.deleteDir(sub_path) catch |err| switch (err) {
+ error.FileNotFound => return,
+ error.DirNotEmpty => continue :start_over,
+ else => |e| return e,
+ };
+ return;
+ }
+ }
}
}
};
@@ -782,13 +1046,18 @@ pub const Walker = struct {
name_buffer: std.Buffer,
pub const Entry = struct {
- path: []const u8,
+ /// The containing directory. This can be used to operate directly on `basename`
+ /// rather than `path`, avoiding `error.NameTooLong` for deeply nested paths.
+ /// The directory remains open until `next` or `deinit` is called.
+ dir: Dir,
basename: []const u8,
+
+ path: []const u8,
kind: Dir.Entry.Kind,
};
const StackItem = struct {
- dir_it: Dir,
+ dir_it: Dir.Iterator,
dirname_len: usize,
};
@@ -806,23 +1075,26 @@ pub const Walker = struct {
try self.name_buffer.appendByte(path.sep);
try self.name_buffer.append(base.name);
if (base.kind == .Directory) {
- // TODO https://github.com/ziglang/zig/issues/2888
- var new_dir = try Dir.open(self.stack.allocator, self.name_buffer.toSliceConst());
+ var new_dir = top.dir_it.dir.openDir(base.name) catch |err| switch (err) {
+ error.NameTooLong => unreachable, // no path sep in base.name
+ else => |e| return e,
+ };
{
errdefer new_dir.close();
try self.stack.append(StackItem{
- .dir_it = new_dir,
+ .dir_it = new_dir.iterate(),
.dirname_len = self.name_buffer.len(),
});
}
}
return Entry{
+ .dir = top.dir_it.dir,
.basename = self.name_buffer.toSliceConst()[dirname_len + 1 ..],
.path = self.name_buffer.toSliceConst(),
.kind = base.kind,
};
} else {
- self.stack.pop().dir_it.close();
+ self.stack.pop().dir_it.dir.close();
}
}
}
@@ -837,12 +1109,12 @@ pub const Walker = struct {
/// Recursively iterates over a directory.
/// Must call `Walker.deinit` when done.
/// `dir_path` must not end in a path separator.
-/// TODO: https://github.com/ziglang/zig/issues/2888
+/// The order of returned file system entries is undefined.
pub fn walkPath(allocator: *Allocator, dir_path: []const u8) !Walker {
assert(!mem.endsWith(u8, dir_path, path.sep_str));
- var dir_it = try Dir.open(allocator, dir_path);
- errdefer dir_it.close();
+ var dir = try Dir.open(dir_path);
+ errdefer dir.close();
var name_buffer = try std.Buffer.init(allocator, dir_path);
errdefer name_buffer.deinit();
@@ -853,7 +1125,7 @@ pub fn walkPath(allocator: *Allocator, dir_path: []const u8) !Walker {
};
try walker.stack.append(Walker.StackItem{
- .dir_it = dir_it,
+ .dir_it = dir.iterate(),
.dirname_len = dir_path.len,
});
@@ -862,15 +1134,13 @@ pub fn walkPath(allocator: *Allocator, dir_path: []const u8) !Walker {
/// Read value of a symbolic link.
/// The return value is a slice of buffer, from index `0`.
-/// TODO https://github.com/ziglang/zig/issues/2888
-pub fn readLink(pathname: []const u8, buffer: *[os.PATH_MAX]u8) ![]u8 {
+pub fn readLink(pathname: []const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 {
return os.readlink(pathname, buffer);
}
-/// Same as `readLink`, except the `pathname` parameter is null-terminated.
-/// TODO https://github.com/ziglang/zig/issues/2888
-pub fn readLinkC(pathname: [*]const u8, buffer: *[os.PATH_MAX]u8) ![]u8 {
- return os.readlinkC(pathname, buffer);
+/// Same as `readLink`, except the parameter is null-terminated.
+pub fn readLinkC(pathname_c: [*]const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 {
+ return os.readlinkC(pathname_c, buffer);
}
pub const OpenSelfExeError = os.OpenError || os.windows.CreateFileError || SelfExePathError;
diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig
index 32c221d063..a9212d69d4 100644
--- a/lib/std/fs/file.zig
+++ b/lib/std/fs/file.zig
@@ -243,6 +243,7 @@ pub const File = struct {
switch (rc) {
windows.STATUS.SUCCESS => {},
windows.STATUS.BUFFER_OVERFLOW => {},
+ windows.STATUS.INVALID_PARAMETER => unreachable,
else => return windows.unexpectedStatus(rc),
}
return Stat{
diff --git a/lib/std/fs/path.zig b/lib/std/fs/path.zig
index 2bb23f04ce..560d9b58f3 100644
--- a/lib/std/fs/path.zig
+++ b/lib/std/fs/path.zig
@@ -136,6 +136,25 @@ pub fn isAbsolute(path: []const u8) bool {
}
}
+pub fn isAbsoluteW(path_w: [*]const u16) bool {
+ if (path_w[0] == '/')
+ return true;
+
+ if (path_w[0] == '\\') {
+ return true;
+ }
+ if (path_w[0] == 0 or path_w[1] == 0 or path_w[2] == 0) {
+ return false;
+ }
+ if (path_w[1] == ':') {
+ if (path_w[2] == '/')
+ return true;
+ if (path_w[2] == '\\')
+ return true;
+ }
+ return false;
+}
+
pub fn isAbsoluteWindows(path: []const u8) bool {
if (path[0] == '/')
return true;
diff --git a/lib/std/io.zig b/lib/std/io.zig
index f9082ee952..7502601650 100644
--- a/lib/std/io.zig
+++ b/lib/std/io.zig
@@ -127,6 +127,7 @@ pub fn OutStream(comptime WriteError: type) type {
};
}
+/// TODO move this to `std.fs` and add a version to `std.fs.Dir`.
pub fn writeFile(path: []const u8, data: []const u8) !void {
var file = try File.openWrite(path);
defer file.close();
@@ -134,11 +135,13 @@ pub fn writeFile(path: []const u8, data: []const u8) !void {
}
/// On success, caller owns returned buffer.
+/// TODO move this to `std.fs` and add a version to `std.fs.Dir`.
pub fn readFileAlloc(allocator: *mem.Allocator, path: []const u8) ![]u8 {
return readFileAllocAligned(allocator, path, @alignOf(u8));
}
/// On success, caller owns returned buffer.
+/// TODO move this to `std.fs` and add a version to `std.fs.Dir`.
pub fn readFileAllocAligned(allocator: *mem.Allocator, path: []const u8, comptime A: u29) ![]align(A) u8 {
var file = try File.openRead(path);
defer file.close();
diff --git a/lib/std/os.zig b/lib/std/os.zig
index 3395ef669d..3f8daf0290 100644
--- a/lib/std/os.zig
+++ b/lib/std/os.zig
@@ -529,22 +529,36 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void
pub const OpenError = error{
AccessDenied,
- FileTooBig,
- IsDir,
SymLinkLoop,
ProcessFdQuotaExceeded,
- NameTooLong,
SystemFdQuotaExceeded,
NoDevice,
FileNotFound,
+ /// The path exceeded `MAX_PATH_BYTES` bytes.
+ NameTooLong,
+
/// Insufficient kernel memory was available, or
/// the named file is a FIFO and per-user hard limit on
/// memory allocation for pipes has been reached.
SystemResources,
+ /// The file is too large to be opened. This error is unreachable
+ /// for 64-bit targets, as well as when opening directories.
+ FileTooBig,
+
+ /// The path refers to directory but the `O_DIRECTORY` flag was not provided.
+ IsDir,
+
+ /// A new path cannot be created because the device has no room for the new file.
+ /// This error is only reachable when the `O_CREAT` flag is provided.
NoSpaceLeft,
+
+ /// A component used as a directory in the path was not, in fact, a directory, or
+ /// `O_DIRECTORY` was specified and the path was not a directory.
NotDir,
+
+ /// The path already exists and the `O_CREAT` and `O_EXCL` flags were provided.
PathAlreadyExists,
DeviceBusy,
} || UnexpectedError;
@@ -978,6 +992,114 @@ pub fn unlinkC(file_path: [*]const u8) UnlinkError!void {
}
}
+pub const UnlinkatError = UnlinkError || error{
+ /// When passing `AT_REMOVEDIR`, this error occurs when the named directory is not empty.
+ DirNotEmpty,
+};
+
+/// Delete a file name and possibly the file it refers to, based on an open directory handle.
+pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void {
+ if (windows.is_the_target) {
+ const file_path_w = try windows.sliceToPrefixedFileW(file_path);
+ return unlinkatW(dirfd, &file_path_w, flags);
+ }
+ const file_path_c = try toPosixPath(file_path);
+ return unlinkatC(dirfd, &file_path_c, flags);
+}
+
+/// Same as `unlinkat` but `file_path` is a null-terminated string.
+pub fn unlinkatC(dirfd: fd_t, file_path_c: [*]const u8, flags: u32) UnlinkatError!void {
+ if (windows.is_the_target) {
+ const file_path_w = try windows.cStrToPrefixedFileW(file_path_c);
+ return unlinkatW(dirfd, &file_path_w, flags);
+ }
+ switch (errno(system.unlinkat(dirfd, file_path_c, flags))) {
+ 0 => return,
+ EACCES => return error.AccessDenied,
+ EPERM => return error.AccessDenied,
+ EBUSY => return error.FileBusy,
+ EFAULT => unreachable,
+ EIO => return error.FileSystem,
+ EISDIR => return error.IsDir,
+ ELOOP => return error.SymLinkLoop,
+ ENAMETOOLONG => return error.NameTooLong,
+ ENOENT => return error.FileNotFound,
+ ENOTDIR => return error.NotDir,
+ ENOMEM => return error.SystemResources,
+ EROFS => return error.ReadOnlyFileSystem,
+ ENOTEMPTY => return error.DirNotEmpty,
+
+ EINVAL => unreachable, // invalid flags, or pathname has . as last component
+ EBADF => unreachable, // always a race condition
+
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+/// Same as `unlinkat` but `sub_path_w` is UTF16LE, NT prefixed. Windows only.
+pub fn unlinkatW(dirfd: fd_t, sub_path_w: [*]const u16, flags: u32) UnlinkatError!void {
+ const w = windows;
+
+ const want_rmdir_behavior = (flags & AT_REMOVEDIR) != 0;
+ const create_options_flags = if (want_rmdir_behavior)
+ w.ULONG(w.FILE_DELETE_ON_CLOSE)
+ else
+ w.ULONG(w.FILE_DELETE_ON_CLOSE | w.FILE_NON_DIRECTORY_FILE);
+
+ const path_len_bytes = @intCast(u16, mem.toSliceConst(u16, sub_path_w).len * 2);
+ var nt_name = w.UNICODE_STRING{
+ .Length = path_len_bytes,
+ .MaximumLength = path_len_bytes,
+ // The Windows API makes this mutable, but it will not mutate here.
+ .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)),
+ };
+
+ if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
+ // Windows does not recognize this, but it does work with empty string.
+ nt_name.Length = 0;
+ }
+ if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) {
+ // Can't remove the parent directory with an open handle.
+ return error.FileBusy;
+ }
+
+
+ var attr = w.OBJECT_ATTRIBUTES{
+ .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
+ .RootDirectory = dirfd,
+ .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
+ .ObjectName = &nt_name,
+ .SecurityDescriptor = null,
+ .SecurityQualityOfService = null,
+ };
+ var io: w.IO_STATUS_BLOCK = undefined;
+ var tmp_handle: w.HANDLE = undefined;
+ var rc = w.ntdll.NtCreateFile(
+ &tmp_handle,
+ w.SYNCHRONIZE | w.DELETE,
+ &attr,
+ &io,
+ null,
+ 0,
+ w.FILE_SHARE_READ | w.FILE_SHARE_WRITE | w.FILE_SHARE_DELETE,
+ w.FILE_OPEN,
+ create_options_flags,
+ null,
+ 0,
+ );
+ if (rc == w.STATUS.SUCCESS) {
+ rc = w.ntdll.NtClose(tmp_handle);
+ }
+ switch (rc) {
+ w.STATUS.SUCCESS => return,
+ w.STATUS.OBJECT_NAME_INVALID => unreachable,
+ w.STATUS.OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
+ w.STATUS.INVALID_PARAMETER => unreachable,
+ w.STATUS.FILE_IS_A_DIRECTORY => return error.IsDir,
+ else => return w.unexpectedStatus(rc),
+ }
+}
+
const RenameError = error{
AccessDenied,
FileBusy,
@@ -1237,6 +1359,27 @@ pub fn readlinkC(file_path: [*]const u8, out_buffer: []u8) ReadLinkError![]u8 {
}
}
+pub fn readlinkatC(dirfd: fd_t, file_path: [*]const u8, out_buffer: []u8) ReadLinkError![]u8 {
+ if (windows.is_the_target) {
+ const file_path_w = try windows.cStrToPrefixedFileW(file_path);
+ @compileError("TODO implement readlink for Windows");
+ }
+ const rc = system.readlinkat(dirfd, file_path, out_buffer.ptr, out_buffer.len);
+ switch (errno(rc)) {
+ 0 => return out_buffer[0..@bitCast(usize, rc)],
+ EACCES => return error.AccessDenied,
+ EFAULT => unreachable,
+ EINVAL => unreachable,
+ EIO => return error.FileSystem,
+ ELOOP => return error.SymLinkLoop,
+ ENAMETOOLONG => return error.NameTooLong,
+ ENOENT => return error.FileNotFound,
+ ENOMEM => return error.SystemResources,
+ ENOTDIR => return error.NotDir,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
pub const SetIdError = error{
ResourceLimitReached,
InvalidUserId,
diff --git a/lib/std/os/bits/darwin.zig b/lib/std/os/bits/darwin.zig
index b076f95884..fe3156bb90 100644
--- a/lib/std/os/bits/darwin.zig
+++ b/lib/std/os/bits/darwin.zig
@@ -1178,3 +1178,17 @@ pub fn S_IWHT(m: u32) bool {
return m & S_IFMT == S_IFWHT;
}
pub const HOST_NAME_MAX = 72;
+
+pub const AT_FDCWD = -2;
+
+/// Use effective ids in access check
+pub const AT_EACCESS = 0x0010;
+
+/// Act on the symlink itself not the target
+pub const AT_SYMLINK_NOFOLLOW = 0x0020;
+
+/// Act on target of symlink
+pub const AT_SYMLINK_FOLLOW = 0x0040;
+
+/// Path refers to directory
+pub const AT_REMOVEDIR = 0x0080;
diff --git a/lib/std/os/bits/freebsd.zig b/lib/std/os/bits/freebsd.zig
index 3d07e92e01..4427acb334 100644
--- a/lib/std/os/bits/freebsd.zig
+++ b/lib/std/os/bits/freebsd.zig
@@ -939,3 +939,23 @@ pub fn S_IWHT(m: u32) bool {
}
pub const HOST_NAME_MAX = 255;
+
+/// Magic value that specify the use of the current working directory
+/// to determine the target of relative file paths in the openat() and
+/// similar syscalls.
+pub const AT_FDCWD = -100;
+
+/// Check access using effective user and group ID
+pub const AT_EACCESS = 0x0100;
+
+/// Do not follow symbolic links
+pub const AT_SYMLINK_NOFOLLOW = 0x0200;
+
+/// Follow symbolic link
+pub const AT_SYMLINK_FOLLOW = 0x0400;
+
+/// Remove directory instead of file
+pub const AT_REMOVEDIR = 0x0800;
+
+/// Fail if not under dirfd
+pub const AT_BENEATH = 0x1000;
diff --git a/lib/std/os/bits/windows.zig b/lib/std/os/bits/windows.zig
index fc148d812f..c261b52bed 100644
--- a/lib/std/os/bits/windows.zig
+++ b/lib/std/os/bits/windows.zig
@@ -158,3 +158,6 @@ pub const EWOULDBLOCK = 140;
pub const EDQUOT = 10069;
pub const F_OK = 0;
+
+/// Remove directory instead of unlinking file
+pub const AT_REMOVEDIR = 0x200;
diff --git a/lib/std/os/test.zig b/lib/std/os/test.zig
index 8d6b437ff3..b6e6728142 100644
--- a/lib/std/os/test.zig
+++ b/lib/std/os/test.zig
@@ -19,8 +19,8 @@ test "makePath, put some files in it, deleteTree" {
try fs.makePath(a, "os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c");
try io.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c" ++ fs.path.sep_str ++ "file.txt", "nonsense");
try io.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "file2.txt", "blah");
- try fs.deleteTree(a, "os_test_tmp");
- if (fs.Dir.open(a, "os_test_tmp")) |dir| {
+ try fs.deleteTree("os_test_tmp");
+ if (fs.Dir.open("os_test_tmp")) |dir| {
@panic("expected error");
} else |err| {
expect(err == error.FileNotFound);
@@ -37,7 +37,7 @@ test "access file" {
try io.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", "");
try os.access("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", os.F_OK);
- try fs.deleteTree(a, "os_test_tmp");
+ try fs.deleteTree("os_test_tmp");
}
fn testThreadIdFn(thread_id: *Thread.Id) void {
diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig
index c35d9d592b..6c4aeb4cef 100644
--- a/lib/std/os/windows.zig
+++ b/lib/std/os/windows.zig
@@ -20,6 +20,8 @@ pub const shell32 = @import("windows/shell32.zig");
pub usingnamespace @import("windows/bits.zig");
+pub const self_process_handle = @intToPtr(HANDLE, maxInt(usize));
+
/// `builtin` is missing `subsystem` when the subsystem is automatically detected,
/// so Zig standard library has the subsystem detection logic here. This should generally be
/// used rather than `builtin.subsystem`.
@@ -791,6 +793,25 @@ pub fn SetFileTime(
}
}
+pub fn peb() *PEB {
+ switch (builtin.arch) {
+ .i386 => {
+ return asm (
+ \\ mov %%fs:0x18, %[ptr]
+ \\ mov %%ds:0x30(%[ptr]), %[ptr]
+ : [ptr] "=r" (-> *PEB)
+ );
+ },
+ .x86_64 => {
+ return asm (
+ \\ mov %%gs:0x60, %[ptr]
+ : [ptr] "=r" (-> *PEB)
+ );
+ },
+ else => @compileError("unsupported architecture"),
+ }
+}
+
/// A file time is a 64-bit value that represents the number of 100-nanosecond
/// intervals that have elapsed since 12:00 A.M. January 1, 1601 Coordinated
/// Universal Time (UTC).
@@ -843,8 +864,8 @@ pub fn sliceToPrefixedSuffixedFileW(s: []const u8, comptime suffix: []const u16)
else => {},
}
}
- const start_index = if (mem.startsWith(u8, s, "\\\\") or !std.fs.path.isAbsolute(s)) 0 else blk: {
- const prefix = [_]u16{ '\\', '\\', '?', '\\' };
+ const start_index = if (mem.startsWith(u8, s, "\\?") or !std.fs.path.isAbsolute(s)) 0 else blk: {
+ const prefix = [_]u16{ '\\', '?', '?', '\\' };
mem.copy(u16, result[0..], prefix);
break :blk prefix.len;
};
@@ -878,7 +899,7 @@ pub fn unexpectedError(err: DWORD) std.os.UnexpectedError {
/// and you get an unexpected status.
pub fn unexpectedStatus(status: NTSTATUS) std.os.UnexpectedError {
if (std.os.unexpected_error_tracing) {
- std.debug.warn("error.Unexpected NTSTATUS={}\n", status);
+ std.debug.warn("error.Unexpected NTSTATUS=0x{x}\n", status);
std.debug.dumpCurrentStackTrace(null);
}
return error.Unexpected;
diff --git a/lib/std/os/windows/bits.zig b/lib/std/os/windows/bits.zig
index ddfdd27e1b..214f75186f 100644
--- a/lib/std/os/windows/bits.zig
+++ b/lib/std/os/windows/bits.zig
@@ -300,6 +300,44 @@ pub const FILE_SHARE_DELETE = 0x00000004;
pub const FILE_SHARE_READ = 0x00000001;
pub const FILE_SHARE_WRITE = 0x00000002;
+pub const DELETE = 0x00010000;
+pub const READ_CONTROL = 0x00020000;
+pub const WRITE_DAC = 0x00040000;
+pub const WRITE_OWNER = 0x00080000;
+pub const SYNCHRONIZE = 0x00100000;
+pub const STANDARD_RIGHTS_REQUIRED = 0x000f0000;
+
+// disposition for NtCreateFile
+pub const FILE_SUPERSEDE = 0;
+pub const FILE_OPEN = 1;
+pub const FILE_CREATE = 2;
+pub const FILE_OPEN_IF = 3;
+pub const FILE_OVERWRITE = 4;
+pub const FILE_OVERWRITE_IF = 5;
+pub const FILE_MAXIMUM_DISPOSITION = 5;
+
+// flags for NtCreateFile and NtOpenFile
+pub const FILE_DIRECTORY_FILE = 0x00000001;
+pub const FILE_WRITE_THROUGH = 0x00000002;
+pub const FILE_SEQUENTIAL_ONLY = 0x00000004;
+pub const FILE_NO_INTERMEDIATE_BUFFERING = 0x00000008;
+pub const FILE_SYNCHRONOUS_IO_ALERT = 0x00000010;
+pub const FILE_SYNCHRONOUS_IO_NONALERT = 0x00000020;
+pub const FILE_NON_DIRECTORY_FILE = 0x00000040;
+pub const FILE_CREATE_TREE_CONNECTION = 0x00000080;
+pub const FILE_COMPLETE_IF_OPLOCKED = 0x00000100;
+pub const FILE_NO_EA_KNOWLEDGE = 0x00000200;
+pub const FILE_OPEN_FOR_RECOVERY = 0x00000400;
+pub const FILE_RANDOM_ACCESS = 0x00000800;
+pub const FILE_DELETE_ON_CLOSE = 0x00001000;
+pub const FILE_OPEN_BY_FILE_ID = 0x00002000;
+pub const FILE_OPEN_FOR_BACKUP_INTENT = 0x00004000;
+pub const FILE_NO_COMPRESSION = 0x00008000;
+pub const FILE_RESERVE_OPFILTER = 0x00100000;
+pub const FILE_TRANSACTED_MODE = 0x00200000;
+pub const FILE_OPEN_OFFLINE_FILE = 0x00400000;
+pub const FILE_OPEN_FOR_FREE_SPACE_QUERY = 0x00800000;
+
pub const CREATE_ALWAYS = 2;
pub const CREATE_NEW = 1;
pub const OPEN_ALWAYS = 4;
@@ -720,15 +758,119 @@ pub const VECTORED_EXCEPTION_HANDLER = stdcallcc fn (ExceptionInfo: *EXCEPTION_P
pub const OBJECT_ATTRIBUTES = extern struct {
Length: ULONG,
- RootDirectory: HANDLE,
+ RootDirectory: ?HANDLE,
ObjectName: *UNICODE_STRING,
Attributes: ULONG,
SecurityDescriptor: ?*c_void,
SecurityQualityOfService: ?*c_void,
};
+pub const OBJ_INHERIT = 0x00000002;
+pub const OBJ_PERMANENT = 0x00000010;
+pub const OBJ_EXCLUSIVE = 0x00000020;
+pub const OBJ_CASE_INSENSITIVE = 0x00000040;
+pub const OBJ_OPENIF = 0x00000080;
+pub const OBJ_OPENLINK = 0x00000100;
+pub const OBJ_KERNEL_HANDLE = 0x00000200;
+pub const OBJ_VALID_ATTRIBUTES = 0x000003F2;
+
pub const UNICODE_STRING = extern struct {
- Length: USHORT,
- MaximumLength: USHORT,
+ Length: c_ushort,
+ MaximumLength: c_ushort,
Buffer: [*]WCHAR,
};
+
+pub const PEB = extern struct {
+ Reserved1: [2]BYTE,
+ BeingDebugged: BYTE,
+ Reserved2: [1]BYTE,
+ Reserved3: [2]PVOID,
+ Ldr: *PEB_LDR_DATA,
+ ProcessParameters: *RTL_USER_PROCESS_PARAMETERS,
+ Reserved4: [3]PVOID,
+ AtlThunkSListPtr: PVOID,
+ Reserved5: PVOID,
+ Reserved6: ULONG,
+ Reserved7: PVOID,
+ Reserved8: ULONG,
+ AtlThunkSListPtr32: ULONG,
+ Reserved9: [45]PVOID,
+ Reserved10: [96]BYTE,
+ PostProcessInitRoutine: PPS_POST_PROCESS_INIT_ROUTINE,
+ Reserved11: [128]BYTE,
+ Reserved12: [1]PVOID,
+ SessionId: ULONG,
+};
+
+pub const PEB_LDR_DATA = extern struct {
+ Reserved1: [8]BYTE,
+ Reserved2: [3]PVOID,
+ InMemoryOrderModuleList: LIST_ENTRY,
+};
+
+pub const RTL_USER_PROCESS_PARAMETERS = extern struct {
+ AllocationSize: ULONG,
+ Size: ULONG,
+ Flags: ULONG,
+ DebugFlags: ULONG,
+ ConsoleHandle: HANDLE,
+ ConsoleFlags: ULONG,
+ hStdInput: HANDLE,
+ hStdOutput: HANDLE,
+ hStdError: HANDLE,
+ CurrentDirectory: CURDIR,
+ DllPath: UNICODE_STRING,
+ ImagePathName: UNICODE_STRING,
+ CommandLine: UNICODE_STRING,
+ Environment: [*]WCHAR,
+ dwX: ULONG,
+ dwY: ULONG,
+ dwXSize: ULONG,
+ dwYSize: ULONG,
+ dwXCountChars: ULONG,
+ dwYCountChars: ULONG,
+ dwFillAttribute: ULONG,
+ dwFlags: ULONG,
+ dwShowWindow: ULONG,
+ WindowTitle: UNICODE_STRING,
+ Desktop: UNICODE_STRING,
+ ShellInfo: UNICODE_STRING,
+ RuntimeInfo: UNICODE_STRING,
+ DLCurrentDirectory: [0x20]RTL_DRIVE_LETTER_CURDIR,
+};
+
+pub const RTL_DRIVE_LETTER_CURDIR = extern struct {
+ Flags: c_ushort,
+ Length: c_ushort,
+ TimeStamp: ULONG,
+ DosPath: UNICODE_STRING,
+};
+
+pub const PPS_POST_PROCESS_INIT_ROUTINE = ?extern fn () void;
+
+pub const FILE_BOTH_DIR_INFORMATION = extern struct {
+ NextEntryOffset: ULONG,
+ FileIndex: ULONG,
+ CreationTime: LARGE_INTEGER,
+ LastAccessTime: LARGE_INTEGER,
+ LastWriteTime: LARGE_INTEGER,
+ ChangeTime: LARGE_INTEGER,
+ EndOfFile: LARGE_INTEGER,
+ AllocationSize: LARGE_INTEGER,
+ FileAttributes: ULONG,
+ FileNameLength: ULONG,
+ EaSize: ULONG,
+ ShortNameLength: CHAR,
+ ShortName: [12]WCHAR,
+ FileName: [1]WCHAR,
+};
+pub const FILE_BOTH_DIRECTORY_INFORMATION = FILE_BOTH_DIR_INFORMATION;
+
+pub const IO_APC_ROUTINE = extern fn (PVOID, *IO_STATUS_BLOCK, ULONG) void;
+
+pub const CURDIR = extern struct {
+ DosPath: UNICODE_STRING,
+ Handle: HANDLE,
+};
+
+pub const DUPLICATE_SAME_ACCESS = 2; \ No newline at end of file
diff --git a/lib/std/os/windows/kernel32.zig b/lib/std/os/windows/kernel32.zig
index 2ae73ad45a..736d81ae58 100644
--- a/lib/std/os/windows/kernel32.zig
+++ b/lib/std/os/windows/kernel32.zig
@@ -47,6 +47,8 @@ pub extern "kernel32" stdcallcc fn CreateThread(lpThreadAttributes: ?LPSECURITY_
pub extern "kernel32" stdcallcc fn DeleteFileW(lpFileName: [*]const u16) BOOL;
+pub extern "kernel32" stdcallcc fn DuplicateHandle(hSourceProcessHandle: HANDLE, hSourceHandle: HANDLE, hTargetProcessHandle: HANDLE, lpTargetHandle: *HANDLE, dwDesiredAccess: DWORD, bInheritHandle: BOOL, dwOptions: DWORD) BOOL;
+
pub extern "kernel32" stdcallcc fn ExitProcess(exit_code: UINT) noreturn;
pub extern "kernel32" stdcallcc fn FindFirstFileW(lpFileName: [*]const u16, lpFindFileData: *WIN32_FIND_DATAW) HANDLE;
diff --git a/lib/std/os/windows/ntdll.zig b/lib/std/os/windows/ntdll.zig
index bfc98aba8a..f914920093 100644
--- a/lib/std/os/windows/ntdll.zig
+++ b/lib/std/os/windows/ntdll.zig
@@ -13,12 +13,33 @@ pub extern "NtDll" stdcallcc fn NtCreateFile(
DesiredAccess: ACCESS_MASK,
ObjectAttributes: *OBJECT_ATTRIBUTES,
IoStatusBlock: *IO_STATUS_BLOCK,
- AllocationSize: *LARGE_INTEGER,
+ AllocationSize: ?*LARGE_INTEGER,
FileAttributes: ULONG,
ShareAccess: ULONG,
CreateDisposition: ULONG,
CreateOptions: ULONG,
- EaBuffer: *c_void,
+ EaBuffer: ?*c_void,
EaLength: ULONG,
) NTSTATUS;
pub extern "NtDll" stdcallcc fn NtClose(Handle: HANDLE) NTSTATUS;
+pub extern "NtDll" stdcallcc fn RtlDosPathNameToNtPathName_U(
+ DosPathName: [*]const u16,
+ NtPathName: *UNICODE_STRING,
+ NtFileNamePart: ?*?[*]const u16,
+ DirectoryInfo: ?*CURDIR,
+) BOOL;
+pub extern "NtDll" stdcallcc fn RtlFreeUnicodeString(UnicodeString: *UNICODE_STRING) void;
+
+pub extern "NtDll" stdcallcc fn NtQueryDirectoryFile(
+ FileHandle: HANDLE,
+ Event: ?HANDLE,
+ ApcRoutine: ?IO_APC_ROUTINE,
+ ApcContext: ?*c_void,
+ IoStatusBlock: *IO_STATUS_BLOCK,
+ FileInformation: *c_void,
+ Length: ULONG,
+ FileInformationClass: FILE_INFORMATION_CLASS,
+ ReturnSingleEntry: BOOLEAN,
+ FileName: ?*UNICODE_STRING,
+ RestartScan: BOOLEAN,
+) NTSTATUS;
diff --git a/lib/std/os/windows/status.zig b/lib/std/os/windows/status.zig
index b9fd2b495f..5f38948620 100644
--- a/lib/std/os/windows/status.zig
+++ b/lib/std/os/windows/status.zig
@@ -3,3 +3,11 @@ pub const SUCCESS = 0x00000000;
/// The data was too large to fit into the specified buffer.
pub const BUFFER_OVERFLOW = 0x80000005;
+
+pub const INVALID_PARAMETER = 0xC000000D;
+pub const ACCESS_DENIED = 0xC0000022;
+pub const OBJECT_NAME_INVALID = 0xC0000033;
+pub const OBJECT_NAME_NOT_FOUND = 0xC0000034;
+pub const OBJECT_PATH_NOT_FOUND = 0xC000003A;
+pub const OBJECT_PATH_SYNTAX_BAD = 0xC000003B;
+pub const FILE_IS_A_DIRECTORY = 0xC00000BA;
diff --git a/lib/std/special/test_runner.zig b/lib/std/special/test_runner.zig
index bc57b3b925..2733775001 100644
--- a/lib/std/special/test_runner.zig
+++ b/lib/std/special/test_runner.zig
@@ -16,14 +16,17 @@ pub fn main() anyerror!void {
var test_node = root_node.start(test_fn.name, null);
test_node.activate();
progress.refresh();
+ if (progress.terminal == null) std.debug.warn("{}/{} {}...", i + 1, test_fn_list.len, test_fn.name);
if (test_fn.func()) |_| {
ok_count += 1;
test_node.end();
+ if (progress.terminal == null) std.debug.warn("OK\n");
} else |err| switch (err) {
error.SkipZigTest => {
skip_count += 1;
test_node.end();
progress.log("{}...SKIP\n", test_fn.name);
+ if (progress.terminal == null) std.debug.warn("SKIP\n");
},
else => {
progress.log("");
@@ -32,7 +35,9 @@ pub fn main() anyerror!void {
}
}
root_node.end();
- if (ok_count != test_fn_list.len) {
+ if (ok_count == test_fn_list.len) {
+ std.debug.warn("All tests passed.\n");
+ } else {
std.debug.warn("{} passed; {} skipped.\n", ok_count, skip_count);
}
}