Commit 4d9eff4bdb

Jakub Konka <kubkon@jakubkonka.com>
2020-07-31 00:54:33
Add prelim `openW` and `openatW`
Added POSIX functions targeting Windows pass `open` and `openat` smoke tests.
1 parent a694f57
lib/std/os/bits/windows.zig
@@ -237,3 +237,28 @@ pub const IPPROTO_TCP = ws2_32.IPPROTO_TCP;
 pub const IPPROTO_UDP = ws2_32.IPPROTO_UDP;
 pub const IPPROTO_ICMPV6 = ws2_32.IPPROTO_ICMPV6;
 pub const IPPROTO_RM = ws2_32.IPPROTO_RM;
+
+pub const O_RDONLY = 0o0;
+pub const O_WRONLY = 0o1;
+pub const O_RDWR = 0o2;
+
+pub const O_CREAT = 0o100;
+pub const O_EXCL = 0o200;
+pub const O_NOCTTY = 0o400;
+pub const O_TRUNC = 0o1000;
+pub const O_APPEND = 0o2000;
+pub const O_NONBLOCK = 0o4000;
+pub const O_DSYNC = 0o10000;
+pub const O_SYNC = 0o4010000;
+pub const O_RSYNC = 0o4010000;
+pub const O_DIRECTORY = 0o200000;
+pub const O_NOFOLLOW = 0o400000;
+pub const O_CLOEXEC = 0o2000000;
+
+pub const O_ASYNC = 0o20000;
+pub const O_DIRECT = 0o40000;
+pub const O_LARGEFILE = 0;
+pub const O_NOATIME = 0o1000000;
+pub const O_PATH = 0o10000000;
+pub const O_TMPFILE = 0o20200000;
+pub const O_NDELAY = O_NONBLOCK;
\ No newline at end of file
lib/std/os/test.zig
@@ -21,7 +21,6 @@ const Dir = std.fs.Dir;
 const ArenaAllocator = std.heap.ArenaAllocator;
 
 test "open smoke test" {
-    if (builtin.os.tag == .windows) return error.SkipZigTest;
     if (builtin.os.tag == .wasi) return error.SkipZigTest;
 
     // TODO verify file attributes using `fstat`
@@ -40,41 +39,41 @@ test "open smoke test" {
 
     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, 0o666);
+    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, 0o666));
+    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, 0o666);
+    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, 0o666));
+    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, 0o666);
+    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, 0o666);
+    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, 0o666));
+    expectError(error.IsDir, os.open(file_path, os.O_RDWR, mode));
 }
 
 test "openat smoke test" {
-    if (builtin.os.tag == .windows) return error.SkipZigTest;
     if (builtin.os.tag == .wasi) return error.SkipZigTest;
 
     // TODO verify file attributes using `fstatat`
@@ -83,30 +82,31 @@ test "openat smoke test" {
     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, 0o666);
+    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, 0o666));
+    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, 0o666);
+    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, 0o666));
+    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", 0o666);
+    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, 0o666);
+    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, 0o666));
+    expectError(error.IsDir, os.openat(tmp.dir.fd, "some_dir", os.O_RDWR, mode));
 }
 
 test "symlink with relative paths" {
lib/std/os/windows.zig
@@ -27,6 +27,7 @@ pub const self_process_handle = @intToPtr(HANDLE, maxInt(usize));
 
 pub const OpenError = error{
     IsDir,
+    NotDir,
     FileNotFound,
     NoDevice,
     AccessDenied,
@@ -125,7 +126,8 @@ 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 => if (options.open_dir) unreachable else return error.IsDir,
+            .FILE_IS_A_DIRECTORY => return error.IsDir,
+            .NOT_A_DIRECTORY => return error.NotDir,
             else => return unexpectedStatus(rc),
         }
     }
@@ -609,6 +611,7 @@ pub fn CreateSymbolicLink(
         .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,
lib/std/child_process.zig
@@ -364,6 +364,7 @@ pub const ChildProcess = struct {
                 error.FileTooBig => unreachable,
                 error.DeviceBusy => unreachable,
                 error.FileLocksNotSupported => unreachable,
+                error.BadPathName => unreachable, // Windows-only
                 else => |e| return e,
             }
         else
lib/std/os.zig
@@ -1041,6 +1041,9 @@ pub const OpenError = error{
 
     /// The underlying filesystem does not support file locks
     FileLocksNotSupported,
+
+    BadPathName,
+    InvalidUtf8,
 } || UnexpectedError;
 
 /// Open and possibly create a file. Keeps trying if it gets interrupted.
@@ -1092,18 +1095,65 @@ pub fn openZ(file_path: [*:0]const u8, flags: u32, perm: mode_t) OpenError!fd_t
     }
 }
 
+fn openOptionsFromFlags(flags: u32) windows.OpenFileOptions {
+    const w = windows;
+
+    var access_mask: w.ULONG = w.READ_CONTROL | w.FILE_WRITE_ATTRIBUTES | w.SYNCHRONIZE;
+    if (flags & O_RDWR != 0) {
+        access_mask |= w.GENERIC_READ | w.GENERIC_WRITE;
+    } else if (flags & O_WRONLY != 0) {
+        access_mask |= w.GENERIC_WRITE;
+    } else {
+        access_mask |= w.GENERIC_READ | w.GENERIC_WRITE;
+    }
+
+    const open_dir: bool = flags & O_DIRECTORY != 0;
+    const follow_symlinks: bool = flags & O_NOFOLLOW == 0;
+
+    const creation: w.ULONG = blk: {
+        if (flags & O_CREAT != 0) {
+            if (flags & O_EXCL != 0) {
+                break :blk w.FILE_CREATE;
+            }
+        }
+        break :blk w.FILE_OPEN;
+    };
+
+    return .{
+        .access_mask = access_mask,
+        .io_mode = .blocking,
+        .creation = creation,
+        .open_dir = open_dir,
+        .follow_symlinks = follow_symlinks,
+    };
+}
+
 /// Windows-only. The path parameter is
 /// [WTF-16](https://simonsapin.github.io/wtf-8/#potentially-ill-formed-utf-16) encoded.
 /// Translates the POSIX open API call to a Windows API call.
-pub fn openW(file_path_w: []const u16, flags: u32, perm: usize) OpenError!fd_t {
-    @compileError("TODO implement openW for windows");
+/// TODO currently, this function does not handle all flag combinations
+/// or makes use of perm argument.
+pub fn openW(file_path_w: []const u16, flags: u32, perm: mode_t) OpenError!fd_t {
+    var options = openOptionsFromFlags(flags);
+    options.dir = std.fs.cwd().fd;
+    return windows.OpenFile(file_path_w, options) catch |err| switch (err) {
+        error.WouldBlock => unreachable,
+        error.PipeBusy => unreachable,
+        else => |e| return e,
+    };
 }
 
 /// Open and possibly create a file. Keeps trying if it gets interrupted.
 /// `file_path` is relative to the open directory handle `dir_fd`.
 /// See also `openatC`.
-/// TODO support windows
 pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: mode_t) OpenError!fd_t {
+    if (builtin.os.tag == .wasi) {
+        @compileError("use openatWasi instead");
+    }
+    if (builtin.os.tag == .windows) {
+        const file_path_w = try windows.sliceToPrefixedFileW(file_path);
+        return openatW(dir_fd, file_path_w.span(), flags, mode);
+    }
     const file_path_c = try toPosixPath(file_path);
     return openatZ(dir_fd, &file_path_c, flags, mode);
 }
@@ -1145,8 +1195,11 @@ pub const openatC = @compileError("deprecated: renamed to openatZ");
 /// Open and possibly create a file. Keeps trying if it gets interrupted.
 /// `file_path` is relative to the open directory handle `dir_fd`.
 /// See also `openat`.
-/// TODO support windows
 pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t) OpenError!fd_t {
+    if (builtin.os.tag == .windows) {
+        const file_path_w = try windows.cStrToPrefixedFileW(file_path);
+        return openatW(dir_fd, file_path_w.span(), flags, mode);
+    }
     while (true) {
         const rc = system.openat(dir_fd, file_path, flags, mode);
         switch (errno(rc)) {
@@ -1177,6 +1230,20 @@ pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t)
     }
 }
 
+/// Windows-only. Similar to `openat` but with pathname argument null-terminated
+/// WTF16 encoded.
+/// TODO currently, this function does not handle all flag combinations
+/// or makes use of perm argument.
+pub fn openatW(dir_fd: fd_t, file_path_w: []const u16, flags: u32, mode: mode_t) OpenError!fd_t {
+    var options = openOptionsFromFlags(flags);
+    options.dir = dir_fd;
+    return windows.OpenFile(file_path_w, options) catch |err| switch (err) {
+        error.WouldBlock => unreachable,
+        error.PipeBusy => unreachable,
+        else => |e| return e,
+    };
+}
+
 pub fn dup2(old_fd: fd_t, new_fd: fd_t) !void {
     while (true) {
         switch (errno(system.dup2(old_fd, new_fd))) {