From 078aa5f7b20e6e705378713a717bc2dd9afb8835 Mon Sep 17 00:00:00 2001 From: Anthony Carrico Date: Sat, 25 Sep 2021 17:32:18 -0400 Subject: [PATCH] Adds Linux support for POSIX file locking with fcntl On Linux, locking fails with EAGAIN (vs. EACCES on other systems). This commit also adds FcntlErrors for EDEADLK and ENOLCK. --- lib/std/c/darwin.zig | 10 +++--- lib/std/c/dragonfly.zig | 10 +++--- lib/std/c/freebsd.zig | 12 +++---- lib/std/c/haiku.zig | 10 +++--- lib/std/c/netbsd.zig | 10 +++--- lib/std/c/openbsd.zig | 10 +++--- lib/std/c/solaris.zig | 12 +++---- lib/std/fs.zig | 8 +++++ lib/std/os.zig | 14 ++++++++- lib/std/os/linux/arm-eabi.zig | 10 +++--- lib/std/os/linux/arm64.zig | 10 +++--- lib/std/os/linux/mips.zig | 10 +++--- lib/std/os/linux/powerpc.zig | 10 +++--- lib/std/os/linux/powerpc64.zig | 10 +++--- lib/std/os/linux/riscv64.zig | 10 +++--- lib/std/os/linux/sparc64.zig | 10 +++--- lib/std/os/test.zig | 57 ++++++++++++++++++++++++++++++++++ 17 files changed, 150 insertions(+), 73 deletions(-) diff --git a/lib/std/c/darwin.zig b/lib/std/c/darwin.zig index bd5d3aeead..308fb106a8 100644 --- a/lib/std/c/darwin.zig +++ b/lib/std/c/darwin.zig @@ -357,11 +357,11 @@ pub const off_t = i64; pub const ino_t = u64; pub const Flock = extern struct { - l_start: off_t, - l_len: off_t, - l_pid: pid_t, - l_type: i16, - l_whence: i16, + start: off_t, + len: off_t, + pid: pid_t, + type: i16, + whence: i16, }; pub const Stat = extern struct { diff --git a/lib/std/c/dragonfly.zig b/lib/std/c/dragonfly.zig index 75e84f637d..186525a9ce 100644 --- a/lib/std/c/dragonfly.zig +++ b/lib/std/c/dragonfly.zig @@ -929,11 +929,11 @@ pub const LOCK = struct { }; pub const Flock = extern struct { - l_start: off_t, - l_len: off_t, - l_pid: pid_t, - l_type: c_short, - l_whence: c_short, + start: off_t, + len: off_t, + pid: pid_t, + type: c_short, + whence: c_short, }; pub const addrinfo = extern struct { diff --git a/lib/std/c/freebsd.zig b/lib/std/c/freebsd.zig index d5b6e4972d..8972b6d6dc 100644 --- a/lib/std/c/freebsd.zig +++ b/lib/std/c/freebsd.zig @@ -193,12 +193,12 @@ pub const dl_phdr_info = extern struct { }; pub const Flock = extern struct { - l_start: off_t, - l_len: off_t, - l_pid: pid_t, - l_type: i16, - l_whence: i16, - l_sysid: i32, + start: off_t, + len: off_t, + pid: pid_t, + type: i16, + whence: i16, + sysid: i32, __unused: [4]u8, }; diff --git a/lib/std/c/haiku.zig b/lib/std/c/haiku.zig index 176d53e6ae..f3f7cd3081 100644 --- a/lib/std/c/haiku.zig +++ b/lib/std/c/haiku.zig @@ -159,11 +159,11 @@ pub const dl_phdr_info = extern struct { }; pub const Flock = extern struct { - l_type: c_short, - l_whence: c_short, - l_start: off_t, - l_len: off_t, - l_pid: pid_t, + type: c_short, + whence: c_short, + start: off_t, + len: off_t, + pid: pid_t, }; pub const msghdr = extern struct { diff --git a/lib/std/c/netbsd.zig b/lib/std/c/netbsd.zig index b0708107ad..b1f8e8846e 100644 --- a/lib/std/c/netbsd.zig +++ b/lib/std/c/netbsd.zig @@ -169,11 +169,11 @@ pub const dl_phdr_info = extern struct { }; pub const Flock = extern struct { - l_start: off_t, - l_len: off_t, - l_pid: pid_t, - l_type: i16, - l_whence: i16, + start: off_t, + len: off_t, + pid: pid_t, + type: i16, + whence: i16, }; pub const addrinfo = extern struct { diff --git a/lib/std/c/openbsd.zig b/lib/std/c/openbsd.zig index 51cdbe3180..5e2cd9d9d4 100644 --- a/lib/std/c/openbsd.zig +++ b/lib/std/c/openbsd.zig @@ -94,11 +94,11 @@ pub const dl_phdr_info = extern struct { }; pub const Flock = extern struct { - l_start: off_t, - l_len: off_t, - l_pid: pid_t, - l_type: c_short, - l_whence: c_short, + start: off_t, + len: off_t, + pid: pid_t, + type: c_short, + whence: c_short, }; pub const addrinfo = extern struct { diff --git a/lib/std/c/solaris.zig b/lib/std/c/solaris.zig index 5f44dc5350..7026cf5dc4 100644 --- a/lib/std/c/solaris.zig +++ b/lib/std/c/solaris.zig @@ -116,13 +116,13 @@ pub const RTLD = struct { }; pub const Flock = extern struct { - l_type: c_short, - l_whence: c_short, - l_start: off_t, + type: c_short, + whence: c_short, + start: off_t, // len == 0 means until end of file. - l_len: off_t, - l_sysid: c_int, - l_pid: pid_t, + len: off_t, + sysid: c_int, + pid: pid_t, __pad: [4]c_long, }; diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 7b577b1b62..758a2bd132 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -1045,6 +1045,8 @@ pub const Dir = struct { error.FileBusy => unreachable, error.Locked => unreachable, error.PermissionDenied => unreachable, + error.DeadLock => unreachable, + error.LockedRegionLimitExceeded => unreachable, else => |e| return e, }; fl_flags &= ~@as(usize, os.O.NONBLOCK); @@ -1052,6 +1054,8 @@ pub const Dir = struct { error.FileBusy => unreachable, error.Locked => unreachable, error.PermissionDenied => unreachable, + error.DeadLock => unreachable, + error.LockedRegionLimitExceeded => unreachable, else => |e| return e, }; } @@ -1197,6 +1201,8 @@ pub const Dir = struct { error.FileBusy => unreachable, error.Locked => unreachable, error.PermissionDenied => unreachable, + error.DeadLock => unreachable, + error.LockedRegionLimitExceeded => unreachable, else => |e| return e, }; fl_flags &= ~@as(usize, os.O.NONBLOCK); @@ -1204,6 +1210,8 @@ pub const Dir = struct { error.FileBusy => unreachable, error.Locked => unreachable, error.PermissionDenied => unreachable, + error.DeadLock => unreachable, + error.LockedRegionLimitExceeded => unreachable, else => |e| return e, }; } diff --git a/lib/std/os.zig b/lib/std/os.zig index d7f60c1e1a..3567420a51 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -4513,6 +4513,8 @@ pub const FcntlError = error{ FileBusy, ProcessFdQuotaExceeded, Locked, + DeadLock, + LockedRegionLimitExceeded, } || UnexpectedError; pub fn fcntl(fd: fd_t, cmd: i32, arg: usize) FcntlError!usize { @@ -4521,13 +4523,15 @@ pub fn fcntl(fd: fd_t, cmd: i32, arg: usize) FcntlError!usize { switch (errno(rc)) { .SUCCESS => return @intCast(usize, rc), .INTR => continue, - .ACCES => return error.Locked, + .AGAIN, .ACCES => return error.Locked, .BADF => unreachable, .BUSY => return error.FileBusy, .INVAL => unreachable, // invalid parameters .PERM => return error.PermissionDenied, .MFILE => return error.ProcessFdQuotaExceeded, .NOTDIR => unreachable, // invalid parameter + .DEADLK => return error.DeadLock, + .NOLCK => return error.LockedRegionLimitExceeded, else => |err| return unexpectedErrno(err), } } @@ -4542,6 +4546,8 @@ fn setSockFlags(sock: socket_t, flags: u32) !void { error.FileBusy => unreachable, error.Locked => unreachable, error.PermissionDenied => unreachable, + error.DeadLock => unreachable, + error.LockedRegionLimitExceeded => unreachable, else => |e| return e, }; fd_flags |= FD_CLOEXEC; @@ -4549,6 +4555,8 @@ fn setSockFlags(sock: socket_t, flags: u32) !void { error.FileBusy => unreachable, error.Locked => unreachable, error.PermissionDenied => unreachable, + error.DeadLock => unreachable, + error.LockedRegionLimitExceeded => unreachable, else => |e| return e, }; } @@ -4570,6 +4578,8 @@ fn setSockFlags(sock: socket_t, flags: u32) !void { error.FileBusy => unreachable, error.Locked => unreachable, error.PermissionDenied => unreachable, + error.DeadLock => unreachable, + error.LockedRegionLimitExceeded => unreachable, else => |e| return e, }; fl_flags |= O.NONBLOCK; @@ -4577,6 +4587,8 @@ fn setSockFlags(sock: socket_t, flags: u32) !void { error.FileBusy => unreachable, error.Locked => unreachable, error.PermissionDenied => unreachable, + error.DeadLock => unreachable, + error.LockedRegionLimitExceeded => unreachable, else => |e| return e, }; } diff --git a/lib/std/os/linux/arm-eabi.zig b/lib/std/os/linux/arm-eabi.zig index 852f6e9283..cea3188b3d 100644 --- a/lib/std/os/linux/arm-eabi.zig +++ b/lib/std/os/linux/arm-eabi.zig @@ -638,12 +638,12 @@ pub const HWCAP = struct { }; pub const Flock = extern struct { - l_type: i16, - l_whence: i16, + type: i16, + whence: i16, __pad0: [4]u8, - l_start: off_t, - l_len: off_t, - l_pid: pid_t, + start: off_t, + len: off_t, + pid: pid_t, __unused: [4]u8, }; diff --git a/lib/std/os/linux/arm64.zig b/lib/std/os/linux/arm64.zig index 16e3b897de..815a640b79 100644 --- a/lib/std/os/linux/arm64.zig +++ b/lib/std/os/linux/arm64.zig @@ -490,11 +490,11 @@ pub const VDSO = struct { }; pub const Flock = extern struct { - l_type: i16, - l_whence: i16, - l_start: off_t, - l_len: off_t, - l_pid: pid_t, + type: i16, + whence: i16, + start: off_t, + len: off_t, + pid: pid_t, __unused: [4]u8, }; diff --git a/lib/std/os/linux/mips.zig b/lib/std/os/linux/mips.zig index a7235f4de2..ef586cba19 100644 --- a/lib/std/os/linux/mips.zig +++ b/lib/std/os/linux/mips.zig @@ -707,12 +707,12 @@ pub const VDSO = struct { }; pub const Flock = extern struct { - l_type: i16, - l_whence: i16, + type: i16, + whence: i16, __pad0: [4]u8, - l_start: off_t, - l_len: off_t, - l_pid: pid_t, + start: off_t, + len: off_t, + pid: pid_t, __unused: [4]u8, }; diff --git a/lib/std/os/linux/powerpc.zig b/lib/std/os/linux/powerpc.zig index f5ef42680b..5decf83a50 100644 --- a/lib/std/os/linux/powerpc.zig +++ b/lib/std/os/linux/powerpc.zig @@ -643,11 +643,11 @@ pub const VDSO = struct { }; pub const Flock = extern struct { - l_type: i16, - l_whence: i16, - l_start: off_t, - l_len: off_t, - l_pid: pid_t, + type: i16, + whence: i16, + start: off_t, + len: off_t, + pid: pid_t, }; pub const msghdr = extern struct { diff --git a/lib/std/os/linux/powerpc64.zig b/lib/std/os/linux/powerpc64.zig index 165ca848c6..74caa71f3d 100644 --- a/lib/std/os/linux/powerpc64.zig +++ b/lib/std/os/linux/powerpc64.zig @@ -617,11 +617,11 @@ pub const VDSO = struct { }; pub const Flock = extern struct { - l_type: i16, - l_whence: i16, - l_start: off_t, - l_len: off_t, - l_pid: pid_t, + type: i16, + whence: i16, + start: off_t, + len: off_t, + pid: pid_t, __unused: [4]u8, }; diff --git a/lib/std/os/linux/riscv64.zig b/lib/std/os/linux/riscv64.zig index 75505442c0..1ce3bed4e2 100644 --- a/lib/std/os/linux/riscv64.zig +++ b/lib/std/os/linux/riscv64.zig @@ -480,11 +480,11 @@ pub const timeval = extern struct { }; pub const Flock = extern struct { - l_type: i16, - l_whence: i16, - l_start: off_t, - l_len: off_t, - l_pid: pid_t, + type: i16, + whence: i16, + start: off_t, + len: off_t, + pid: pid_t, __unused: [4]u8, }; diff --git a/lib/std/os/linux/sparc64.zig b/lib/std/os/linux/sparc64.zig index 3ec9a67210..146ac8d2ee 100644 --- a/lib/std/os/linux/sparc64.zig +++ b/lib/std/os/linux/sparc64.zig @@ -648,11 +648,11 @@ pub const VDSO = struct { }; pub const Flock = extern struct { - l_type: i16, - l_whence: i16, - l_start: off_t, - l_len: off_t, - l_pid: pid_t, + type: i16, + whence: i16, + start: off_t, + len: off_t, + pid: pid_t, }; pub const msghdr = extern struct { diff --git a/lib/std/os/test.zig b/lib/std/os/test.zig index b502a8090a..cc62e6161c 100644 --- a/lib/std/os/test.zig +++ b/lib/std/os/test.zig @@ -821,3 +821,60 @@ test "writev longer than IOV_MAX" { const amt = try file.writev(&iovecs); try testing.expectEqual(@as(usize, os.IOV_MAX), amt); } + +test "POSIX file locking with fcntl" { + if (native_os == .windows or native_os == .wasi) return error.SkipZigTest; + + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + + // Create a temporary lock file + var file = try tmp.dir.createFile("lock", .{ .read = true }); + defer file.close(); + try file.setEndPos(2); + const fd = file.handle; + + // Place an exclusive lock on the first byte, and a shared lock on the second byte: + var struct_flock = std.mem.zeroInit(std.os.Flock, .{ .type = std.os.F.WRLCK }); + _ = try std.os.fcntl(fd, std.os.F.SETLK, @ptrToInt(&struct_flock)); + struct_flock.start = 1; + struct_flock.type = std.os.F.RDLCK; + _ = try std.os.fcntl(fd, std.os.F.SETLK, @ptrToInt(&struct_flock)); + + // Check the locks in a child process: + const pid = try std.os.fork(); + if (pid == 0) { + // child expects be denied the exclusive lock: + struct_flock.start = 0; + struct_flock.type = std.os.F.WRLCK; + try expectError(error.Locked, std.os.fcntl(fd, std.os.F.SETLK, @ptrToInt(&struct_flock))); + // child expects to get the shared lock: + struct_flock.start = 1; + struct_flock.type = std.os.F.RDLCK; + _ = try std.os.fcntl(fd, std.os.F.SETLK, @ptrToInt(&struct_flock)); + // child waits for the exclusive lock in order to test deadlock: + struct_flock.start = 0; + struct_flock.type = std.os.F.WRLCK; + _ = try std.os.fcntl(fd, std.os.F.SETLKW, @ptrToInt(&struct_flock)); + // child exits without continuing: + std.os.exit(0); + } else { + // parent waits for child to get shared lock: + std.time.sleep(1 * std.time.ns_per_ms); + // parent expects deadlock when attempting to upgrade the shared lock to exclusive: + struct_flock.start = 1; + struct_flock.type = std.os.F.WRLCK; + try expectError(error.DeadLock, std.os.fcntl(fd, std.os.F.SETLKW, @ptrToInt(&struct_flock))); + // parent releases exclusive lock: + struct_flock.start = 0; + struct_flock.type = std.os.F.UNLCK; + _ = try std.os.fcntl(fd, std.os.F.SETLK, @ptrToInt(&struct_flock)); + // parent releases shared lock: + struct_flock.start = 1; + struct_flock.type = std.os.F.UNLCK; + _ = try std.os.fcntl(fd, std.os.F.SETLK, @ptrToInt(&struct_flock)); + // parent waits for child: + const result = std.os.waitpid(pid, 0); + try expect(result.status == 0 * 256); + } +}