Merge pull request #5960 from kubkon/windows-dir-refactor

Refactor out file and dir creation routines in Windows
This commit is contained in:
Andrew Kelley 2020-08-02 17:41:47 +00:00 committed by GitHub
commit 6df35c5877
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 460 additions and 440 deletions

View File

@ -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

View File

@ -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) {

View File

@ -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 };

View File

@ -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);

View File

@ -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;
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);
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: [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.

View File

@ -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;

View File

@ -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");
}

View File

@ -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,24 +745,69 @@ 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 const DeleteFileOptions = struct {
dir: ?HANDLE,
remove_dir: bool = false,
};
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 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?
const path_len_bytes = @intCast(u16, sub_path_w.len * 2);
var nt_name = 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.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.isAbsoluteWindowsWTF16(sub_path_w)) null else options.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 tmp_handle: HANDLE = undefined;
var rc = ntdll.NtCreateFile(
&tmp_handle,
SYNCHRONIZE | DELETE,
&attr,
&io,
null,
0,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
FILE_OPEN,
create_options_flags,
null,
0,
);
switch (rc) {
.SUCCESS => return CloseHandle(tmp_handle),
.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 unexpectedStatus(rc),
}
}
@ -915,103 +827,6 @@ pub fn MoveFileExW(old_path: [*:0]const u16, new_path: [*:0]const u16, flags: DW
}
}
pub const CreateDirectoryError = error{
NameTooLong,
PathAlreadyExists,
FileNotFound,
NoDevice,
AccessDenied,
InvalidUtf8,
BadPathName,
Unexpected,
};
/// 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);
}
/// 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,
};
var nt_name = UNICODE_STRING{
.Length = path_len_bytes,
.MaximumLength = path_len_bytes,
.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;
}
var attr = OBJECT_ATTRIBUTES{
.Length = @sizeOf(OBJECT_ATTRIBUTES),
.RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w)) null else dir,
.Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
.ObjectName = &nt_name,
.SecurityDescriptor = if (sa) |ptr| ptr.lpSecurityDescriptor else null,
.SecurityQualityOfService = null,
};
var io: IO_STATUS_BLOCK = undefined;
var result_handle: HANDLE = undefined;
const rc = ntdll.NtCreateFile(
&result_handle,
GENERIC_READ | SYNCHRONIZE,
&attr,
&io,
null,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ,
FILE_CREATE,
FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
null,
0,
);
switch (rc) {
.SUCCESS => return result_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,
else => return unexpectedStatus(rc),
}
}
pub const RemoveDirectoryError = error{
FileNotFound,
DirNotEmpty,
Unexpected,
NotDir,
};
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 RemoveDirectoryW(dir_path_w: [*:0]const u16) RemoveDirectoryError!void {
if (kernel32.RemoveDirectoryW(dir_path_w) == 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),
}
}
}
pub const GetStdHandleError = error{
NoStandardHandleAttached,
Unexpected,
@ -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;