From 4d9eff4bdb368dd4dad99b6b19c83468607cd1d2 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 31 Jul 2020 00:54:33 +0200 Subject: [PATCH] Add prelim `openW` and `openatW` Added POSIX functions targeting Windows pass `open` and `openat` smoke tests. --- lib/std/child_process.zig | 1 + lib/std/os.zig | 75 +++++++++++++++++++++++++++++++++++-- lib/std/os/bits/windows.zig | 25 +++++++++++++ lib/std/os/test.zig | 32 ++++++++-------- lib/std/os/windows.zig | 5 ++- 5 files changed, 117 insertions(+), 21 deletions(-) diff --git a/lib/std/child_process.zig b/lib/std/child_process.zig index d157e5dc3..022405f92 100644 --- a/lib/std/child_process.zig +++ b/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 diff --git a/lib/std/os.zig b/lib/std/os.zig index 0a21c9880..7e4125e3a 100644 --- a/lib/std/os.zig +++ b/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))) { diff --git a/lib/std/os/bits/windows.zig b/lib/std/os/bits/windows.zig index 4988cafbe..53a590ff1 100644 --- a/lib/std/os/bits/windows.zig +++ b/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 diff --git a/lib/std/os/test.zig b/lib/std/os/test.zig index 3aa0d0b3b..b39e239ed 100644 --- a/lib/std/os/test.zig +++ b/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" { diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index c94c89bd0..bf41d0ae9 100644 --- a/lib/std/os/windows.zig +++ b/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,