| author | |
| committer | |
| log | ea6525797d0db83a8560179b317837cb58634049 |
| tree | 48a190000f83ba3303a9a3cae797430303c883b9 |
| parent | e7cf3f92a98cd2bd94d9d94fb361819b6ab89e8d |
`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.12 files changed, 73 insertions(+), 72 deletions(-)
lib/std/c.zig+1| ... | @@ -122,6 +122,7 @@ pub extern "c" fn sysctlnametomib(name: [*:0]const u8, mibp: ?*c_int, sizep: ?*u | ... | @@ -122,6 +122,7 @@ pub extern "c" fn sysctlnametomib(name: [*:0]const u8, mibp: ?*c_int, sizep: ?*u |
| 122 | pub extern "c" fn tcgetattr(fd: fd_t, termios_p: *termios) c_int; | 122 | pub extern "c" fn tcgetattr(fd: fd_t, termios_p: *termios) c_int; |
| 123 | pub extern "c" fn tcsetattr(fd: fd_t, optional_action: TCSA, termios_p: *const termios) c_int; | 123 | pub extern "c" fn tcsetattr(fd: fd_t, optional_action: TCSA, termios_p: *const termios) c_int; |
| 124 | pub extern "c" fn fcntl(fd: fd_t, cmd: c_int, ...) c_int; | 124 | pub extern "c" fn fcntl(fd: fd_t, cmd: c_int, ...) c_int; |
| 125 | pub extern "c" fn flock(fd: fd_t, operation: c_int) c_int; | ||
| 125 | pub extern "c" fn uname(buf: *utsname) c_int; | 126 | pub extern "c" fn uname(buf: *utsname) c_int; |
| 126 | 127 | ||
| 127 | pub extern "c" fn gethostname(name: [*]u8, len: usize) c_int; | 128 | pub extern "c" fn gethostname(name: [*]u8, len: usize) c_int; |
lib/std/fs.zig+9-62| ... | @@ -617,14 +617,9 @@ pub const Dir = struct { | ... | @@ -617,14 +617,9 @@ pub const Dir = struct { |
| 617 | else | 617 | else |
| 618 | try os.openatZ(self.fd, sub_path, os_flags, 0); | 618 | try os.openatZ(self.fd, sub_path, os_flags, 0); |
| 619 | 619 | ||
| 620 | // use fcntl file locking if no lock flag was given | ||
| 621 | if (flags.lock and lock_flag == 0) { | 620 | if (flags.lock and lock_flag == 0) { |
| 622 | // TODO: integrate async I/O | 621 | // TODO: integrate async I/O |
| 623 | // mem.zeroes is used here because flock's structure can vary across architectures and systems | 622 | _ = try os.flock(fd, if (flags.write) os.LOCK_EX else os.LOCK_SH); |
| 624 | var flock = mem.zeroes(os.Flock); | ||
| 625 | flock.l_type = if (flags.write) os.F_WRLCK else os.F_RDLCK; | ||
| 626 | flock.l_whence = os.SEEK_SET; | ||
| 627 | _ = try os.fcntl(fd, os.F_SETLKW, @ptrToInt(&flock)); | ||
| 628 | } | 623 | } |
| 629 | 624 | ||
| 630 | return File{ | 625 | return File{ |
| ... | @@ -695,10 +690,7 @@ pub const Dir = struct { | ... | @@ -695,10 +690,7 @@ pub const Dir = struct { |
| 695 | if (flags.lock and lock_flag == 0) { | 690 | if (flags.lock and lock_flag == 0) { |
| 696 | // TODO: integrate async I/O | 691 | // TODO: integrate async I/O |
| 697 | // mem.zeroes is used here because flock's structure can vary across architectures and systems | 692 | // mem.zeroes is used here because flock's structure can vary across architectures and systems |
| 698 | var flock = mem.zeroes(os.Flock); | 693 | _ = try os.flock(fd, os.LOCK_EX); |
| 699 | flock.l_type = os.F_WRLCK; | ||
| 700 | flock.l_whence = os.SEEK_SET; | ||
| 701 | _ = try os.fcntl(fd, os.F_SETLKW, @ptrToInt(&flock)); | ||
| 702 | } | 694 | } |
| 703 | 695 | ||
| 704 | return File{ .handle = fd, .io_mode = .blocking }; | 696 | return File{ .handle = fd, .io_mode = .blocking }; |
| ... | @@ -1801,59 +1793,14 @@ const FileLockTestContext = struct { | ... | @@ -1801,59 +1793,14 @@ const FileLockTestContext = struct { |
| 1801 | }; | 1793 | }; |
| 1802 | 1794 | ||
| 1803 | fn run_lock_file_test(contexts: []FileLockTestContext) !void { | 1795 | fn run_lock_file_test(contexts: []FileLockTestContext) !void { |
| 1804 | var shared_mem: if (builtin.os.tag == .windows) void else []align(mem.page_size) u8 = undefined; | 1796 | var threads = std.ArrayList(*std.Thread).init(std.testing.allocator); |
| 1805 | 1797 | defer { | |
| 1806 | var ctxs: []FileLockTestContext = undefined; | 1798 | for (threads.toSlice()) |thread| { |
| 1807 | if (builtin.os.tag == .windows) { | 1799 | thread.wait(); |
| 1808 | ctxs = contexts; | ||
| 1809 | } else { | ||
| 1810 | 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); | ||
| 1811 | const ctxs_ptr = @ptrCast([*]FileLockTestContext, shared_mem.ptr); | ||
| 1812 | ctxs = ctxs_ptr[0..contexts.len]; | ||
| 1813 | |||
| 1814 | for (contexts) |context, idx| { | ||
| 1815 | ctxs[idx] = context; | ||
| 1816 | } | ||
| 1817 | } | ||
| 1818 | |||
| 1819 | if (builtin.os.tag == .windows) { | ||
| 1820 | var threads = std.ArrayList(*std.Thread).init(std.testing.allocator); | ||
| 1821 | defer { | ||
| 1822 | for (threads.toSlice()) |thread| { | ||
| 1823 | thread.wait(); | ||
| 1824 | } | ||
| 1825 | threads.deinit(); | ||
| 1826 | } | ||
| 1827 | for (ctxs) |*ctx, idx| { | ||
| 1828 | try threads.append(try std.Thread.spawn(ctx, FileLockTestContext.run)); | ||
| 1829 | } | ||
| 1830 | } else { | ||
| 1831 | var ctx_opt: ?*FileLockTestContext = null; | ||
| 1832 | for (ctxs) |*ctx| { | ||
| 1833 | const childpid = try std.os.fork(); | ||
| 1834 | if (childpid == 0) { | ||
| 1835 | ctx_opt = ctx; | ||
| 1836 | break; | ||
| 1837 | } | ||
| 1838 | ctx.pid = childpid; | ||
| 1839 | } | ||
| 1840 | |||
| 1841 | if (ctx_opt) |ctx| { | ||
| 1842 | ctx.run(); | ||
| 1843 | // Exit so we don't have duplicate test processes | ||
| 1844 | std.os.exit(0); | ||
| 1845 | } else { | ||
| 1846 | for (ctxs) |ctx| { | ||
| 1847 | _ = std.os.waitpid(ctx.pid.?, 0); | ||
| 1848 | } | ||
| 1849 | } | 1800 | } |
| 1801 | threads.deinit(); | ||
| 1850 | } | 1802 | } |
| 1851 | 1803 | for (contexts) |*ctx, idx| { | |
| 1852 | if (builtin.os.tag != .windows) { | 1804 | try threads.append(try std.Thread.spawn(ctx, FileLockTestContext.run)); |
| 1853 | // Copy contexts out of shared memory | ||
| 1854 | for (ctxs) |ctx, idx| { | ||
| 1855 | contexts[idx] = ctx; | ||
| 1856 | } | ||
| 1857 | std.os.munmap(shared_mem); | ||
| 1858 | } | 1805 | } |
| 1859 | } | 1806 | } |
lib/std/fs/file.zig+1-1| ... | @@ -34,7 +34,7 @@ pub const File = struct { | ... | @@ -34,7 +34,7 @@ pub const File = struct { |
| 34 | else => 0o666, | 34 | else => 0o666, |
| 35 | }; | 35 | }; |
| 36 | 36 | ||
| 37 | pub const OpenError = windows.CreateFileError || os.OpenError || os.FcntlError; | 37 | pub const OpenError = windows.CreateFileError || os.OpenError || os.FlockError; |
| 38 | 38 | ||
| 39 | /// TODO https://github.com/ziglang/zig/issues/3802 | 39 | /// TODO https://github.com/ziglang/zig/issues/3802 |
| 40 | pub const OpenFlags = struct { | 40 | pub const OpenFlags = struct { |
lib/std/os.zig+23-9| ... | @@ -819,36 +819,28 @@ pub const OpenError = error{ | ... | @@ -819,36 +819,28 @@ pub const OpenError = error{ |
| 819 | SystemFdQuotaExceeded, | 819 | SystemFdQuotaExceeded, |
| 820 | NoDevice, | 820 | NoDevice, |
| 821 | FileNotFound, | 821 | FileNotFound, |
| 822 | |||
| 823 | /// The path exceeded `MAX_PATH_BYTES` bytes. | 822 | /// The path exceeded `MAX_PATH_BYTES` bytes. |
| 824 | NameTooLong, | 823 | NameTooLong, |
| 825 | |||
| 826 | /// Insufficient kernel memory was available, or | 824 | /// Insufficient kernel memory was available, or |
| 827 | /// the named file is a FIFO and per-user hard limit on | 825 | /// the named file is a FIFO and per-user hard limit on |
| 828 | /// memory allocation for pipes has been reached. | 826 | /// memory allocation for pipes has been reached. |
| 829 | SystemResources, | 827 | SystemResources, |
| 830 | |||
| 831 | /// The file is too large to be opened. This error is unreachable | 828 | /// The file is too large to be opened. This error is unreachable |
| 832 | /// for 64-bit targets, as well as when opening directories. | 829 | /// for 64-bit targets, as well as when opening directories. |
| 833 | FileTooBig, | 830 | FileTooBig, |
| 834 | |||
| 835 | /// The path refers to directory but the `O_DIRECTORY` flag was not provided. | 831 | /// The path refers to directory but the `O_DIRECTORY` flag was not provided. |
| 836 | IsDir, | 832 | IsDir, |
| 837 | |||
| 838 | /// A new path cannot be created because the device has no room for the new file. | 833 | /// A new path cannot be created because the device has no room for the new file. |
| 839 | /// This error is only reachable when the `O_CREAT` flag is provided. | 834 | /// This error is only reachable when the `O_CREAT` flag is provided. |
| 840 | NoSpaceLeft, | 835 | NoSpaceLeft, |
| 841 | |||
| 842 | /// A component used as a directory in the path was not, in fact, a directory, or | 836 | /// A component used as a directory in the path was not, in fact, a directory, or |
| 843 | /// `O_DIRECTORY` was specified and the path was not a directory. | 837 | /// `O_DIRECTORY` was specified and the path was not a directory. |
| 844 | NotDir, | 838 | NotDir, |
| 845 | |||
| 846 | /// The path already exists and the `O_CREAT` and `O_EXCL` flags were provided. | 839 | /// The path already exists and the `O_CREAT` and `O_EXCL` flags were provided. |
| 847 | PathAlreadyExists, | 840 | PathAlreadyExists, |
| 848 | DeviceBusy, | 841 | DeviceBusy, |
| 849 | |||
| 850 | /// The underlying filesystem does not support file locks | 842 | /// The underlying filesystem does not support file locks |
| 851 | FileLocksNotSupported | 843 | FileLocksNotSupported, |
| 852 | } || UnexpectedError; | 844 | } || UnexpectedError; |
| 853 | 845 | ||
| 854 | /// Open and possibly create a file. Keeps trying if it gets interrupted. | 846 | /// 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 { | ... | @@ -3222,6 +3214,28 @@ pub fn fcntl(fd: fd_t, cmd: i32, arg: usize) FcntlError!usize { |
| 3222 | } | 3214 | } |
| 3223 | } | 3215 | } |
| 3224 | 3216 | ||
| 3217 | pub const FlockError = error{ | ||
| 3218 | WouldBlock, | ||
| 3219 | |||
| 3220 | /// The kernel ran out of memory for allocating file locks | ||
| 3221 | SystemResources, | ||
| 3222 | } || UnexpectedError; | ||
| 3223 | |||
| 3224 | pub fn flock(fd: fd_t, operation: i32) FlockError!usize { | ||
| 3225 | while (true) { | ||
| 3226 | const rc = system.flock(fd, operation); | ||
| 3227 | switch (errno(rc)) { | ||
| 3228 | 0 => return @intCast(usize, rc), | ||
| 3229 | EBADF => unreachable, | ||
| 3230 | EINTR => continue, | ||
| 3231 | EINVAL => unreachable, // invalid parameters | ||
| 3232 | ENOLCK => return error.SystemResources, | ||
| 3233 | EWOULDBLOCK => return error.WouldBlock, // TODO: integrate with async instead of just returning an error | ||
| 3234 | else => |err| return unexpectedErrno(err), | ||
| 3235 | } | ||
| 3236 | } | ||
| 3237 | } | ||
| 3238 | |||
| 3225 | pub const RealPathError = error{ | 3239 | pub const RealPathError = error{ |
| 3226 | FileNotFound, | 3240 | FileNotFound, |
| 3227 | AccessDenied, | 3241 | AccessDenied, |
lib/std/os/bits/darwin.zig+5| ... | @@ -1394,3 +1394,8 @@ pub const F_UNLCK = 2; | ... | @@ -1394,3 +1394,8 @@ pub const F_UNLCK = 2; |
| 1394 | 1394 | ||
| 1395 | /// exclusive or write lock | 1395 | /// exclusive or write lock |
| 1396 | pub const F_WRLCK = 3; | 1396 | pub const F_WRLCK = 3; |
| 1397 | |||
| 1398 | pub const LOCK_SH = 1; | ||
| 1399 | pub const LOCK_EX = 2; | ||
| 1400 | pub const LOCK_UN = 8; | ||
| 1401 | pub const LOCK_NB = 4; |
lib/std/os/bits/linux/arm-eabi.zig+5| ... | @@ -462,6 +462,11 @@ pub const F_GETOWN_EX = 16; | ... | @@ -462,6 +462,11 @@ pub const F_GETOWN_EX = 16; |
| 462 | 462 | ||
| 463 | pub const F_GETOWNER_UIDS = 17; | 463 | pub const F_GETOWNER_UIDS = 17; |
| 464 | 464 | ||
| 465 | pub const LOCK_SH = 1; | ||
| 466 | pub const LOCK_EX = 2; | ||
| 467 | pub const LOCK_UN = 8; | ||
| 468 | pub const LOCK_NB = 4; | ||
| 469 | |||
| 465 | /// stack-like segment | 470 | /// stack-like segment |
| 466 | pub const MAP_GROWSDOWN = 0x0100; | 471 | pub const MAP_GROWSDOWN = 0x0100; |
| 467 | 472 |
lib/std/os/bits/linux/arm64.zig+5| ... | @@ -349,6 +349,11 @@ pub const F_RDLCK = 0; | ... | @@ -349,6 +349,11 @@ pub const F_RDLCK = 0; |
| 349 | pub const F_WRLCK = 1; | 349 | pub const F_WRLCK = 1; |
| 350 | pub const F_UNLCK = 2; | 350 | pub const F_UNLCK = 2; |
| 351 | 351 | ||
| 352 | pub const LOCK_SH = 1; | ||
| 353 | pub const LOCK_EX = 2; | ||
| 354 | pub const LOCK_UN = 8; | ||
| 355 | pub const LOCK_NB = 4; | ||
| 356 | |||
| 352 | pub const F_SETOWN_EX = 15; | 357 | pub const F_SETOWN_EX = 15; |
| 353 | pub const F_GETOWN_EX = 16; | 358 | pub const F_GETOWN_EX = 16; |
| 354 | 359 |
lib/std/os/bits/linux/i386.zig+5| ... | @@ -482,6 +482,11 @@ pub const F_RDLCK = 0; | ... | @@ -482,6 +482,11 @@ pub const F_RDLCK = 0; |
| 482 | pub const F_WRLCK = 1; | 482 | pub const F_WRLCK = 1; |
| 483 | pub const F_UNLCK = 2; | 483 | pub const F_UNLCK = 2; |
| 484 | 484 | ||
| 485 | pub const LOCK_SH = 1; | ||
| 486 | pub const LOCK_EX = 2; | ||
| 487 | pub const LOCK_UN = 8; | ||
| 488 | pub const LOCK_NB = 4; | ||
| 489 | |||
| 485 | pub const F_SETOWN_EX = 15; | 490 | pub const F_SETOWN_EX = 15; |
| 486 | pub const F_GETOWN_EX = 16; | 491 | pub const F_GETOWN_EX = 16; |
| 487 | 492 |
lib/std/os/bits/linux/mipsel.zig+5| ... | @@ -424,6 +424,11 @@ pub const F_RDLCK = 0; | ... | @@ -424,6 +424,11 @@ pub const F_RDLCK = 0; |
| 424 | pub const F_WRLCK = 1; | 424 | pub const F_WRLCK = 1; |
| 425 | pub const F_UNLCK = 2; | 425 | pub const F_UNLCK = 2; |
| 426 | 426 | ||
| 427 | pub const LOCK_SH = 1; | ||
| 428 | pub const LOCK_EX = 2; | ||
| 429 | pub const LOCK_UN = 8; | ||
| 430 | pub const LOCK_NB = 4; | ||
| 431 | |||
| 427 | pub const F_SETOWN_EX = 15; | 432 | pub const F_SETOWN_EX = 15; |
| 428 | pub const F_GETOWN_EX = 16; | 433 | pub const F_GETOWN_EX = 16; |
| 429 | 434 |
lib/std/os/bits/linux/riscv64.zig+5| ... | @@ -343,6 +343,11 @@ pub const F_RDLCK = 0; | ... | @@ -343,6 +343,11 @@ pub const F_RDLCK = 0; |
| 343 | pub const F_WRLCK = 1; | 343 | pub const F_WRLCK = 1; |
| 344 | pub const F_UNLCK = 2; | 344 | pub const F_UNLCK = 2; |
| 345 | 345 | ||
| 346 | pub const LOCK_SH = 1; | ||
| 347 | pub const LOCK_EX = 2; | ||
| 348 | pub const LOCK_UN = 8; | ||
| 349 | pub const LOCK_NB = 4; | ||
| 350 | |||
| 346 | pub const F_SETOWN_EX = 15; | 351 | pub const F_SETOWN_EX = 15; |
| 347 | pub const F_GETOWN_EX = 16; | 352 | pub const F_GETOWN_EX = 16; |
| 348 | 353 |
lib/std/os/bits/linux/x86_64.zig+5| ... | @@ -462,6 +462,11 @@ pub const REG_TRAPNO = 20; | ... | @@ -462,6 +462,11 @@ pub const REG_TRAPNO = 20; |
| 462 | pub const REG_OLDMASK = 21; | 462 | pub const REG_OLDMASK = 21; |
| 463 | pub const REG_CR2 = 22; | 463 | pub const REG_CR2 = 22; |
| 464 | 464 | ||
| 465 | pub const LOCK_SH = 1; | ||
| 466 | pub const LOCK_EX = 2; | ||
| 467 | pub const LOCK_UN = 8; | ||
| 468 | pub const LOCK_NB = 4; | ||
| 469 | |||
| 465 | pub const F_RDLCK = 0; | 470 | pub const F_RDLCK = 0; |
| 466 | pub const F_WRLCK = 1; | 471 | pub const F_WRLCK = 1; |
| 467 | pub const F_UNLCK = 2; | 472 | pub const F_UNLCK = 2; |
lib/std/os/linux.zig+4| ... | @@ -592,6 +592,10 @@ pub fn fcntl(fd: fd_t, cmd: i32, arg: usize) usize { | ... | @@ -592,6 +592,10 @@ pub fn fcntl(fd: fd_t, cmd: i32, arg: usize) usize { |
| 592 | return syscall3(.fcntl, @bitCast(usize, @as(isize, fd)), @bitCast(usize, @as(isize, cmd)), arg); | 592 | return syscall3(.fcntl, @bitCast(usize, @as(isize, fd)), @bitCast(usize, @as(isize, cmd)), arg); |
| 593 | } | 593 | } |
| 594 | 594 | ||
| 595 | pub fn flock(fd: fd_t, operation: i32) usize { | ||
| 596 | return syscall2(.flock, @bitCast(usize, @as(isize, fd)), @bitCast(usize, @as(isize, operation))); | ||
| 597 | } | ||
| 598 | |||
| 595 | var vdso_clock_gettime = @ptrCast(?*const c_void, init_vdso_clock_gettime); | 599 | var vdso_clock_gettime = @ptrCast(?*const c_void, init_vdso_clock_gettime); |
| 596 | 600 | ||
| 597 | // We must follow the C calling convention when we call into the VDSO | 601 | // We must follow the C calling convention when we call into the VDSO |