Add prelim `openW` and `openatW`

Added POSIX functions targeting Windows pass `open` and `openat`
smoke tests.
master
Jakub Konka 2020-07-31 00:54:33 +02:00
parent a694f575ad
commit 4d9eff4bdb
5 changed files with 117 additions and 21 deletions

View File

@ -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

View File

@ -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))) {

View File

@ -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;

View File

@ -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" {

View File

@ -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,