From a89d5cfc3eaaf97b914abc5d355099ec8357925d Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 30 Jul 2020 17:00:50 +0200 Subject: [PATCH] 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. --- lib/std/child_process.zig | 21 +++--- lib/std/fs.zig | 6 +- lib/std/fs/watch.zig | 16 ++--- lib/std/os.zig | 84 ++++++++++++++++++------ lib/std/os/windows.zig | 132 +++----------------------------------- 5 files changed, 90 insertions(+), 169 deletions(-) diff --git a/lib/std/child_process.zig b/lib/std/child_process.zig index 57c1dfb945..d157e5dc30 100644 --- a/lib/std/child_process.zig +++ b/lib/std/child_process.zig @@ -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 diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 6e3a53b772..c03b36d718 100644 --- 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 diff --git a/lib/std/fs/watch.zig b/lib/std/fs/watch.zig index b161e45c71..a753bdd247 100644 --- 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, + .expect_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 index 1665a2efa6..56e8374bdb 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -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. diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index b0b70139fd..3b810951e0 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -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;