Remove CreateDirectoryW and CreateFileW calls

Replace them with `std.os.windows.OpenFile` instead. To allow
creation/opening of directories, `std.os.windows.OpenFileOptions`
now features a `.expect_dir: bool` member which is meant to emualate
POSIX's `O_DIRECTORY` flag.
This commit is contained in:
Jakub Konka 2020-07-30 17:00:50 +02:00
parent 0d31877444
commit a89d5cfc3e
5 changed files with 90 additions and 169 deletions

View File

@ -480,25 +480,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,
.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

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,
.expect_dir = true,
});
var dir_handle_consumed = false;
defer if (!dir_handle_consumed) windows.CloseHandle(dir_handle);

View File

@ -2146,7 +2146,18 @@ 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);
const sub_dir_handle = windows.OpenFile(std.mem.spanZ(sub_path_w), .{
.dir = dir_fd,
.access_mask = windows.GENERIC_READ | windows.SYNCHRONIZE,
.creation = windows.FILE_CREATE,
.io_mode = .blocking,
.expect_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 +2186,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().ptr, mode);
} else {
const dir_path_c = try toPosixPath(dir_path);
return mkdirZ(&dir_path_c, mode);
@ -2188,9 +2198,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().ptr, mode);
}
switch (errno(system.mkdir(dir_path, mode))) {
0 => return,
@ -2211,6 +2219,23 @@ pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
}
}
/// Windows-only. Same as `mkdir` but the parameters is null-terminated, WTF16 encoded.
pub fn mkdirW(dir_path_w: [*:0]const u16, mode: u32) MakeDirError!void {
const sub_dir_handle = windows.OpenFile(std.mem.spanZ(dir_path_w), .{
.dir = std.fs.cwd().fd,
.access_mask = windows.GENERIC_READ | windows.SYNCHRONIZE,
.creation = windows.FILE_CREATE,
.io_mode = .blocking,
.expect_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,
@ -4013,19 +4038,40 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP
/// 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);
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(std.mem.spanZ(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(std.mem.spanZ(pathname), .{
.dir = dir,
.access_mask = access_mask,
.share_access = share_access,
.creation = creation,
.io_mode = .blocking,
.expect_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

@ -49,52 +49,10 @@ pub const CreateFileError = error{
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,
FileNotFound,
NoDevice,
SharingViolation,
AccessDenied,
PipeBusy,
PathAlreadyExists,
@ -111,15 +69,16 @@ pub const OpenFileOptions = struct {
share_access_nonblocking: bool = false,
creation: ULONG,
io_mode: std.io.ModeOverride,
expect_dir: bool = false,
};
/// 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.expect_dir) {
return error.IsDir;
}
if (mem.eql(u16, sub_path_w, &[_]u16{ '.', '.' })) {
if (mem.eql(u16, sub_path_w, &[_]u16{ '.', '.' }) and !options.expect_dir) {
return error.IsDir;
}
@ -145,8 +104,9 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
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 file_or_dir_flag: ULONG = if (options.expect_dir) FILE_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT else FILE_NON_DIRECTORY_FILE;
const flags: ULONG = file_or_dir_flag | blocking_flag;
const rc = ntdll.NtCreateFile(
&result,
options.access_mask,
@ -156,7 +116,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,
);
@ -183,7 +143,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
.PIPE_BUSY => return error.PipeBusy,
.OBJECT_PATH_SYNTAX_BAD => unreachable,
.OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
.FILE_IS_A_DIRECTORY => return error.IsDir,
.FILE_IS_A_DIRECTORY => if (options.expect_dir) unreachable else return error.IsDir,
else => return unexpectedStatus(rc),
}
}
@ -733,7 +693,6 @@ pub fn CreateSymbolicLinkW(
error.WouldBlock => unreachable,
error.IsDir => return error.PathAlreadyExists,
error.PipeBusy => unreachable,
error.SharingViolation => return error.AccessDenied,
else => |e| return e,
};
}
@ -915,80 +874,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,
@ -1493,8 +1378,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;