Use flock instead of fcntl to lock files

`flock` locks based on the file handle, instead of the process id.
This brings the file locking on unix based systems closer to file
locking on Windows.
This commit is contained in:
LeRoyce Pearson 2020-04-02 22:57:02 -06:00
parent e7cf3f92a9
commit ea6525797d
12 changed files with 73 additions and 72 deletions

View File

@ -122,6 +122,7 @@ pub extern "c" fn sysctlnametomib(name: [*:0]const u8, mibp: ?*c_int, sizep: ?*u
pub extern "c" fn tcgetattr(fd: fd_t, termios_p: *termios) c_int;
pub extern "c" fn tcsetattr(fd: fd_t, optional_action: TCSA, termios_p: *const termios) c_int;
pub extern "c" fn fcntl(fd: fd_t, cmd: c_int, ...) c_int;
pub extern "c" fn flock(fd: fd_t, operation: c_int) c_int;
pub extern "c" fn uname(buf: *utsname) c_int;
pub extern "c" fn gethostname(name: [*]u8, len: usize) c_int;

View File

@ -617,14 +617,9 @@ pub const Dir = struct {
else
try os.openatZ(self.fd, sub_path, os_flags, 0);
// use fcntl file locking if no lock flag was given
if (flags.lock and lock_flag == 0) {
// TODO: integrate async I/O
// mem.zeroes is used here because flock's structure can vary across architectures and systems
var flock = mem.zeroes(os.Flock);
flock.l_type = if (flags.write) os.F_WRLCK else os.F_RDLCK;
flock.l_whence = os.SEEK_SET;
_ = try os.fcntl(fd, os.F_SETLKW, @ptrToInt(&flock));
_ = try os.flock(fd, if (flags.write) os.LOCK_EX else os.LOCK_SH);
}
return File{
@ -695,10 +690,7 @@ pub const Dir = struct {
if (flags.lock and lock_flag == 0) {
// TODO: integrate async I/O
// mem.zeroes is used here because flock's structure can vary across architectures and systems
var flock = mem.zeroes(os.Flock);
flock.l_type = os.F_WRLCK;
flock.l_whence = os.SEEK_SET;
_ = try os.fcntl(fd, os.F_SETLKW, @ptrToInt(&flock));
_ = try os.flock(fd, os.LOCK_EX);
}
return File{ .handle = fd, .io_mode = .blocking };
@ -1801,59 +1793,14 @@ const FileLockTestContext = struct {
};
fn run_lock_file_test(contexts: []FileLockTestContext) !void {
var shared_mem: if (builtin.os.tag == .windows) void else []align(mem.page_size) u8 = undefined;
var ctxs: []FileLockTestContext = undefined;
if (builtin.os.tag == .windows) {
ctxs = contexts;
} else {
shared_mem = try std.os.mmap(null, contexts.len * @sizeOf(FileLockTestContext), std.os.PROT_READ | std.os.PROT_WRITE, std.os.MAP_SHARED | std.os.MAP_ANONYMOUS, -1, 0);
const ctxs_ptr = @ptrCast([*]FileLockTestContext, shared_mem.ptr);
ctxs = ctxs_ptr[0..contexts.len];
for (contexts) |context, idx| {
ctxs[idx] = context;
var threads = std.ArrayList(*std.Thread).init(std.testing.allocator);
defer {
for (threads.toSlice()) |thread| {
thread.wait();
}
threads.deinit();
}
if (builtin.os.tag == .windows) {
var threads = std.ArrayList(*std.Thread).init(std.testing.allocator);
defer {
for (threads.toSlice()) |thread| {
thread.wait();
}
threads.deinit();
}
for (ctxs) |*ctx, idx| {
try threads.append(try std.Thread.spawn(ctx, FileLockTestContext.run));
}
} else {
var ctx_opt: ?*FileLockTestContext = null;
for (ctxs) |*ctx| {
const childpid = try std.os.fork();
if (childpid == 0) {
ctx_opt = ctx;
break;
}
ctx.pid = childpid;
}
if (ctx_opt) |ctx| {
ctx.run();
// Exit so we don't have duplicate test processes
std.os.exit(0);
} else {
for (ctxs) |ctx| {
_ = std.os.waitpid(ctx.pid.?, 0);
}
}
}
if (builtin.os.tag != .windows) {
// Copy contexts out of shared memory
for (ctxs) |ctx, idx| {
contexts[idx] = ctx;
}
std.os.munmap(shared_mem);
for (contexts) |*ctx, idx| {
try threads.append(try std.Thread.spawn(ctx, FileLockTestContext.run));
}
}

View File

@ -34,7 +34,7 @@ pub const File = struct {
else => 0o666,
};
pub const OpenError = windows.CreateFileError || os.OpenError || os.FcntlError;
pub const OpenError = windows.CreateFileError || os.OpenError || os.FlockError;
/// TODO https://github.com/ziglang/zig/issues/3802
pub const OpenFlags = struct {

View File

@ -819,36 +819,28 @@ pub const OpenError = error{
SystemFdQuotaExceeded,
NoDevice,
FileNotFound,
/// The path exceeded `MAX_PATH_BYTES` bytes.
NameTooLong,
/// Insufficient kernel memory was available, or
/// the named file is a FIFO and per-user hard limit on
/// memory allocation for pipes has been reached.
SystemResources,
/// The file is too large to be opened. This error is unreachable
/// for 64-bit targets, as well as when opening directories.
FileTooBig,
/// The path refers to directory but the `O_DIRECTORY` flag was not provided.
IsDir,
/// A new path cannot be created because the device has no room for the new file.
/// This error is only reachable when the `O_CREAT` flag is provided.
NoSpaceLeft,
/// A component used as a directory in the path was not, in fact, a directory, or
/// `O_DIRECTORY` was specified and the path was not a directory.
NotDir,
/// The path already exists and the `O_CREAT` and `O_EXCL` flags were provided.
PathAlreadyExists,
DeviceBusy,
/// The underlying filesystem does not support file locks
FileLocksNotSupported
FileLocksNotSupported,
} || UnexpectedError;
/// Open and possibly create a file. Keeps trying if it gets interrupted.
@ -3222,6 +3214,28 @@ pub fn fcntl(fd: fd_t, cmd: i32, arg: usize) FcntlError!usize {
}
}
pub const FlockError = error{
WouldBlock,
/// The kernel ran out of memory for allocating file locks
SystemResources,
} || UnexpectedError;
pub fn flock(fd: fd_t, operation: i32) FlockError!usize {
while (true) {
const rc = system.flock(fd, operation);
switch (errno(rc)) {
0 => return @intCast(usize, rc),
EBADF => unreachable,
EINTR => continue,
EINVAL => unreachable, // invalid parameters
ENOLCK => return error.SystemResources,
EWOULDBLOCK => return error.WouldBlock, // TODO: integrate with async instead of just returning an error
else => |err| return unexpectedErrno(err),
}
}
}
pub const RealPathError = error{
FileNotFound,
AccessDenied,

View File

@ -1394,3 +1394,8 @@ pub const F_UNLCK = 2;
/// exclusive or write lock
pub const F_WRLCK = 3;
pub const LOCK_SH = 1;
pub const LOCK_EX = 2;
pub const LOCK_UN = 8;
pub const LOCK_NB = 4;

View File

@ -462,6 +462,11 @@ pub const F_GETOWN_EX = 16;
pub const F_GETOWNER_UIDS = 17;
pub const LOCK_SH = 1;
pub const LOCK_EX = 2;
pub const LOCK_UN = 8;
pub const LOCK_NB = 4;
/// stack-like segment
pub const MAP_GROWSDOWN = 0x0100;

View File

@ -349,6 +349,11 @@ pub const F_RDLCK = 0;
pub const F_WRLCK = 1;
pub const F_UNLCK = 2;
pub const LOCK_SH = 1;
pub const LOCK_EX = 2;
pub const LOCK_UN = 8;
pub const LOCK_NB = 4;
pub const F_SETOWN_EX = 15;
pub const F_GETOWN_EX = 16;

View File

@ -482,6 +482,11 @@ pub const F_RDLCK = 0;
pub const F_WRLCK = 1;
pub const F_UNLCK = 2;
pub const LOCK_SH = 1;
pub const LOCK_EX = 2;
pub const LOCK_UN = 8;
pub const LOCK_NB = 4;
pub const F_SETOWN_EX = 15;
pub const F_GETOWN_EX = 16;

View File

@ -424,6 +424,11 @@ pub const F_RDLCK = 0;
pub const F_WRLCK = 1;
pub const F_UNLCK = 2;
pub const LOCK_SH = 1;
pub const LOCK_EX = 2;
pub const LOCK_UN = 8;
pub const LOCK_NB = 4;
pub const F_SETOWN_EX = 15;
pub const F_GETOWN_EX = 16;

View File

@ -343,6 +343,11 @@ pub const F_RDLCK = 0;
pub const F_WRLCK = 1;
pub const F_UNLCK = 2;
pub const LOCK_SH = 1;
pub const LOCK_EX = 2;
pub const LOCK_UN = 8;
pub const LOCK_NB = 4;
pub const F_SETOWN_EX = 15;
pub const F_GETOWN_EX = 16;

View File

@ -462,6 +462,11 @@ pub const REG_TRAPNO = 20;
pub const REG_OLDMASK = 21;
pub const REG_CR2 = 22;
pub const LOCK_SH = 1;
pub const LOCK_EX = 2;
pub const LOCK_UN = 8;
pub const LOCK_NB = 4;
pub const F_RDLCK = 0;
pub const F_WRLCK = 1;
pub const F_UNLCK = 2;

View File

@ -592,6 +592,10 @@ pub fn fcntl(fd: fd_t, cmd: i32, arg: usize) usize {
return syscall3(.fcntl, @bitCast(usize, @as(isize, fd)), @bitCast(usize, @as(isize, cmd)), arg);
}
pub fn flock(fd: fd_t, operation: i32) usize {
return syscall2(.flock, @bitCast(usize, @as(isize, fd)), @bitCast(usize, @as(isize, operation)));
}
var vdso_clock_gettime = @ptrCast(?*const c_void, init_vdso_clock_gettime);
// We must follow the C calling convention when we call into the VDSO