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.
This commit is contained in:
Anthony Carrico 2021-09-25 17:32:18 -04:00 committed by Veikka Tuominen
parent c6cd919a18
commit 078aa5f7b2
17 changed files with 150 additions and 73 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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