Commit a89d5cfc3e

Jakub Konka <kubkon@jakubkonka.com>
2020-07-30 17:00:50
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.
1 parent 0d31877
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);
 
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;
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
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
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;
+
+    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: [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.