commit a6e288d5fe51d5373fa995b5eec2dd2325c1ea9f (tree)
parent 121307679bd1ffeaa8a290a826396662f26a4ac1
Author: Andrew Kelley <andrew@ziglang.org>
Date: Fri, 10 Apr 2020 15:00:45 -0400
Merge pull request #4711 from leroycep/feature-file-locks
Add lock option to File.OpenFlags and File.CreateFlags
Diffstat:
21 files changed, 526 insertions(+), 34 deletions(-)
diff --git a/lib/std/c.zig b/lib/std/c.zig
@@ -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;
diff --git a/lib/std/child_process.zig b/lib/std/child_process.zig
@@ -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
diff --git a/lib/std/fs.zig b/lib/std/fs.zig
@@ -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));
+ }
+}
diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig
@@ -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,
diff --git a/lib/std/os.zig b/lib/std/os.zig
@@ -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;
diff --git a/lib/std/os/bits/darwin.zig b/lib/std/os/bits/darwin.zig
@@ -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;
diff --git a/lib/std/os/bits/dragonfly.zig b/lib/std/os/bits/dragonfly.zig
@@ -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,
diff --git a/lib/std/os/bits/freebsd.zig b/lib/std/os/bits/freebsd.zig
@@ -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;
diff --git a/lib/std/os/bits/linux/arm-eabi.zig b/lib/std/os/bits/linux/arm-eabi.zig
@@ -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,
diff --git a/lib/std/os/bits/linux/arm64.zig b/lib/std/os/bits/linux/arm64.zig
@@ -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,
diff --git a/lib/std/os/bits/linux/i386.zig b/lib/std/os/bits/linux/i386.zig
@@ -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,
diff --git a/lib/std/os/bits/linux/mipsel.zig b/lib/std/os/bits/linux/mipsel.zig
@@ -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;
diff --git a/lib/std/os/bits/linux/riscv64.zig b/lib/std/os/bits/linux/riscv64.zig
@@ -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
diff --git a/lib/std/os/bits/linux/x86_64.zig b/lib/std/os/bits/linux/x86_64.zig
@@ -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,
diff --git a/lib/std/os/bits/netbsd.zig b/lib/std/os/bits/netbsd.zig
@@ -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;
diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig
@@ -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
diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig
@@ -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),
+ }
}
}
diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig
@@ -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,
diff --git a/src-self-hosted/stage2.zig b/src-self-hosted/stage2.zig
@@ -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;
diff --git a/src/error.cpp b/src/error.cpp
@@ -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)";
}
diff --git a/src/stage2.h b/src/stage2.h
@@ -108,6 +108,8 @@ enum Error {
ErrorUnknownClangOption,
ErrorNestedResponseFile,
ErrorZigIsTheCCompiler,
+ ErrorFileBusy,
+ ErrorLocked,
};
// ABI warning