Merge pull request #4711 from leroycep/feature-file-locks

Add lock option to File.OpenFlags and File.CreateFlags
This commit is contained in:
Andrew Kelley 2020-04-10 15:00:45 -04:00 committed by GitHub
commit a6e288d5fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 526 additions and 34 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

@ -357,6 +357,7 @@ pub const ChildProcess = struct {
error.NoSpaceLeft => unreachable,
error.FileTooBig => unreachable,
error.DeviceBusy => unreachable,
error.FileLocksNotSupported => unreachable,
else => |e| return e,
}
else

View File

@ -594,8 +594,19 @@ pub const Dir = struct {
const path_w = try os.windows.cStrToPrefixedFileW(sub_path);
return self.openFileW(&path_w, flags);
}
// Use the O_ locking flags if the os supports them
// (Or if it's darwin, as darwin's `open` doesn't support the O_SYNC flag)
const has_flock_open_flags = @hasDecl(os, "O_EXLOCK") and !builtin.os.tag.isDarwin();
const nonblocking_lock_flag = if (has_flock_open_flags and flags.lock_nonblocking) (os.O_NONBLOCK | os.O_SYNC) else @as(u32, 0);
const lock_flag: u32 = if (has_flock_open_flags) switch (flags.lock) {
.None => @as(u32, 0),
.Shared => os.O_SHLOCK | nonblocking_lock_flag,
.Exclusive => os.O_EXLOCK | nonblocking_lock_flag,
} else 0;
const O_LARGEFILE = if (@hasDecl(os, "O_LARGEFILE")) os.O_LARGEFILE else 0;
const os_flags = O_LARGEFILE | os.O_CLOEXEC | if (flags.write and flags.read)
const os_flags = lock_flag | O_LARGEFILE | os.O_CLOEXEC | if (flags.write and flags.read)
@as(u32, os.O_RDWR)
else if (flags.write)
@as(u32, os.O_WRONLY)
@ -605,6 +616,17 @@ pub const Dir = struct {
try std.event.Loop.instance.?.openatZ(self.fd, sub_path, os_flags, 0)
else
try os.openatZ(self.fd, sub_path, os_flags, 0);
if (!has_flock_open_flags and flags.lock != .None) {
// TODO: integrate async I/O
const lock_nonblocking = if (flags.lock_nonblocking) os.LOCK_NB else @as(i32, 0);
try os.flock(fd, switch (flags.lock) {
.None => unreachable,
.Shared => os.LOCK_SH | lock_nonblocking,
.Exclusive => os.LOCK_EX | lock_nonblocking,
});
}
return File{
.handle = fd,
.io_mode = .blocking,
@ -622,8 +644,15 @@ 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);
const share_access = switch (flags.lock) {
.None => @as(?w.ULONG, null),
.Shared => w.FILE_SHARE_READ | w.FILE_SHARE_DELETE,
.Exclusive => w.FILE_SHARE_DELETE,
};
return @as(File, .{
.handle = try os.windows.OpenFileW(self.fd, sub_path_w, null, access_mask, w.FILE_OPEN),
.handle = try os.windows.OpenFileW(self.fd, sub_path_w, null, access_mask, share_access, flags.lock_nonblocking, w.FILE_OPEN),
.io_mode = .blocking,
});
}
@ -648,8 +677,19 @@ pub const Dir = struct {
const path_w = try os.windows.cStrToPrefixedFileW(sub_path_c);
return self.createFileW(&path_w, flags);
}
// Use the O_ locking flags if the os supports them
// (Or if it's darwin, as darwin's `open` doesn't support the O_SYNC flag)
const has_flock_open_flags = @hasDecl(os, "O_EXLOCK") and !builtin.os.tag.isDarwin();
const nonblocking_lock_flag = if (has_flock_open_flags and flags.lock_nonblocking) (os.O_NONBLOCK | os.O_SYNC) else @as(u32, 0);
const lock_flag: u32 = if (has_flock_open_flags) switch (flags.lock) {
.None => @as(u32, 0),
.Shared => os.O_SHLOCK,
.Exclusive => os.O_EXLOCK,
} else 0;
const O_LARGEFILE = if (@hasDecl(os, "O_LARGEFILE")) os.O_LARGEFILE else 0;
const os_flags = O_LARGEFILE | os.O_CREAT | os.O_CLOEXEC |
const os_flags = lock_flag | O_LARGEFILE | os.O_CREAT | os.O_CLOEXEC |
(if (flags.truncate) @as(u32, os.O_TRUNC) else 0) |
(if (flags.read) @as(u32, os.O_RDWR) else os.O_WRONLY) |
(if (flags.exclusive) @as(u32, os.O_EXCL) else 0);
@ -657,6 +697,17 @@ pub const Dir = struct {
try std.event.Loop.instance.?.openatZ(self.fd, sub_path_c, os_flags, flags.mode)
else
try os.openatZ(self.fd, sub_path_c, os_flags, flags.mode);
if (!has_flock_open_flags and flags.lock != .None) {
// TODO: integrate async I/O
const lock_nonblocking = if (flags.lock_nonblocking) os.LOCK_NB else @as(i32, 0);
try os.flock(fd, switch (flags.lock) {
.None => unreachable,
.Shared => os.LOCK_SH | lock_nonblocking,
.Exclusive => os.LOCK_EX | lock_nonblocking,
});
}
return File{ .handle = fd, .io_mode = .blocking };
}
@ -672,8 +723,15 @@ pub const Dir = struct {
@as(u32, w.FILE_OVERWRITE_IF)
else
@as(u32, w.FILE_OPEN_IF);
const share_access = switch (flags.lock) {
.None => @as(?w.ULONG, null),
.Shared => w.FILE_SHARE_READ | w.FILE_SHARE_DELETE,
.Exclusive => w.FILE_SHARE_DELETE,
};
return @as(File, .{
.handle = try os.windows.OpenFileW(self.fd, sub_path_w, null, access_mask, creation),
.handle = try os.windows.OpenFileW(self.fd, sub_path_w, null, access_mask, share_access, flags.lock_nonblocking, creation),
.io_mode = .blocking,
});
}
@ -802,6 +860,7 @@ pub const Dir = struct {
error.IsDir => unreachable, // we're providing O_DIRECTORY
error.NoSpaceLeft => unreachable, // not providing O_CREAT
error.PathAlreadyExists => unreachable, // not providing O_CREAT
error.FileLocksNotSupported => unreachable, // locking folders is not supported
else => |e| return e,
};
return Dir{ .fd = fd };
@ -1508,7 +1567,7 @@ pub fn walkPath(allocator: *Allocator, dir_path: []const u8) !Walker {
return walker;
}
pub const OpenSelfExeError = os.OpenError || os.windows.CreateFileError || SelfExePathError;
pub const OpenSelfExeError = os.OpenError || os.windows.CreateFileError || SelfExePathError || os.FlockError;
pub fn openSelfExe() OpenSelfExeError!File {
if (builtin.os.tag == .linux) {
@ -1624,3 +1683,158 @@ test "" {
_ = @import("fs/get_app_data_dir.zig");
_ = @import("fs/watch.zig");
}
const FILE_LOCK_TEST_SLEEP_TIME = 5 * std.time.millisecond;
test "open file with exclusive nonblocking lock twice" {
const dir = cwd();
const filename = "file_nonblocking_lock_test.txt";
const file1 = try dir.createFile(filename, .{ .lock = .Exclusive, .lock_nonblocking = true });
defer file1.close();
const file2 = dir.createFile(filename, .{ .lock = .Exclusive, .lock_nonblocking = true });
std.debug.assert(std.meta.eql(file2, error.WouldBlock));
dir.deleteFile(filename) catch |err| switch (err) {
error.FileNotFound => {},
else => return err,
};
}
test "open file with lock twice, make sure it wasn't open at the same time" {
if (builtin.single_threaded) return;
const filename = "file_lock_test.txt";
var contexts = [_]FileLockTestContext{
.{ .filename = filename, .create = true, .lock = .Exclusive },
.{ .filename = filename, .create = true, .lock = .Exclusive },
};
try run_lock_file_test(&contexts);
// Check for an error
var was_error = false;
for (contexts) |context, idx| {
if (context.err) |err| {
was_error = true;
std.debug.warn("\nError in context {}: {}\n", .{ idx, err });
}
}
if (was_error) builtin.panic("There was an error in contexts", null);
std.debug.assert(!contexts[0].overlaps(&contexts[1]));
cwd().deleteFile(filename) catch |err| switch (err) {
error.FileNotFound => {},
else => return err,
};
}
test "create file, lock and read from multiple process at once" {
if (builtin.single_threaded) return;
const filename = "file_read_lock_test.txt";
const filedata = "Hello, world!\n";
try std.fs.cwd().writeFile(filename, filedata);
var contexts = [_]FileLockTestContext{
.{ .filename = filename, .create = false, .lock = .Shared },
.{ .filename = filename, .create = false, .lock = .Shared },
.{ .filename = filename, .create = false, .lock = .Exclusive },
};
try run_lock_file_test(&contexts);
var was_error = false;
for (contexts) |context, idx| {
if (context.err) |err| {
was_error = true;
std.debug.warn("\nError in context {}: {}\n", .{ idx, err });
}
}
if (was_error) builtin.panic("There was an error in contexts", null);
std.debug.assert(contexts[0].overlaps(&contexts[1]));
std.debug.assert(!contexts[2].overlaps(&contexts[0]));
std.debug.assert(!contexts[2].overlaps(&contexts[1]));
if (contexts[0].bytes_read.? != filedata.len) {
std.debug.warn("\n bytes_read: {}, expected: {} \n", .{ contexts[0].bytes_read, filedata.len });
}
std.debug.assert(contexts[0].bytes_read.? == filedata.len);
std.debug.assert(contexts[1].bytes_read.? == filedata.len);
cwd().deleteFile(filename) catch |err| switch (err) {
error.FileNotFound => {},
else => return err,
};
}
const FileLockTestContext = struct {
filename: []const u8,
pid: if (builtin.os.tag == .windows) ?void else ?std.os.pid_t = null,
// use file.createFile
create: bool,
// the type of lock to use
lock: File.Lock,
// Output variables
err: ?(File.OpenError || std.os.ReadError) = null,
start_time: u64 = 0,
end_time: u64 = 0,
bytes_read: ?usize = null,
fn overlaps(self: *const @This(), other: *const @This()) bool {
return (self.start_time < other.end_time) and (self.end_time > other.start_time);
}
fn run(ctx: *@This()) void {
var file: File = undefined;
if (ctx.create) {
file = cwd().createFile(ctx.filename, .{ .lock = ctx.lock }) catch |err| {
ctx.err = err;
return;
};
} else {
file = cwd().openFile(ctx.filename, .{ .lock = ctx.lock }) catch |err| {
ctx.err = err;
return;
};
}
defer file.close();
ctx.start_time = std.time.milliTimestamp();
if (!ctx.create) {
var buffer: [100]u8 = undefined;
ctx.bytes_read = 0;
while (true) {
const amt = file.read(buffer[0..]) catch |err| {
ctx.err = err;
return;
};
if (amt == 0) break;
ctx.bytes_read.? += amt;
}
}
std.time.sleep(FILE_LOCK_TEST_SLEEP_TIME);
ctx.end_time = std.time.milliTimestamp();
}
};
fn run_lock_file_test(contexts: []FileLockTestContext) !void {
var threads = std.ArrayList(*std.Thread).init(std.testing.allocator);
defer {
for (threads.toSlice()) |thread| {
thread.wait();
}
threads.deinit();
}
for (contexts) |*ctx, idx| {
try threads.append(try std.Thread.spawn(ctx, FileLockTestContext.run));
}
}

View File

@ -34,13 +34,37 @@ pub const File = struct {
else => 0o666,
};
pub const OpenError = windows.CreateFileError || os.OpenError;
pub const OpenError = windows.CreateFileError || os.OpenError || os.FlockError;
pub const Lock = enum {
None, Shared, Exclusive
};
/// TODO https://github.com/ziglang/zig/issues/3802
pub const OpenFlags = struct {
read: bool = true,
write: bool = false,
/// Open the file with a lock to prevent other processes from accessing it at the
/// same time. An exclusive lock will prevent other processes from acquiring a lock.
/// A shared lock will prevent other processes from acquiring a exclusive lock, but
/// doesn't prevent other process from getting their own shared locks.
///
/// Note that the lock is only advisory on Linux, except in very specific cirsumstances[1].
/// This means that a process that does not respect the locking API can still get access
/// to the file, despite the lock.
///
/// Windows' file locks are mandatory, and any process attempting to access the file will
/// receive an error.
///
/// [1]: https://www.kernel.org/doc/Documentation/filesystems/mandatory-locking.txt
lock: Lock = .None,
/// Sets whether or not to wait until the file is locked to return. If set to true,
/// `error.WouldBlock` will be returned. Otherwise, the file will wait until the file
/// is available to proceed.
lock_nonblocking: bool = false,
/// This prevents `O_NONBLOCK` from being passed even if `std.io.is_async`.
/// It allows the use of `noasync` when calling functions related to opening
/// the file, reading, and writing.
@ -60,6 +84,26 @@ pub const File = struct {
/// `error.FileAlreadyExists` to be returned.
exclusive: bool = false,
/// Open the file with a lock to prevent other processes from accessing it at the
/// same time. An exclusive lock will prevent other processes from acquiring a lock.
/// A shared lock will prevent other processes from acquiring a exclusive lock, but
/// doesn't prevent other process from getting their own shared locks.
///
/// Note that the lock is only advisory on Linux, except in very specific cirsumstances[1].
/// This means that a process that does not respect the locking API can still get access
/// to the file, despite the lock.
///
/// Windows' file locks are mandatory, and any process attempting to access the file will
/// receive an error.
///
/// [1]: https://www.kernel.org/doc/Documentation/filesystems/mandatory-locking.txt
lock: Lock = .None,
/// Sets whether or not to wait until the file is locked to return. If set to true,
/// `error.WouldBlock` will be returned. Otherwise, the file will wait until the file
/// is available to proceed.
lock_nonblocking: bool = false,
/// For POSIX systems this is the file system mode the file will
/// be created with.
mode: Mode = default_mode,

View File

@ -846,6 +846,9 @@ pub const OpenError = error{
/// 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,
} || UnexpectedError;
/// Open and possibly create a file. Keeps trying if it gets interrupted.
@ -931,6 +934,7 @@ pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t)
EPERM => return error.AccessDenied,
EEXIST => return error.PathAlreadyExists,
EBUSY => return error.DeviceBusy,
EOPNOTSUPP => return error.FileLocksNotSupported,
else => |err| return unexpectedErrno(err),
}
}
@ -1676,7 +1680,10 @@ pub fn renameatW(
ReplaceIfExists: windows.BOOLEAN,
) RenameError!void {
const access_mask = windows.SYNCHRONIZE | windows.GENERIC_WRITE | windows.DELETE;
const src_fd = try windows.OpenFileW(old_dir_fd, old_path, null, access_mask, windows.FILE_OPEN);
const src_fd = windows.OpenFileW(old_dir_fd, old_path, null, access_mask, null, false, windows.FILE_OPEN) catch |err| switch (err) {
error.WouldBlock => unreachable,
else => |e| return e,
};
defer windows.CloseHandle(src_fd);
const struct_buf_len = @sizeOf(windows.FILE_RENAME_INFORMATION) + (MAX_PATH_BYTES - 1);
@ -3218,6 +3225,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!void {
while (true) {
const rc = system.flock(fd, operation);
switch (errno(rc)) {
0 => return,
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,
@ -3269,7 +3298,10 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP
return realpathW(&pathname_w, out_buffer);
}
if (builtin.os.tag == .linux and !builtin.link_libc) {
const fd = try openZ(pathname, linux.O_PATH | linux.O_NONBLOCK | linux.O_CLOEXEC, 0);
const fd = openZ(pathname, linux.O_PATH | linux.O_NONBLOCK | linux.O_CLOEXEC, 0) catch |err| switch (err) {
error.FileLocksNotSupported => unreachable,
else => |e| return e,
};
defer close(fd);
var procfs_buf: ["/proc/self/fd/-2147483648".len:0]u8 = undefined;

View File

@ -55,6 +55,14 @@ pub const mach_timebase_info_data = extern struct {
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,
};
/// Renamed to Stat to not conflict with the stat function.
/// atime, mtime, and ctime have functions to return `timespec`,
/// because although this is a POSIX API, the layout and names of
@ -1386,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

@ -697,6 +697,11 @@ pub const F_DUP2FD = 10;
pub const F_DUPFD_CLOEXEC = 17;
pub const F_DUP2FD_CLOEXEC = 18;
pub const LOCK_SH = 1;
pub const LOCK_EX = 2;
pub const LOCK_UN = 8;
pub const LOCK_NB = 4;
pub const Flock = extern struct {
l_start: off_t,
l_len: off_t,

View File

@ -51,6 +51,16 @@ pub const dl_phdr_info = extern struct {
dlpi_phnum: u16,
};
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,
__unused: [4]u8,
};
pub const msghdr = extern struct {
/// optional address
msg_name: ?*sockaddr,
@ -315,6 +325,9 @@ pub const O_WRONLY = 0x0001;
pub const O_RDWR = 0x0002;
pub const O_ACCMODE = 0x0003;
pub const O_SHLOCK = 0x0010;
pub const O_EXLOCK = 0x0020;
pub const O_CREAT = 0x0200;
pub const O_EXCL = 0x0800;
pub const O_NOCTTY = 0x8000;
@ -350,6 +363,15 @@ pub const F_GETLK = 5;
pub const F_SETLK = 6;
pub const F_SETLKW = 7;
pub const F_RDLCK = 1;
pub const F_WRLCK = 3;
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

@ -8,6 +8,7 @@ const stack_t = linux.stack_t;
const sigset_t = linux.sigset_t;
const uid_t = linux.uid_t;
const gid_t = linux.gid_t;
const pid_t = linux.pid_t;
pub const SYS = extern enum(usize) {
restart_syscall = 0,
@ -452,11 +453,20 @@ pub const F_GETLK = 12;
pub const F_SETLK = 13;
pub const F_SETLKW = 14;
pub const F_RDLCK = 0;
pub const F_WRLCK = 1;
pub const F_UNLCK = 2;
pub const F_SETOWN_EX = 15;
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;
@ -499,6 +509,16 @@ pub const HWCAP_IDIV = HWCAP_IDIVA | HWCAP_IDIVT;
pub const HWCAP_LPAE = 1 << 20;
pub const HWCAP_EVTSTRM = 1 << 21;
pub const Flock = extern struct {
l_type: i16,
l_whence: i16,
__pad0: [4]u8,
l_start: off_t,
l_len: off_t,
l_pid: pid_t,
__unused: [4]u8,
};
pub const msghdr = extern struct {
msg_name: ?*sockaddr,
msg_namelen: socklen_t,

View File

@ -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;
pub const SYS = extern enum(usize) {
@ -344,6 +345,15 @@ pub const F_GETLK = 5;
pub const F_SETLK = 6;
pub const F_SETLKW = 7;
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;
@ -367,6 +377,15 @@ pub const MAP_NORESERVE = 0x4000;
pub const VDSO_CGT_SYM = "__kernel_clock_gettime";
pub const VDSO_CGT_VER = "LINUX_2.6.39";
pub const Flock = extern struct {
l_type: i16,
l_whence: i16,
l_start: off_t,
l_len: off_t,
l_pid: pid_t,
__unused: [4]u8,
};
pub const msghdr = extern struct {
msg_name: ?*sockaddr,
msg_namelen: socklen_t,

View File

@ -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;
@ -477,6 +478,15 @@ pub const F_GETLK = 12;
pub const F_SETLK = 13;
pub const F_SETLKW = 14;
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;
@ -494,6 +504,14 @@ pub const MMAP2_UNIT = 4096;
pub const VDSO_CGT_SYM = "__vdso_clock_gettime";
pub const VDSO_CGT_VER = "LINUX_2.6";
pub const Flock = extern struct {
l_type: i16,
l_whence: i16,
l_start: off_t,
l_len: off_t,
l_pid: pid_t,
};
pub const msghdr = extern struct {
msg_name: ?*sockaddr,
msg_namelen: socklen_t,

View File

@ -5,6 +5,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;
pub const SYS = extern enum(usize) {
pub const Linux = 4000;
@ -419,6 +420,15 @@ pub const F_GETLK = 33;
pub const F_SETLK = 34;
pub const F_SETLKW = 35;
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;
@ -464,6 +474,16 @@ pub const SO_RCVBUFFORCE = 33;
pub const VDSO_CGT_SYM = "__kernel_clock_gettime";
pub const VDSO_CGT_VER = "LINUX_2.6.39";
pub const Flock = extern struct {
l_type: i16,
l_whence: i16,
__pad0: [4]u8,
l_start: off_t,
l_len: off_t,
l_pid: pid_t,
__unused: [4]u8,
};
pub const blksize_t = i32;
pub const nlink_t = u32;
pub const time_t = isize;

View File

@ -2,6 +2,7 @@
const std = @import("../../../std.zig");
const uid_t = std.os.linux.uid_t;
const gid_t = std.os.linux.gid_t;
const pid_t = std.os.linux.pid_t;
pub const SYS = extern enum(usize) {
io_setup = 0,
@ -338,6 +339,15 @@ pub const F_GETOWN = 9;
pub const F_SETSIG = 10;
pub const F_GETSIG = 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;
@ -356,6 +366,15 @@ pub const timespec = extern struct {
tv_nsec: isize,
};
pub const Flock = extern struct {
l_type: i16,
l_whence: i16,
l_start: off_t,
l_len: off_t,
l_pid: pid_t,
__unused: [4]u8,
};
/// Renamed to Stat to not conflict with the stat function.
/// atime, mtime, and ctime have functions to return `timespec`,
/// because although this is a POSIX API, the layout and names of

View File

@ -462,6 +462,23 @@ 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;
pub const Flock = extern struct {
l_type: i16,
l_whence: i16,
l_start: off_t,
l_len: off_t,
l_pid: pid_t,
};
pub const msghdr = extern struct {
msg_name: ?*sockaddr,
msg_namelen: socklen_t,

View File

@ -35,6 +35,14 @@ pub const dl_phdr_info = extern struct {
dlpi_phnum: u16,
};
pub const Flock = extern struct {
l_start: off_t,
l_len: off_t,
l_pid: pid_t,
l_type: i16,
l_whence: i16,
};
pub const addrinfo = extern struct {
flags: i32,
family: i32,
@ -435,6 +443,15 @@ pub const F_GETLK = 7;
pub const F_SETLK = 8;
pub const F_SETLKW = 9;
pub const F_RDLCK = 1;
pub const F_WRLCK = 3;
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 FD_CLOEXEC = 1;
pub const SEEK_SET = 0;

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

View File

@ -98,6 +98,7 @@ pub const OpenError = error{
PathAlreadyExists,
Unexpected,
NameTooLong,
WouldBlock,
};
/// TODO rename to CreateFileW
@ -107,6 +108,8 @@ pub fn OpenFileW(
sub_path_w: [*:0]const u16,
sa: ?*SECURITY_ATTRIBUTES,
access_mask: ACCESS_MASK,
share_access_opt: ?ULONG,
share_access_nonblocking: bool,
creation: ULONG,
) OpenError!HANDLE {
if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
@ -135,32 +138,46 @@ pub fn OpenFileW(
.SecurityQualityOfService = null,
};
var io: IO_STATUS_BLOCK = undefined;
const rc = ntdll.NtCreateFile(
&result,
access_mask,
&attr,
&io,
null,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE,
creation,
FILE_NON_DIRECTORY_FILE | 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 unexpectedStatus(rc),
const share_access = share_access_opt orelse (FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE);
var delay: usize = 1;
while (true) {
const rc = ntdll.NtCreateFile(
&result,
access_mask,
&attr,
&io,
null,
FILE_ATTRIBUTE_NORMAL,
share_access,
creation,
FILE_NON_DIRECTORY_FILE | 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 => {
if (share_access_nonblocking) {
return error.WouldBlock;
}
std.time.sleep(delay);
if (delay < 1 * std.time.ns_per_s) {
delay *= 2;
}
continue; // TODO: don't loop for async
},
.ACCESS_DENIED => return error.AccessDenied,
.PIPE_BUSY => return error.PipeBusy,
.OBJECT_PATH_SYNTAX_BAD => unreachable,
.OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
else => return unexpectedStatus(rc),
}
}
}

View File

@ -468,6 +468,8 @@ pub const NativeTargetInfo = struct {
error.InvalidUtf8 => unreachable,
error.BadPathName => unreachable,
error.PipeBusy => unreachable,
error.FileLocksNotSupported => unreachable,
error.WouldBlock => unreachable,
error.IsDir,
error.NotDir,

View File

@ -116,6 +116,8 @@ const Error = extern enum {
UnknownClangOption,
NestedResponseFile,
ZigIsTheCCompiler,
FileBusy,
Locked,
};
const FILE = std.c.FILE;
@ -847,6 +849,7 @@ export fn stage2_libc_parse(stage1_libc: *Stage2LibCInstallation, libc_file_z: [
error.NoDevice => return .NoDevice,
error.NotDir => return .NotDir,
error.DeviceBusy => return .DeviceBusy,
error.FileLocksNotSupported => unreachable,
};
stage1_libc.initFromStage2(libc);
return .None;

View File

@ -86,6 +86,8 @@ const char *err_str(Error err) {
case ErrorUnknownClangOption: return "unknown Clang option";
case ErrorNestedResponseFile: return "nested response file";
case ErrorZigIsTheCCompiler: return "Zig was not provided with libc installation information, and so it does not know where the libc paths are on the system. Zig attempted to use the system C compiler to find out where the libc paths are, but discovered that Zig is being used as the system C compiler.";
case ErrorFileBusy: return "file is busy";
case ErrorLocked: return "file is locked by another process";
}
return "(invalid error)";
}

View File

@ -108,6 +108,8 @@ enum Error {
ErrorUnknownClangOption,
ErrorNestedResponseFile,
ErrorZigIsTheCCompiler,
ErrorFileBusy,
ErrorLocked,
};
// ABI warning