From e1868029e9c861fa6b1382ba52052264fc5f9c31 Mon Sep 17 00:00:00 2001 From: LeRoyce Pearson Date: Mon, 9 Mar 2020 20:48:00 -0600 Subject: [PATCH] Implement blocking file locking API for windows --- lib/std/fs.zig | 132 +++++++++++++++++++-------------- lib/std/os.zig | 4 +- lib/std/os/bits/linux/i386.zig | 1 + lib/std/os/linux.zig | 2 +- 4 files changed, 80 insertions(+), 59 deletions(-) diff --git a/lib/std/fs.zig b/lib/std/fs.zig index edb12e0f0..4d00babb6 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -708,7 +708,12 @@ pub const Dir = struct { const access_mask = w.SYNCHRONIZE | (if (flags.read) @as(u32, w.GENERIC_READ) else 0) | (if (flags.write) @as(u32, w.GENERIC_WRITE) else 0); - return self.openFileWindows(sub_path_w, access_mask, w.FILE_OPEN); + const share_access = if (flags.lock) + w.FILE_SHARE_DELETE | + (if (flags.write) @as(os.windows.ULONG, 0) else w.FILE_SHARE_READ) + else + null; + return self.openFileWindows(sub_path_w, access_mask, share_access, w.FILE_OPEN); } /// Creates, opens, or overwrites a file with write access. @@ -753,7 +758,7 @@ pub const Dir = struct { @as(u32, w.FILE_OVERWRITE_IF) else @as(u32, w.FILE_OPEN_IF); - return self.openFileWindows(sub_path_w, access_mask, creation); + return self.openFileWindows(sub_path_w, access_mask, null, creation); } /// Deprecated; call `openFile` directly. @@ -775,65 +780,80 @@ pub const Dir = struct { self: Dir, sub_path_w: [*:0]const u16, access_mask: os.windows.ACCESS_MASK, + share_access_opt: ?os.windows.ULONG, creation: os.windows.ULONG, ) File.OpenError!File { - const w = os.windows; + var delay: usize = 1; + while (true) { + const w = os.windows; - if (sub_path_w[0] == '.' and sub_path_w[1] == 0) { - return error.IsDir; - } - if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) { - return error.IsDir; - } + if (sub_path_w[0] == '.' and sub_path_w[1] == 0) { + return error.IsDir; + } + if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) { + return error.IsDir; + } - var result = File{ - .handle = undefined, - .io_mode = .blocking, - }; + var result = File{ + .handle = undefined, + .io_mode = .blocking, + }; - const path_len_bytes = math.cast(u16, mem.toSliceConst(u16, sub_path_w).len * 2) catch |err| switch (err) { - error.Overflow => return error.NameTooLong, - }; - var nt_name = w.UNICODE_STRING{ - .Length = path_len_bytes, - .MaximumLength = path_len_bytes, - .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)), - }; - var attr = w.OBJECT_ATTRIBUTES{ - .Length = @sizeOf(w.OBJECT_ATTRIBUTES), - .RootDirectory = if (path.isAbsoluteWindowsW(sub_path_w)) null else self.fd, - .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here. - .ObjectName = &nt_name, - .SecurityDescriptor = null, - .SecurityQualityOfService = null, - }; - var io: w.IO_STATUS_BLOCK = undefined; - const rc = w.ntdll.NtCreateFile( - &result.handle, - access_mask, - &attr, - &io, - null, - w.FILE_ATTRIBUTE_NORMAL, - w.FILE_SHARE_WRITE | w.FILE_SHARE_READ | w.FILE_SHARE_DELETE, - creation, - w.FILE_NON_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT, - null, - 0, - ); - switch (rc) { - .SUCCESS => return result, - .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, - .SHARING_VIOLATION => return error.SharingViolation, - .ACCESS_DENIED => return error.AccessDenied, - .PIPE_BUSY => return error.PipeBusy, - .OBJECT_PATH_SYNTAX_BAD => unreachable, - .OBJECT_NAME_COLLISION => return error.PathAlreadyExists, - else => return w.unexpectedStatus(rc), + const path_len_bytes = math.cast(u16, mem.toSliceConst(u16, sub_path_w).len * 2) catch |err| switch (err) { + error.Overflow => return error.NameTooLong, + }; + var nt_name = w.UNICODE_STRING{ + .Length = path_len_bytes, + .MaximumLength = path_len_bytes, + .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)), + }; + var attr = w.OBJECT_ATTRIBUTES{ + .Length = @sizeOf(w.OBJECT_ATTRIBUTES), + .RootDirectory = if (path.isAbsoluteWindowsW(sub_path_w)) null else self.fd, + .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here. + .ObjectName = &nt_name, + .SecurityDescriptor = null, + .SecurityQualityOfService = null, + }; + var io: w.IO_STATUS_BLOCK = undefined; + const share_access = share_access_opt orelse w.FILE_SHARE_WRITE | w.FILE_SHARE_READ | w.FILE_SHARE_DELETE; + const rc = w.ntdll.NtCreateFile( + &result.handle, + access_mask, + &attr, + &io, + null, + w.FILE_ATTRIBUTE_NORMAL, + share_access, + creation, + w.FILE_NON_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT, + null, + 0, + ); + switch (rc) { + .SUCCESS => return result, + .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, + .SHARING_VIOLATION => { + // TODO: check if async or blocking + //return error.SharingViolation + // Sleep so we don't consume a ton of CPU waiting to get lock on file + std.time.sleep(delay); + // Increase sleep time as long as it is less than 5 seconds + if (delay < 5 * std.time.ns_per_s) { + delay *= 2; + } + continue; + }, + .ACCESS_DENIED => return error.AccessDenied, + .PIPE_BUSY => return error.PipeBusy, + .OBJECT_PATH_SYNTAX_BAD => unreachable, + .OBJECT_NAME_COLLISION => return error.PathAlreadyExists, + else => return w.unexpectedStatus(rc), + } } } diff --git a/lib/std/os.zig b/lib/std/os.zig index ca007ecb4..f645f73e2 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -1141,8 +1141,8 @@ pub fn freeNullDelimitedEnvMap(allocator: *mem.Allocator, envp_buf: []?[*:0]u8) } /// Attempts to get lock the file, blocking if the file is locked. -pub fn fcntlFlockBlocking(fd: fd_t, flock_p: *flock) OpenError!void { - const rc = system.fcntlFlock(fd, F_SETLKW, flock_p); +pub fn fcntlFlockBlocking(fd: fd_t, flock_p: *const flock) OpenError!void { + const rc = system.fcntl(fd, F_SETLKW, flock_p); if (rc < 0) { std.debug.panic("fcntl error: {}\n", .{rc}); } diff --git a/lib/std/os/bits/linux/i386.zig b/lib/std/os/bits/linux/i386.zig index e075f1826..0043dd24e 100644 --- a/lib/std/os/bits/linux/i386.zig +++ b/lib/std/os/bits/linux/i386.zig @@ -8,6 +8,7 @@ const iovec = linux.iovec; const iovec_const = linux.iovec_const; const uid_t = linux.uid_t; const gid_t = linux.gid_t; +const pid_t = linux.pid_t; const stack_t = linux.stack_t; const sigset_t = linux.sigset_t; diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 389f110d0..4b29a1cc2 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -219,7 +219,7 @@ pub fn mmap(address: ?[*]u8, length: usize, prot: usize, flags: u32, fd: i32, of } } -pub fn fcntlFlock(fd: fd_t, cmd: i32, flock_p: *flock) usize { +pub fn fcntl(fd: fd_t, cmd: i32, flock_p: *const c_void) usize { return syscall3(SYS_fcntl, @bitCast(usize, @as(isize, fd)), @bitCast(usize, @as(isize, cmd)), @ptrToInt(flock_p)); }