commit 6df35c5877c1a1060dcfd28d5d4092638e6e6754 (tree)
parent cf15b4c2690ae7884e7bdb9259e6497c18213d35
Author: Andrew Kelley <andrew@ziglang.org>
Date: Sun, 2 Aug 2020 17:41:47 +0000
Merge pull request #5960 from kubkon/windows-dir-refactor
Refactor out file and dir creation routines in Windows
Diffstat:
8 files changed, 438 insertions(+), 417 deletions(-)
diff --git a/lib/std/child_process.zig b/lib/std/child_process.zig
@@ -364,6 +364,7 @@ pub const ChildProcess = struct {
error.FileTooBig => unreachable,
error.DeviceBusy => unreachable,
error.FileLocksNotSupported => unreachable,
+ error.BadPathName => unreachable, // Windows-only
else => |e| return e,
}
else
@@ -480,25 +481,20 @@ pub const ChildProcess = struct {
const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore);
- // TODO use CreateFileW here since we are using a string literal for the path
const nul_handle = if (any_ignore)
- windows.CreateFile(
- "NUL",
- windows.GENERIC_READ,
- windows.FILE_SHARE_READ,
- null,
- windows.OPEN_EXISTING,
- windows.FILE_ATTRIBUTE_NORMAL,
- null,
- ) catch |err| switch (err) {
- error.SharingViolation => unreachable, // not possible for "NUL"
+ windows.OpenFile(&[_]u16{ 'N', 'U', 'L' }, .{
+ .dir = std.fs.cwd().fd,
+ .access_mask = windows.GENERIC_READ | windows.SYNCHRONIZE,
+ .share_access = windows.FILE_SHARE_READ,
+ .creation = windows.OPEN_EXISTING,
+ .io_mode = .blocking,
+ }) catch |err| switch (err) {
error.PathAlreadyExists => unreachable, // not possible for "NUL"
error.PipeBusy => unreachable, // not possible for "NUL"
- error.InvalidUtf8 => unreachable, // not possible for "NUL"
- error.BadPathName => unreachable, // not possible for "NUL"
error.FileNotFound => unreachable, // not possible for "NUL"
error.AccessDenied => unreachable, // not possible for "NUL"
error.NameTooLong => unreachable, // not possible for "NUL"
+ error.WouldBlock => unreachable, // not possible for "NUL"
else => |e| return e,
}
else
diff --git a/lib/std/fs.zig b/lib/std/fs.zig
@@ -225,8 +225,7 @@ pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void {
/// Same as `makeDirAbsolute` except the parameter is a null-terminated WTF-16 encoded string.
pub fn makeDirAbsoluteW(absolute_path_w: [*:0]const u16) !void {
assert(path.isAbsoluteWindowsW(absolute_path_w));
- const handle = try os.windows.CreateDirectoryW(null, absolute_path_w, null);
- os.windows.CloseHandle(handle);
+ return os.mkdirW(absolute_path_w, default_new_dir_mode);
}
pub const deleteDir = @compileError("deprecated; use dir.deleteDir or deleteDirAbsolute");
@@ -881,8 +880,7 @@ pub const Dir = struct {
}
pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) !void {
- const handle = try os.windows.CreateDirectoryW(self.fd, sub_path, null);
- os.windows.CloseHandle(handle);
+ try os.mkdiratW(self.fd, sub_path, default_new_dir_mode);
}
/// Calls makeDir recursively to make an entire path. Returns success if the path
@@ -1119,7 +1117,7 @@ pub const Dir = struct {
pub fn deleteFile(self: Dir, sub_path: []const u8) DeleteFileError!void {
if (builtin.os.tag == .windows) {
const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
- return self.deleteFileW(sub_path_w.span().ptr);
+ return self.deleteFileW(sub_path_w.span());
} else if (builtin.os.tag == .wasi) {
os.unlinkatWasi(self.fd, sub_path, 0) catch |err| switch (err) {
error.DirNotEmpty => unreachable, // not passing AT_REMOVEDIR
@@ -1153,7 +1151,7 @@ pub const Dir = struct {
}
/// Same as `deleteFile` except the parameter is WTF-16 encoded.
- pub fn deleteFileW(self: Dir, sub_path_w: [*:0]const u16) DeleteFileError!void {
+ pub fn deleteFileW(self: Dir, sub_path_w: []const u16) DeleteFileError!void {
os.unlinkatW(self.fd, sub_path_w, 0) catch |err| switch (err) {
error.DirNotEmpty => unreachable, // not passing AT_REMOVEDIR
else => |e| return e,
@@ -1182,7 +1180,7 @@ pub const Dir = struct {
pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void {
if (builtin.os.tag == .windows) {
const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
- return self.deleteDirW(sub_path_w.span().ptr);
+ return self.deleteDirW(sub_path_w.span());
} else if (builtin.os.tag == .wasi) {
os.unlinkat(self.fd, sub_path, os.AT_REMOVEDIR) catch |err| switch (err) {
error.IsDir => unreachable, // not possible since we pass AT_REMOVEDIR
@@ -1204,7 +1202,7 @@ pub const Dir = struct {
/// Same as `deleteDir` except the parameter is UTF16LE, NT prefixed.
/// This function is Windows-only.
- pub fn deleteDirW(self: Dir, sub_path_w: [*:0]const u16) DeleteDirError!void {
+ 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,
@@ -1263,11 +1261,11 @@ pub const Dir = struct {
/// are null-terminated, WTF16 encoded.
pub fn symLinkW(
self: Dir,
- target_path_w: [:0]const u16,
- sym_link_path_w: [:0]const u16,
+ target_path_w: []const u16,
+ sym_link_path_w: []const u16,
flags: SymLinkFlags,
) !void {
- return os.windows.CreateSymbolicLinkW(self.fd, sym_link_path_w, target_path_w, flags.is_directory);
+ return os.windows.CreateSymbolicLink(self.fd, sym_link_path_w, target_path_w, flags.is_directory);
}
/// Read value of a symbolic link.
@@ -1278,7 +1276,8 @@ pub const Dir = struct {
return self.readLinkWasi(sub_path, buffer);
}
if (builtin.os.tag == .windows) {
- return os.windows.ReadLink(self.fd, sub_path, buffer);
+ const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
+ return self.readLinkW(sub_path_w.span(), buffer);
}
const sub_path_c = try os.toPosixPath(sub_path);
return self.readLinkZ(&sub_path_c, buffer);
@@ -1295,15 +1294,15 @@ pub const Dir = struct {
pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 {
if (builtin.os.tag == .windows) {
const sub_path_w = try os.windows.cStrToPrefixedFileW(sub_path_c);
- return self.readLinkW(sub_path_w, buffer);
+ return self.readLinkW(sub_path_w.span(), buffer);
}
return os.readlinkatZ(self.fd, sub_path_c, buffer);
}
/// Windows-only. Same as `readLink` except the pathname parameter
/// is null-terminated, WTF16 encoded.
- pub fn readLinkW(self: Dir, sub_path_w: [*:0]const u16, buffer: []u8) ![]u8 {
- return os.windows.ReadLinkW(self.fd, sub_path_w, buffer);
+ pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u8) ![]u8 {
+ return os.windows.ReadLink(self.fd, sub_path_w, buffer);
}
/// On success, caller owns returned buffer.
@@ -1813,7 +1812,9 @@ pub fn symLinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags
assert(path.isAbsolute(target_path));
assert(path.isAbsolute(sym_link_path));
if (builtin.os.tag == .windows) {
- return os.windows.CreateSymbolicLink(null, sym_link_path, target_path, flags.is_directory);
+ const target_path_w = try os.windows.sliceToPrefixedFileW(target_path);
+ const sym_link_path_w = try os.windows.sliceToPrefixedFileW(sym_link_path);
+ return os.windows.CreateSymbolicLink(null, sym_link_path_w.span(), target_path_w.span(), flags.is_directory);
}
return os.symlink(target_path, sym_link_path);
}
@@ -1822,10 +1823,10 @@ pub fn symLinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags
/// Note that this function will by default try creating a symbolic link to a file. If you would
/// like to create a symbolic link to a directory, specify this with `SymLinkFlags{ .is_directory = true }`.
/// See also `symLinkAbsolute`, `symLinkAbsoluteZ`.
-pub fn symLinkAbsoluteW(target_path_w: [:0]const u16, sym_link_path_w: [:0]const u16, flags: SymLinkFlags) !void {
- assert(path.isAbsoluteWindowsW(target_path_w));
- assert(path.isAbsoluteWindowsW(sym_link_path_w));
- return os.windows.CreateSymbolicLinkW(null, sym_link_path_w, target_path_w, flags.is_directory);
+pub fn symLinkAbsoluteW(target_path_w: []const u16, sym_link_path_w: []const u16, flags: SymLinkFlags) !void {
+ assert(path.isAbsoluteWindowsWTF16(target_path_w));
+ assert(path.isAbsoluteWindowsWTF16(sym_link_path_w));
+ return os.windows.CreateSymbolicLink(null, sym_link_path_w, target_path_w, flags.is_directory);
}
/// Same as `symLinkAbsolute` except the parameters are null-terminated pointers.
@@ -1836,7 +1837,7 @@ pub fn symLinkAbsoluteZ(target_path_c: [*:0]const u8, sym_link_path_c: [*:0]cons
if (builtin.os.tag == .windows) {
const target_path_w = try os.windows.cStrToWin32PrefixedFileW(target_path_c);
const sym_link_path_w = try os.windows.cStrToWin32PrefixedFileW(sym_link_path_c);
- return os.windows.CreateSymbolicLinkW(sym_link_path_w.span().ptr, target_path_w.span().ptr, flags.is_directory);
+ return os.windows.CreateSymbolicLink(sym_link_path_w.span(), target_path_w.span(), flags.is_directory);
}
return os.symlinkZ(target_path_c, sym_link_path_c);
}
@@ -1938,7 +1939,20 @@ pub fn walkPath(allocator: *Allocator, dir_path: []const u8) !Walker {
return walker;
}
-pub const OpenSelfExeError = os.OpenError || os.windows.CreateFileError || SelfExePathError || os.FlockError;
+pub const OpenSelfExeError = error{
+ SharingViolation,
+ PathAlreadyExists,
+ FileNotFound,
+ AccessDenied,
+ PipeBusy,
+ NameTooLong,
+ /// On Windows, file paths must be valid Unicode.
+ InvalidUtf8,
+ /// On Windows, file paths cannot contain these characters:
+ /// '/', '*', '?', '"', '<', '>', '|'
+ BadPathName,
+ Unexpected,
+} || os.OpenError || SelfExePathError || os.FlockError;
pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File {
if (builtin.os.tag == .linux) {
diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig
@@ -47,7 +47,20 @@ pub const File = struct {
else => 0o666,
};
- pub const OpenError = windows.CreateFileError || os.OpenError || os.FlockError;
+ pub const OpenError = error{
+ SharingViolation,
+ PathAlreadyExists,
+ FileNotFound,
+ AccessDenied,
+ PipeBusy,
+ NameTooLong,
+ /// On Windows, file paths must be valid Unicode.
+ InvalidUtf8,
+ /// On Windows, file paths cannot contain these characters:
+ /// '/', '*', '?', '"', '<', '>', '|'
+ BadPathName,
+ Unexpected,
+ } || os.OpenError || os.FlockError;
pub const Lock = enum { None, Shared, Exclusive };
diff --git a/lib/std/fs/watch.zig b/lib/std/fs/watch.zig
@@ -374,15 +374,13 @@ pub fn Watch(comptime V: type) type {
defer if (!basename_utf16le_null_consumed) self.allocator.free(basename_utf16le_null);
const basename_utf16le_no_null = basename_utf16le_null[0 .. basename_utf16le_null.len - 1];
- const dir_handle = try windows.CreateFileW(
- dirname_utf16le.ptr,
- windows.FILE_LIST_DIRECTORY,
- windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE | windows.FILE_SHARE_WRITE,
- null,
- windows.OPEN_EXISTING,
- windows.FILE_FLAG_BACKUP_SEMANTICS | windows.FILE_FLAG_OVERLAPPED,
- null,
- );
+ const dir_handle = try windows.OpenFile(dirname_utf16le, .{
+ .dir = std.fs.cwd().fd,
+ .access_mask = windows.FILE_LIST_DIRECTORY,
+ .creation = windows.FILE_OPEN,
+ .io_mode = .blocking,
+ .open_dir = true,
+ });
var dir_handle_consumed = false;
defer if (!dir_handle_consumed) windows.CloseHandle(dir_handle);
diff --git a/lib/std/os.zig b/lib/std/os.zig
@@ -1041,6 +1041,9 @@ pub const OpenError = error{
/// The underlying filesystem does not support file locks
FileLocksNotSupported,
+
+ BadPathName,
+ InvalidUtf8,
} || UnexpectedError;
/// Open and possibly create a file. Keeps trying if it gets interrupted.
@@ -1092,18 +1095,65 @@ pub fn openZ(file_path: [*:0]const u8, flags: u32, perm: mode_t) OpenError!fd_t
}
}
+fn openOptionsFromFlags(flags: u32) windows.OpenFileOptions {
+ const w = windows;
+
+ var access_mask: w.ULONG = w.READ_CONTROL | w.FILE_WRITE_ATTRIBUTES | w.SYNCHRONIZE;
+ if (flags & O_RDWR != 0) {
+ access_mask |= w.GENERIC_READ | w.GENERIC_WRITE;
+ } else if (flags & O_WRONLY != 0) {
+ access_mask |= w.GENERIC_WRITE;
+ } else {
+ access_mask |= w.GENERIC_READ | w.GENERIC_WRITE;
+ }
+
+ const open_dir: bool = flags & O_DIRECTORY != 0;
+ const follow_symlinks: bool = flags & O_NOFOLLOW == 0;
+
+ const creation: w.ULONG = blk: {
+ if (flags & O_CREAT != 0) {
+ if (flags & O_EXCL != 0) {
+ break :blk w.FILE_CREATE;
+ }
+ }
+ break :blk w.FILE_OPEN;
+ };
+
+ return .{
+ .access_mask = access_mask,
+ .io_mode = .blocking,
+ .creation = creation,
+ .open_dir = open_dir,
+ .follow_symlinks = follow_symlinks,
+ };
+}
+
/// Windows-only. The path parameter is
/// [WTF-16](https://simonsapin.github.io/wtf-8/#potentially-ill-formed-utf-16) encoded.
/// Translates the POSIX open API call to a Windows API call.
-pub fn openW(file_path_w: []const u16, flags: u32, perm: usize) OpenError!fd_t {
- @compileError("TODO implement openW for windows");
+/// TODO currently, this function does not handle all flag combinations
+/// or makes use of perm argument.
+pub fn openW(file_path_w: []const u16, flags: u32, perm: mode_t) OpenError!fd_t {
+ var options = openOptionsFromFlags(flags);
+ options.dir = std.fs.cwd().fd;
+ return windows.OpenFile(file_path_w, options) catch |err| switch (err) {
+ error.WouldBlock => unreachable,
+ error.PipeBusy => unreachable,
+ else => |e| return e,
+ };
}
/// Open and possibly create a file. Keeps trying if it gets interrupted.
/// `file_path` is relative to the open directory handle `dir_fd`.
/// See also `openatC`.
-/// TODO support windows
pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: mode_t) OpenError!fd_t {
+ if (builtin.os.tag == .wasi) {
+ @compileError("use openatWasi instead");
+ }
+ if (builtin.os.tag == .windows) {
+ const file_path_w = try windows.sliceToPrefixedFileW(file_path);
+ return openatW(dir_fd, file_path_w.span(), flags, mode);
+ }
const file_path_c = try toPosixPath(file_path);
return openatZ(dir_fd, &file_path_c, flags, mode);
}
@@ -1145,8 +1195,11 @@ pub const openatC = @compileError("deprecated: renamed to openatZ");
/// Open and possibly create a file. Keeps trying if it gets interrupted.
/// `file_path` is relative to the open directory handle `dir_fd`.
/// See also `openat`.
-/// TODO support windows
pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t) OpenError!fd_t {
+ if (builtin.os.tag == .windows) {
+ const file_path_w = try windows.cStrToPrefixedFileW(file_path);
+ return openatW(dir_fd, file_path_w.span(), flags, mode);
+ }
while (true) {
const rc = system.openat(dir_fd, file_path, flags, mode);
switch (errno(rc)) {
@@ -1177,6 +1230,20 @@ pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t)
}
}
+/// Windows-only. Similar to `openat` but with pathname argument null-terminated
+/// WTF16 encoded.
+/// TODO currently, this function does not handle all flag combinations
+/// or makes use of perm argument.
+pub fn openatW(dir_fd: fd_t, file_path_w: []const u16, flags: u32, mode: mode_t) OpenError!fd_t {
+ var options = openOptionsFromFlags(flags);
+ options.dir = dir_fd;
+ return windows.OpenFile(file_path_w, options) catch |err| switch (err) {
+ error.WouldBlock => unreachable,
+ error.PipeBusy => unreachable,
+ else => |e| return e,
+ };
+}
+
pub fn dup2(old_fd: fd_t, new_fd: fd_t) !void {
while (true) {
switch (errno(system.dup2(old_fd, new_fd))) {
@@ -1683,7 +1750,7 @@ pub fn unlink(file_path: []const u8) UnlinkError!void {
@compileError("unlink is not supported in WASI; use unlinkat instead");
} else if (builtin.os.tag == .windows) {
const file_path_w = try windows.sliceToPrefixedFileW(file_path);
- return windows.DeleteFileW(file_path_w.span().ptr);
+ return unlinkW(file_path_w.span());
} else {
const file_path_c = try toPosixPath(file_path);
return unlinkZ(&file_path_c);
@@ -1696,7 +1763,7 @@ pub const unlinkC = @compileError("deprecated: renamed to unlinkZ");
pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void {
if (builtin.os.tag == .windows) {
const file_path_w = try windows.cStrToPrefixedFileW(file_path);
- return windows.DeleteFileW(file_path_w.span().ptr);
+ return unlinkW(file_path_w.span());
}
switch (errno(system.unlink(file_path))) {
0 => return,
@@ -1717,6 +1784,11 @@ pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void {
}
}
+/// Windows-only. Same as `unlink` except the parameter is null-terminated, WTF16 encoded.
+pub fn unlinkW(file_path_w: []const u16) UnlinkError!void {
+ return windows.DeleteFile(file_path_w, .{ .dir = std.fs.cwd().fd });
+}
+
pub const UnlinkatError = UnlinkError || error{
/// When passing `AT_REMOVEDIR`, this error occurs when the named directory is not empty.
DirNotEmpty,
@@ -1727,7 +1799,7 @@ pub const UnlinkatError = UnlinkError || error{
pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void {
if (builtin.os.tag == .windows) {
const file_path_w = try windows.sliceToPrefixedFileW(file_path);
- return unlinkatW(dirfd, file_path_w.span().ptr, flags);
+ return unlinkatW(dirfd, file_path_w.span(), flags);
} else if (builtin.os.tag == .wasi) {
return unlinkatWasi(dirfd, file_path, flags);
} else {
@@ -1774,7 +1846,7 @@ pub fn unlinkatWasi(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatErro
pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatError!void {
if (builtin.os.tag == .windows) {
const file_path_w = try windows.cStrToPrefixedFileW(file_path_c);
- return unlinkatW(dirfd, file_path_w.span().ptr, flags);
+ return unlinkatW(dirfd, file_path_w.span(), flags);
}
switch (errno(system.unlinkat(dirfd, file_path_c, flags))) {
0 => return,
@@ -1800,67 +1872,9 @@ pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatEr
}
/// Same as `unlinkat` but `sub_path_w` is UTF16LE, NT prefixed. Windows only.
-pub fn unlinkatW(dirfd: fd_t, sub_path_w: [*:0]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)
- @as(w.ULONG, w.FILE_DELETE_ON_CLOSE | w.FILE_DIRECTORY_FILE | w.FILE_OPEN_REPARSE_POINT)
- else
- @as(w.ULONG, w.FILE_DELETE_ON_CLOSE | w.FILE_NON_DIRECTORY_FILE | w.FILE_OPEN_REPARSE_POINT); // would we ever want to delete the target instead?
-
- const path_len_bytes = @intCast(u16, mem.lenZ(sub_path_w) * 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 = if (std.fs.path.isAbsoluteWindowsW(sub_path_w)) null else 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 == .SUCCESS) {
- rc = w.ntdll.NtClose(tmp_handle);
- }
- switch (rc) {
- .SUCCESS => return,
- .OBJECT_NAME_INVALID => unreachable,
- .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
- .INVALID_PARAMETER => unreachable,
- .FILE_IS_A_DIRECTORY => return error.IsDir,
- .NOT_A_DIRECTORY => return error.NotDir,
- else => return w.unexpectedStatus(rc),
- }
+pub fn unlinkatW(dirfd: fd_t, sub_path_w: []const u16, flags: u32) UnlinkatError!void {
+ const remove_dir = (flags & AT_REMOVEDIR) != 0;
+ return windows.DeleteFile(sub_path_w, .{ .dir = dirfd, .remove_dir = remove_dir });
}
const RenameError = error{
@@ -2087,7 +2101,7 @@ pub fn renameatW(
pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void {
if (builtin.os.tag == .windows) {
const sub_dir_path_w = try windows.sliceToPrefixedFileW(sub_dir_path);
- return mkdiratW(dir_fd, sub_dir_path_w.span().ptr, mode);
+ return mkdiratW(dir_fd, sub_dir_path_w.span(), mode);
} else if (builtin.os.tag == .wasi) {
return mkdiratWasi(dir_fd, sub_dir_path, mode);
} else {
@@ -2145,8 +2159,19 @@ pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirErr
}
}
-pub fn mkdiratW(dir_fd: fd_t, sub_path_w: [*:0]const u16, mode: u32) MakeDirError!void {
- const sub_dir_handle = try windows.CreateDirectoryW(dir_fd, sub_path_w, null);
+pub fn mkdiratW(dir_fd: fd_t, sub_path_w: []const u16, mode: u32) MakeDirError!void {
+ const sub_dir_handle = windows.OpenFile(sub_path_w, .{
+ .dir = dir_fd,
+ .access_mask = windows.GENERIC_READ | windows.SYNCHRONIZE,
+ .creation = windows.FILE_CREATE,
+ .io_mode = .blocking,
+ .open_dir = true,
+ }) catch |err| switch (err) {
+ error.IsDir => unreachable,
+ error.PipeBusy => unreachable,
+ error.WouldBlock => unreachable,
+ else => |e| return e,
+ };
windows.CloseHandle(sub_dir_handle);
}
@@ -2175,9 +2200,8 @@ pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
if (builtin.os.tag == .wasi) {
@compileError("mkdir is not supported in WASI; use mkdirat instead");
} else if (builtin.os.tag == .windows) {
- const sub_dir_handle = try windows.CreateDirectory(null, dir_path, null);
- windows.CloseHandle(sub_dir_handle);
- return;
+ const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);
+ return mkdirW(dir_path_w.span(), mode);
} else {
const dir_path_c = try toPosixPath(dir_path);
return mkdirZ(&dir_path_c, mode);
@@ -2188,9 +2212,7 @@ pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
if (builtin.os.tag == .windows) {
const dir_path_w = try windows.cStrToPrefixedFileW(dir_path);
- const sub_dir_handle = try windows.CreateDirectoryW(null, dir_path_w.span().ptr, null);
- windows.CloseHandle(sub_dir_handle);
- return;
+ return mkdirW(dir_path_w.span(), mode);
}
switch (errno(system.mkdir(dir_path, mode))) {
0 => return,
@@ -2211,6 +2233,23 @@ pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
}
}
+/// Windows-only. Same as `mkdir` but the parameters is WTF16 encoded.
+pub fn mkdirW(dir_path_w: []const u16, mode: u32) MakeDirError!void {
+ const sub_dir_handle = windows.OpenFile(dir_path_w, .{
+ .dir = std.fs.cwd().fd,
+ .access_mask = windows.GENERIC_READ | windows.SYNCHRONIZE,
+ .creation = windows.FILE_CREATE,
+ .io_mode = .blocking,
+ .open_dir = true,
+ }) catch |err| switch (err) {
+ error.IsDir => unreachable,
+ error.PipeBusy => unreachable,
+ error.WouldBlock => unreachable,
+ else => |e| return e,
+ };
+ windows.CloseHandle(sub_dir_handle);
+}
+
pub const DeleteDirError = error{
AccessDenied,
FileBusy,
@@ -2231,7 +2270,7 @@ pub fn rmdir(dir_path: []const u8) DeleteDirError!void {
@compileError("rmdir is not supported in WASI; use unlinkat instead");
} else if (builtin.os.tag == .windows) {
const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);
- return windows.RemoveDirectoryW(dir_path_w.span().ptr);
+ return rmdirW(dir_path_w.span());
} else {
const dir_path_c = try toPosixPath(dir_path);
return rmdirZ(&dir_path_c);
@@ -2244,7 +2283,7 @@ pub const rmdirC = @compileError("deprecated: renamed to rmdirZ");
pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void {
if (builtin.os.tag == .windows) {
const dir_path_w = try windows.cStrToPrefixedFileW(dir_path);
- return windows.RemoveDirectoryW(dir_path_w.span().ptr);
+ return rmdirW(dir_path_w.span());
}
switch (errno(system.rmdir(dir_path))) {
0 => return,
@@ -2265,6 +2304,14 @@ pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void {
}
}
+/// Windows-only. Same as `rmdir` except the parameter is WTF16 encoded.
+pub fn rmdirW(dir_path_w: []const u16) DeleteDirError!void {
+ return windows.DeleteFile(dir_path_w, .{ .dir = std.fs.cwd().fd, .remove_dir = true }) catch |err| switch (err) {
+ error.IsDir => unreachable,
+ else => |e| return e,
+ };
+}
+
pub const ChangeCurDirError = error{
AccessDenied,
FileSystem,
@@ -2354,7 +2401,8 @@ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
if (builtin.os.tag == .wasi) {
@compileError("readlink is not supported in WASI; use readlinkat instead");
} else if (builtin.os.tag == .windows) {
- return windows.ReadLink(std.fs.cwd().fd, file_path, out_buffer);
+ const file_path_w = try windows.sliceToPrefixedFileW(file_path);
+ return readlinkW(file_path_w.span(), out_buffer);
} else {
const file_path_c = try toPosixPath(file_path);
return readlinkZ(&file_path_c, out_buffer);
@@ -2363,17 +2411,17 @@ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
pub const readlinkC = @compileError("deprecated: renamed to readlinkZ");
-/// Windows-only. Same as `readlink` except `file_path` is null-terminated, WTF16 encoded.
+/// Windows-only. Same as `readlink` except `file_path` is WTF16 encoded.
/// See also `readlinkZ`.
-pub fn readlinkW(file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 {
- return windows.ReadLinkW(std.fs.cwd().fd, file_path, out_buffer);
+pub fn readlinkW(file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 {
+ return windows.ReadLink(std.fs.cwd().fd, file_path, out_buffer);
}
/// Same as `readlink` except `file_path` is null-terminated.
pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 {
if (builtin.os.tag == .windows) {
const file_path_w = try windows.cStrToWin32PrefixedFileW(file_path);
- return readlinkW(file_path_w.span().ptr, out_buffer);
+ return readlinkW(file_path_w.span(), out_buffer);
}
const rc = system.readlink(file_path, out_buffer.ptr, out_buffer.len);
switch (errno(rc)) {
@@ -2399,7 +2447,8 @@ pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLink
return readlinkatWasi(dirfd, file_path, out_buffer);
}
if (builtin.os.tag == .windows) {
- return windows.ReadLink(dirfd, file_path, out_buffer);
+ const file_path_w = try windows.sliceToPrefixedFileW(file_path);
+ return readlinkatW(dirfd, file_path_w.span(), out_buffer);
}
const file_path_c = try toPosixPath(file_path);
return readlinkatZ(dirfd, &file_path_c, out_buffer);
@@ -2429,8 +2478,8 @@ pub fn readlinkatWasi(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) Read
/// Windows-only. Same as `readlinkat` except `file_path` is null-terminated, WTF16 encoded.
/// See also `readlinkat`.
-pub fn readlinkatW(dirfd: fd_t, file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 {
- return windows.ReadLinkW(dirfd, file_path, out_buffer);
+pub fn readlinkatW(dirfd: fd_t, file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 {
+ return windows.ReadLink(dirfd, file_path, out_buffer);
}
/// Same as `readlinkat` except `file_path` is null-terminated.
@@ -2438,7 +2487,7 @@ pub fn readlinkatW(dirfd: fd_t, file_path: [*:0]const u16, out_buffer: []u8) Rea
pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 {
if (builtin.os.tag == .windows) {
const file_path_w = try windows.cStrToPrefixedFileW(file_path);
- return readlinkatW(dirfd, file_path_w.span().ptr, out_buffer);
+ return readlinkatW(dirfd, file_path_w.span(), out_buffer);
}
const rc = system.readlinkat(dirfd, file_path, out_buffer.ptr, out_buffer.len);
switch (errno(rc)) {
@@ -3959,7 +4008,7 @@ pub const RealPathError = error{
pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
if (builtin.os.tag == .windows) {
const pathname_w = try windows.sliceToPrefixedFileW(pathname);
- return realpathW(pathname_w.span().ptr, out_buffer);
+ return realpathW(pathname_w.span(), out_buffer);
}
if (builtin.os.tag == .wasi) {
@compileError("Use std.fs.wasi.PreopenList to obtain valid Dir handles instead of using absolute paths");
@@ -3974,7 +4023,7 @@ pub const realpathC = @compileError("deprecated: renamed realpathZ");
pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
if (builtin.os.tag == .windows) {
const pathname_w = try windows.cStrToPrefixedFileW(pathname);
- return realpathW(pathname_w.span().ptr, out_buffer);
+ return realpathW(pathname_w.span(), out_buffer);
}
if (builtin.os.tag == .linux and !builtin.link_libc) {
const fd = openZ(pathname, linux.O_PATH | linux.O_NONBLOCK | linux.O_CLOEXEC, 0) catch |err| switch (err) {
@@ -4010,22 +4059,43 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP
return mem.spanZ(result_path);
}
-/// Same as `realpath` except `pathname` is null-terminated and UTF16LE-encoded.
-/// TODO use ntdll for better semantics
-pub fn realpathW(pathname: [*:0]const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
- const h_file = try windows.CreateFileW(
- pathname,
- windows.GENERIC_READ,
- windows.FILE_SHARE_READ,
- null,
- windows.OPEN_EXISTING,
- windows.FILE_FLAG_BACKUP_SEMANTICS,
- null,
- );
- defer windows.CloseHandle(h_file);
+/// Same as `realpath` except `pathname` is UTF16LE-encoded.
+/// TODO use ntdll to emulate `GetFinalPathNameByHandleW` routine
+pub fn realpathW(pathname: []const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
+ const w = windows;
+
+ const dir = std.fs.cwd().fd;
+ const access_mask = w.GENERIC_READ | w.SYNCHRONIZE;
+ const share_access = w.FILE_SHARE_READ;
+ const creation = w.FILE_OPEN;
+ const h_file = blk: {
+ const res = w.OpenFile(pathname, .{
+ .dir = dir,
+ .access_mask = access_mask,
+ .share_access = share_access,
+ .creation = creation,
+ .io_mode = .blocking,
+ }) catch |err| switch (err) {
+ error.IsDir => break :blk w.OpenFile(pathname, .{
+ .dir = dir,
+ .access_mask = access_mask,
+ .share_access = share_access,
+ .creation = creation,
+ .io_mode = .blocking,
+ .open_dir = true,
+ }) catch |er| switch (er) {
+ error.WouldBlock => unreachable,
+ else => |e2| return e2,
+ },
+ error.WouldBlock => unreachable,
+ else => |e| return e,
+ };
+ break :blk res;
+ };
+ defer w.CloseHandle(h_file);
- var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined;
- const wide_slice = try windows.GetFinalPathNameByHandleW(h_file, &wide_buf, wide_buf.len, windows.VOLUME_NAME_DOS);
+ var wide_buf: [w.PATH_MAX_WIDE]u16 = undefined;
+ const wide_slice = try w.GetFinalPathNameByHandleW(h_file, &wide_buf, wide_buf.len, w.VOLUME_NAME_DOS);
// Windows returns \\?\ prepended to the path.
// We strip it to make this function consistent across platforms.
diff --git a/lib/std/os/bits/windows.zig b/lib/std/os/bits/windows.zig
@@ -237,3 +237,28 @@ pub const IPPROTO_TCP = ws2_32.IPPROTO_TCP;
pub const IPPROTO_UDP = ws2_32.IPPROTO_UDP;
pub const IPPROTO_ICMPV6 = ws2_32.IPPROTO_ICMPV6;
pub const IPPROTO_RM = ws2_32.IPPROTO_RM;
+
+pub const O_RDONLY = 0o0;
+pub const O_WRONLY = 0o1;
+pub const O_RDWR = 0o2;
+
+pub const O_CREAT = 0o100;
+pub const O_EXCL = 0o200;
+pub const O_NOCTTY = 0o400;
+pub const O_TRUNC = 0o1000;
+pub const O_APPEND = 0o2000;
+pub const O_NONBLOCK = 0o4000;
+pub const O_DSYNC = 0o10000;
+pub const O_SYNC = 0o4010000;
+pub const O_RSYNC = 0o4010000;
+pub const O_DIRECTORY = 0o200000;
+pub const O_NOFOLLOW = 0o400000;
+pub const O_CLOEXEC = 0o2000000;
+
+pub const O_ASYNC = 0o20000;
+pub const O_DIRECT = 0o40000;
+pub const O_LARGEFILE = 0;
+pub const O_NOATIME = 0o1000000;
+pub const O_PATH = 0o10000000;
+pub const O_TMPFILE = 0o20200000;
+pub const O_NDELAY = O_NONBLOCK;
+\ No newline at end of file
diff --git a/lib/std/os/test.zig b/lib/std/os/test.zig
@@ -3,6 +3,7 @@ const os = std.os;
const testing = std.testing;
const expect = testing.expect;
const expectEqual = testing.expectEqual;
+const expectError = testing.expectError;
const io = std.io;
const fs = std.fs;
const mem = std.mem;
@@ -19,6 +20,95 @@ const tmpDir = std.testing.tmpDir;
const Dir = std.fs.Dir;
const ArenaAllocator = std.heap.ArenaAllocator;
+test "open smoke test" {
+ if (builtin.os.tag == .wasi) return error.SkipZigTest;
+
+ // TODO verify file attributes using `fstat`
+
+ var tmp = tmpDir(.{});
+ defer tmp.cleanup();
+
+ // Get base abs path
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+
+ const base_path = blk: {
+ const relative_path = try fs.path.join(&arena.allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] });
+ break :blk try fs.realpathAlloc(&arena.allocator, relative_path);
+ };
+
+ var file_path: []u8 = undefined;
+ var fd: os.fd_t = undefined;
+ const mode: os.mode_t = if (builtin.os.tag == .windows) 0 else 0o666;
+
+ // Create some file using `open`.
+ file_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "some_file" });
+ fd = try os.open(file_path, os.O_RDWR | os.O_CREAT | os.O_EXCL, mode);
+ os.close(fd);
+
+ // Try this again with the same flags. This op should fail with error.PathAlreadyExists.
+ file_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "some_file" });
+ expectError(error.PathAlreadyExists, os.open(file_path, os.O_RDWR | os.O_CREAT | os.O_EXCL, mode));
+
+ // Try opening without `O_EXCL` flag.
+ file_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "some_file" });
+ fd = try os.open(file_path, os.O_RDWR | os.O_CREAT, mode);
+ os.close(fd);
+
+ // Try opening as a directory which should fail.
+ file_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "some_file" });
+ expectError(error.NotDir, os.open(file_path, os.O_RDWR | os.O_DIRECTORY, mode));
+
+ // Create some directory
+ file_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "some_dir" });
+ try os.mkdir(file_path, mode);
+
+ // Open dir using `open`
+ file_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "some_dir" });
+ fd = try os.open(file_path, os.O_RDONLY | os.O_DIRECTORY, mode);
+ os.close(fd);
+
+ // Try opening as file which should fail.
+ file_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "some_dir" });
+ expectError(error.IsDir, os.open(file_path, os.O_RDWR, mode));
+}
+
+test "openat smoke test" {
+ if (builtin.os.tag == .wasi) return error.SkipZigTest;
+
+ // TODO verify file attributes using `fstatat`
+
+ var tmp = tmpDir(.{});
+ defer tmp.cleanup();
+
+ var fd: os.fd_t = undefined;
+ const mode: os.mode_t = if (builtin.os.tag == .windows) 0 else 0o666;
+
+ // Create some file using `openat`.
+ fd = try os.openat(tmp.dir.fd, "some_file", os.O_RDWR | os.O_CREAT | os.O_EXCL, mode);
+ os.close(fd);
+
+ // Try this again with the same flags. This op should fail with error.PathAlreadyExists.
+ expectError(error.PathAlreadyExists, os.openat(tmp.dir.fd, "some_file", os.O_RDWR | os.O_CREAT | os.O_EXCL, mode));
+
+ // Try opening without `O_EXCL` flag.
+ fd = try os.openat(tmp.dir.fd, "some_file", os.O_RDWR | os.O_CREAT, mode);
+ os.close(fd);
+
+ // Try opening as a directory which should fail.
+ expectError(error.NotDir, os.openat(tmp.dir.fd, "some_file", os.O_RDWR | os.O_DIRECTORY, mode));
+
+ // Create some directory
+ try os.mkdirat(tmp.dir.fd, "some_dir", mode);
+
+ // Open dir using `open`
+ fd = try os.openat(tmp.dir.fd, "some_dir", os.O_RDONLY | os.O_DIRECTORY, mode);
+ os.close(fd);
+
+ // Try opening as file which should fail.
+ expectError(error.IsDir, os.openat(tmp.dir.fd, "some_dir", os.O_RDWR, mode));
+}
+
test "symlink with relative paths" {
if (builtin.os.tag == .wasi) return error.SkipZigTest;
@@ -27,7 +117,7 @@ test "symlink with relative paths" {
try cwd.writeFile("file.txt", "nonsense");
if (builtin.os.tag == .windows) {
- try os.windows.CreateSymbolicLink(cwd.fd, "symlinked", "file.txt", false);
+ try os.windows.CreateSymbolicLink(cwd.fd, &[_]u16{ 's', 'y', 'm', 'l', 'i', 'n', 'k', 'e', 'd' }, &[_]u16{ 'f', 'i', 'l', 'e', '.', 't', 'x', 't' }, false);
} else {
try os.symlink("file.txt", "symlinked");
}
@@ -85,7 +175,7 @@ test "readlinkat" {
// create a symbolic link
if (builtin.os.tag == .windows) {
- try os.windows.CreateSymbolicLink(tmp.dir.fd, "link", "file.txt", false);
+ try os.windows.CreateSymbolicLink(tmp.dir.fd, &[_]u16{ 'l', 'i', 'n', 'k' }, &[_]u16{ 'f', 'i', 'l', 'e', '.', 't', 'x', 't' }, false);
} else {
try os.symlinkat("file.txt", tmp.dir.fd, "link");
}
diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig
@@ -25,76 +25,11 @@ pub usingnamespace @import("windows/bits.zig");
pub const self_process_handle = @intToPtr(HANDLE, maxInt(usize));
-pub const CreateFileError = error{
- SharingViolation,
- 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,
-
- AccessDenied,
- PipeBusy,
- NameTooLong,
-
- /// On Windows, file paths must be valid Unicode.
- InvalidUtf8,
-
- /// On Windows, file paths cannot contain these characters:
- /// '/', '*', '?', '"', '<', '>', '|'
- BadPathName,
-
- Unexpected,
-};
-
-pub fn CreateFile(
- file_path: []const u8,
- desired_access: DWORD,
- share_mode: DWORD,
- lpSecurityAttributes: ?LPSECURITY_ATTRIBUTES,
- creation_disposition: DWORD,
- flags_and_attrs: DWORD,
- hTemplateFile: ?HANDLE,
-) CreateFileError!HANDLE {
- const file_path_w = try sliceToPrefixedFileW(file_path);
- return CreateFileW(file_path_w.span().ptr, desired_access, share_mode, lpSecurityAttributes, creation_disposition, flags_and_attrs, hTemplateFile);
-}
-
-pub fn CreateFileW(
- file_path_w: [*:0]const u16,
- desired_access: DWORD,
- share_mode: DWORD,
- lpSecurityAttributes: ?LPSECURITY_ATTRIBUTES,
- creation_disposition: DWORD,
- flags_and_attrs: DWORD,
- hTemplateFile: ?HANDLE,
-) CreateFileError!HANDLE {
- const result = kernel32.CreateFileW(file_path_w, desired_access, share_mode, lpSecurityAttributes, creation_disposition, flags_and_attrs, hTemplateFile);
-
- if (result == INVALID_HANDLE_VALUE) {
- switch (kernel32.GetLastError()) {
- .SHARING_VIOLATION => return error.SharingViolation,
- .ALREADY_EXISTS => return error.PathAlreadyExists,
- .FILE_EXISTS => return error.PathAlreadyExists,
- .FILE_NOT_FOUND => return error.FileNotFound,
- .PATH_NOT_FOUND => return error.FileNotFound,
- .ACCESS_DENIED => return error.AccessDenied,
- .PIPE_BUSY => return error.PipeBusy,
- .FILENAME_EXCED_RANGE => return error.NameTooLong,
- else => |err| return unexpectedError(err),
- }
- }
-
- return result;
-}
-
pub const OpenError = error{
IsDir,
+ NotDir,
FileNotFound,
NoDevice,
- SharingViolation,
AccessDenied,
PipeBusy,
PathAlreadyExists,
@@ -111,15 +46,21 @@ pub const OpenFileOptions = struct {
share_access_nonblocking: bool = false,
creation: ULONG,
io_mode: std.io.ModeOverride,
+ /// If true, tries to open path as a directory.
+ /// Defaults to false.
+ open_dir: bool = false,
+ /// If false, tries to open path as a reparse point without dereferencing it.
+ /// Defaults to true.
+ follow_symlinks: bool = true,
};
/// TODO when share_access_nonblocking is false, this implementation uses
/// untinterruptible sleep() to block. This is not the final iteration of the API.
pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HANDLE {
- if (mem.eql(u16, sub_path_w, &[_]u16{'.'})) {
+ if (mem.eql(u16, sub_path_w, &[_]u16{'.'}) and !options.open_dir) {
return error.IsDir;
}
- if (mem.eql(u16, sub_path_w, &[_]u16{ '.', '.' })) {
+ if (mem.eql(u16, sub_path_w, &[_]u16{ '.', '.' }) and !options.open_dir) {
return error.IsDir;
}
@@ -142,11 +83,13 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
.SecurityQualityOfService = null,
};
var io: IO_STATUS_BLOCK = undefined;
+ const blocking_flag: ULONG = if (options.io_mode == .blocking) FILE_SYNCHRONOUS_IO_NONALERT else 0;
+ const file_or_dir_flag: ULONG = if (options.open_dir) FILE_DIRECTORY_FILE else FILE_NON_DIRECTORY_FILE;
+ // If we're not following symlinks, we need to ensure we don't pass in any synchronization flags such as FILE_SYNCHRONOUS_IO_NONALERT.
+ const flags: ULONG = if (options.follow_symlinks) file_or_dir_flag | blocking_flag else file_or_dir_flag | FILE_OPEN_REPARSE_POINT;
var delay: usize = 1;
while (true) {
- var flags: ULONG = undefined;
- const blocking_flag: ULONG = if (options.io_mode == .blocking) FILE_SYNCHRONOUS_IO_NONALERT else 0;
const rc = ntdll.NtCreateFile(
&result,
options.access_mask,
@@ -156,7 +99,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
FILE_ATTRIBUTE_NORMAL,
options.share_access,
options.creation,
- FILE_NON_DIRECTORY_FILE | blocking_flag,
+ flags,
null,
0,
);
@@ -184,6 +127,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
.OBJECT_PATH_SYNTAX_BAD => unreachable,
.OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
.FILE_IS_A_DIRECTORY => return error.IsDir,
+ .NOT_A_DIRECTORY => return error.NotDir,
else => return unexpectedStatus(rc),
}
}
@@ -638,27 +582,14 @@ pub const CreateSymbolicLinkError = error{
PathAlreadyExists,
FileNotFound,
NameTooLong,
- InvalidUtf8,
- BadPathName,
NoDevice,
Unexpected,
};
pub fn CreateSymbolicLink(
dir: ?HANDLE,
- sym_link_path: []const u8,
- target_path: []const u8,
- is_directory: bool,
-) CreateSymbolicLinkError!void {
- const sym_link_path_w = try sliceToPrefixedFileW(sym_link_path);
- const target_path_w = try sliceToPrefixedFileW(target_path);
- return CreateSymbolicLinkW(dir, sym_link_path_w.span(), target_path_w.span(), is_directory);
-}
-
-pub fn CreateSymbolicLinkW(
- dir: ?HANDLE,
- sym_link_path: [:0]const u16,
- target_path: [:0]const u16,
+ sym_link_path: []const u16,
+ target_path: []const u16,
is_directory: bool,
) CreateSymbolicLinkError!void {
const SYMLINK_DATA = extern struct {
@@ -672,71 +603,19 @@ pub fn CreateSymbolicLinkW(
Flags: ULONG,
};
- var symlink_handle: HANDLE = undefined;
- if (is_directory) {
- const sym_link_len_bytes = math.cast(u16, sym_link_path.len * 2) catch |err| switch (err) {
- error.Overflow => return error.NameTooLong,
- };
- var nt_name = UNICODE_STRING{
- .Length = sym_link_len_bytes,
- .MaximumLength = sym_link_len_bytes,
- .Buffer = @intToPtr([*]u16, @ptrToInt(sym_link_path.ptr)),
- };
-
- if (sym_link_path[0] == '.' and sym_link_path[1] == 0) {
- // Windows does not recognize this, but it does work with empty string.
- nt_name.Length = 0;
- }
-
- var attr = OBJECT_ATTRIBUTES{
- .Length = @sizeOf(OBJECT_ATTRIBUTES),
- .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sym_link_path)) null else dir,
- .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
- .ObjectName = &nt_name,
- .SecurityDescriptor = null,
- .SecurityQualityOfService = null,
- };
-
- var io: IO_STATUS_BLOCK = undefined;
- const rc = ntdll.NtCreateFile(
- &symlink_handle,
- GENERIC_READ | SYNCHRONIZE | FILE_WRITE_ATTRIBUTES,
- &attr,
- &io,
- null,
- FILE_ATTRIBUTE_NORMAL,
- FILE_SHARE_READ,
- FILE_CREATE,
- FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT,
- null,
- 0,
- );
- switch (rc) {
- .SUCCESS => {},
- .OBJECT_NAME_INVALID => unreachable,
- .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
- .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
- .NO_MEDIA_IN_DEVICE => return error.NoDevice,
- .INVALID_PARAMETER => unreachable,
- .ACCESS_DENIED => return error.AccessDenied,
- .OBJECT_PATH_SYNTAX_BAD => unreachable,
- .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
- else => return unexpectedStatus(rc),
- }
- } else {
- symlink_handle = OpenFile(sym_link_path, .{
- .access_mask = SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE,
- .dir = dir,
- .creation = FILE_CREATE,
- .io_mode = .blocking,
- }) catch |err| switch (err) {
- error.WouldBlock => unreachable,
- error.IsDir => return error.PathAlreadyExists,
- error.PipeBusy => unreachable,
- error.SharingViolation => return error.AccessDenied,
- else => |e| return e,
- };
- }
+ const symlink_handle = OpenFile(sym_link_path, .{
+ .access_mask = SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE,
+ .dir = dir,
+ .creation = FILE_CREATE,
+ .io_mode = .blocking,
+ .open_dir = is_directory,
+ }) catch |err| switch (err) {
+ error.IsDir => return error.PathAlreadyExists,
+ error.NotDir => unreachable,
+ error.WouldBlock => unreachable,
+ error.PipeBusy => unreachable,
+ else => |e| return e,
+ };
defer CloseHandle(symlink_handle);
// prepare reparse data buffer
@@ -767,44 +646,32 @@ pub const ReadLinkError = error{
Unexpected,
NameTooLong,
UnsupportedReparsePointType,
- InvalidUtf8,
- BadPathName,
};
-pub fn ReadLink(
- dir: ?HANDLE,
- sub_path: []const u8,
- out_buffer: []u8,
-) ReadLinkError![]u8 {
- const sub_path_w = try sliceToPrefixedFileW(sub_path);
- return ReadLinkW(dir, sub_path_w.span().ptr, out_buffer);
-}
-
-pub fn ReadLinkW(dir: ?HANDLE, sub_path_w: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 {
- const path_len_bytes = math.cast(u16, mem.lenZ(sub_path_w) * 2) catch |err| switch (err) {
+pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u8) ReadLinkError![]u8 {
+ // Here, we use `NtCreateFile` to shave off one syscall if we were to use `OpenFile` wrapper.
+ // With the latter, we'd need to call `NtCreateFile` twice, once for file symlink, and if that
+ // failed, again for dir symlink. Omitting any mention of file/dir flags makes it possible
+ // to open the symlink there and then.
+ const path_len_bytes = math.cast(u16, sub_path_w.len * 2) catch |err| switch (err) {
error.Overflow => return error.NameTooLong,
};
var nt_name = UNICODE_STRING{
.Length = path_len_bytes,
.MaximumLength = path_len_bytes,
- .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)),
+ .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w.ptr)),
};
-
- 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;
- }
-
var attr = OBJECT_ATTRIBUTES{
.Length = @sizeOf(OBJECT_ATTRIBUTES),
- .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w)) null else dir,
+ .RootDirectory = if (std.fs.path.isAbsoluteWindowsWTF16(sub_path_w)) null else dir,
.Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
.ObjectName = &nt_name,
.SecurityDescriptor = null,
.SecurityQualityOfService = null,
};
- var io: IO_STATUS_BLOCK = undefined;
var result_handle: HANDLE = undefined;
+ var io: IO_STATUS_BLOCK = undefined;
+
const rc = ntdll.NtCreateFile(
&result_handle,
FILE_READ_ATTRIBUTES,
@@ -878,135 +745,83 @@ pub const DeleteFileError = error{
NameTooLong,
FileBusy,
Unexpected,
+ NotDir,
+ IsDir,
};
-pub fn DeleteFile(filename: []const u8) DeleteFileError!void {
- const filename_w = try sliceToPrefixedFileW(filename);
- return DeleteFileW(filename_w.span().ptr);
-}
-
-pub fn DeleteFileW(filename: [*:0]const u16) DeleteFileError!void {
- if (kernel32.DeleteFileW(filename) == 0) {
- switch (kernel32.GetLastError()) {
- .FILE_NOT_FOUND => return error.FileNotFound,
- .PATH_NOT_FOUND => return error.FileNotFound,
- .ACCESS_DENIED => return error.AccessDenied,
- .FILENAME_EXCED_RANGE => return error.NameTooLong,
- .INVALID_PARAMETER => return error.NameTooLong,
- .SHARING_VIOLATION => return error.FileBusy,
- else => |err| return unexpectedError(err),
- }
- }
-}
-
-pub const MoveFileError = error{Unexpected};
-
-pub fn MoveFileEx(old_path: []const u8, new_path: []const u8, flags: DWORD) MoveFileError!void {
- const old_path_w = try sliceToPrefixedFileW(old_path);
- const new_path_w = try sliceToPrefixedFileW(new_path);
- return MoveFileExW(old_path_w.span().ptr, new_path_w.span().ptr, flags);
-}
-
-pub fn MoveFileExW(old_path: [*:0]const u16, new_path: [*:0]const u16, flags: DWORD) MoveFileError!void {
- if (kernel32.MoveFileExW(old_path, new_path, flags) == 0) {
- switch (kernel32.GetLastError()) {
- else => |err| return unexpectedError(err),
- }
- }
-}
-
-pub const CreateDirectoryError = error{
- NameTooLong,
- PathAlreadyExists,
- FileNotFound,
- NoDevice,
- AccessDenied,
- InvalidUtf8,
- BadPathName,
- Unexpected,
+pub const DeleteFileOptions = struct {
+ dir: ?HANDLE,
+ remove_dir: bool = false,
};
-/// Returns an open directory handle which the caller is responsible for closing with `CloseHandle`.
-pub fn CreateDirectory(dir: ?HANDLE, pathname: []const u8, sa: ?*SECURITY_ATTRIBUTES) CreateDirectoryError!HANDLE {
- const pathname_w = try sliceToPrefixedFileW(pathname);
- return CreateDirectoryW(dir, pathname_w.span().ptr, sa);
-}
+pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFileError!void {
+ const create_options_flags: ULONG = if (options.remove_dir)
+ FILE_DELETE_ON_CLOSE | FILE_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT
+ else
+ FILE_DELETE_ON_CLOSE | FILE_NON_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT; // would we ever want to delete the target instead?
-/// Same as `CreateDirectory` except takes a WTF-16 encoded path.
-pub fn CreateDirectoryW(
- dir: ?HANDLE,
- sub_path_w: [*:0]const u16,
- sa: ?*SECURITY_ATTRIBUTES,
-) CreateDirectoryError!HANDLE {
- const path_len_bytes = math.cast(u16, mem.lenZ(sub_path_w) * 2) catch |err| switch (err) {
- error.Overflow => return error.NameTooLong,
- };
+ const path_len_bytes = @intCast(u16, sub_path_w.len * 2);
var nt_name = UNICODE_STRING{
.Length = path_len_bytes,
.MaximumLength = path_len_bytes,
- .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)),
+ // The Windows API makes this mutable, but it will not mutate here.
+ .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w.ptr)),
};
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 = OBJECT_ATTRIBUTES{
.Length = @sizeOf(OBJECT_ATTRIBUTES),
- .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w)) null else dir,
+ .RootDirectory = if (std.fs.path.isAbsoluteWindowsWTF16(sub_path_w)) null else options.dir,
.Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
.ObjectName = &nt_name,
- .SecurityDescriptor = if (sa) |ptr| ptr.lpSecurityDescriptor else null,
+ .SecurityDescriptor = null,
.SecurityQualityOfService = null,
};
var io: IO_STATUS_BLOCK = undefined;
- var result_handle: HANDLE = undefined;
- const rc = ntdll.NtCreateFile(
- &result_handle,
- GENERIC_READ | SYNCHRONIZE,
+ var tmp_handle: HANDLE = undefined;
+ var rc = ntdll.NtCreateFile(
+ &tmp_handle,
+ SYNCHRONIZE | DELETE,
&attr,
&io,
null,
- FILE_ATTRIBUTE_NORMAL,
- FILE_SHARE_READ,
- FILE_CREATE,
- FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
+ 0,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ FILE_OPEN,
+ create_options_flags,
null,
0,
);
switch (rc) {
- .SUCCESS => return result_handle,
+ .SUCCESS => return CloseHandle(tmp_handle),
.OBJECT_NAME_INVALID => unreachable,
.OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
- .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
- .NO_MEDIA_IN_DEVICE => return error.NoDevice,
.INVALID_PARAMETER => unreachable,
- .ACCESS_DENIED => return error.AccessDenied,
- .OBJECT_PATH_SYNTAX_BAD => unreachable,
- .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
+ .FILE_IS_A_DIRECTORY => return error.IsDir,
+ .NOT_A_DIRECTORY => return error.NotDir,
else => return unexpectedStatus(rc),
}
}
-pub const RemoveDirectoryError = error{
- FileNotFound,
- DirNotEmpty,
- Unexpected,
- NotDir,
-};
+pub const MoveFileError = error{Unexpected};
-pub fn RemoveDirectory(dir_path: []const u8) RemoveDirectoryError!void {
- const dir_path_w = try sliceToPrefixedFileW(dir_path);
- return RemoveDirectoryW(dir_path_w.span().ptr);
+pub fn MoveFileEx(old_path: []const u8, new_path: []const u8, flags: DWORD) MoveFileError!void {
+ const old_path_w = try sliceToPrefixedFileW(old_path);
+ const new_path_w = try sliceToPrefixedFileW(new_path);
+ return MoveFileExW(old_path_w.span().ptr, new_path_w.span().ptr, flags);
}
-pub fn RemoveDirectoryW(dir_path_w: [*:0]const u16) RemoveDirectoryError!void {
- if (kernel32.RemoveDirectoryW(dir_path_w) == 0) {
+pub fn MoveFileExW(old_path: [*:0]const u16, new_path: [*:0]const u16, flags: DWORD) MoveFileError!void {
+ if (kernel32.MoveFileExW(old_path, new_path, flags) == 0) {
switch (kernel32.GetLastError()) {
- .PATH_NOT_FOUND => return error.FileNotFound,
- .DIR_NOT_EMPTY => return error.DirNotEmpty,
- .DIRECTORY => return error.NotDir,
else => |err| return unexpectedError(err),
}
}
@@ -1493,8 +1308,7 @@ pub fn cStrToPrefixedFileW(s: [*:0]const u8) !PathSpace {
}
/// Converts the path `s` to WTF16, null-terminated. If the path is absolute,
-/// it will get NT-style prefix `\??\` prepended automatically. For prepending
-/// Win32-style prefix, see `sliceToWin32PrefixedFileW` instead.
+/// it will get NT-style prefix `\??\` prepended automatically.
pub fn sliceToPrefixedFileW(s: []const u8) !PathSpace {
// TODO https://github.com/ziglang/zig/issues/2765
var path_space: PathSpace = undefined;