Commit 078aa5f7b2

Anthony Carrico <acarrico@memebeam.org>
2021-09-25 23:32:18
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.
1 parent c6cd919
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 {
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 {
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,
 };
 
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 {
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 {
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 {
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,
 };
 
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,
 };
 
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,
 };
 
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,
 };
 
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 {
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,
 };
 
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,
 };
 
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 {
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);
+    }
+}
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,
             };
         }
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,
             };
         }