diff --git a/lib/std/c.zig b/lib/std/c.zig index 1261974d65..114d12afe8 100644 --- 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 index e1c489bf05..1bd0becfd0 100644 --- 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 index 1c7472c3f6..b924a498b7 100644 --- 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 index b158be0a9b..2a6ad875c5 100644 --- 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 index bb206b289f..0ce10601b7 100644 --- 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 index 6808af0315..3fa37fe0f5 100644 --- 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 index 1e869679ef..6a6c871fc5 100644 --- 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 index 6f97884e16..9999dca62f 100644 --- 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 index 7dd6941b5b..b32d3ef9d4 100644 --- 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 index 7e745a51d8..0745f19fb0 100644 --- 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 index f6dfef29bd..868e4f537b 100644 --- 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 index d1f088be40..5946e821ad 100644 --- 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 index daeb3a7819..4ac30a979b 100644 --- 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 index 4fd9922674..c87bca6f8e 100644 --- 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 index 21f75a0414..91e101620f 100644 --- 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 index 1ed8370274..87ee198985 100644 --- 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 index f6186acc8d..72593b92c7 100644 --- 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 index c65e8911dc..f6a45860be 100644 --- 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 index 3f96ff2110..9d8c8446d5 100644 --- 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 index 0ca7d25b79..d8bb4ac8a2 100644 --- 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 index 7dd2de988e..0acef166d7 100644 --- a/src/stage2.h +++ b/src/stage2.h @@ -108,6 +108,8 @@ enum Error { ErrorUnknownClangOption, ErrorNestedResponseFile, ErrorZigIsTheCCompiler, + ErrorFileBusy, + ErrorLocked, }; // ABI warning