authorgravatar for leroycepearson@geemili.xyzLeRoyce Pearson <leroycepearson@geemili.xyz> 2020-04-02 22:57:02-06:00
committergravatar for leroycepearson@geemili.xyzLeRoyce Pearson <leroycepearson@geemili.xyz> 2020-04-02 22:57:02-06:00
logea6525797d0db83a8560179b317837cb58634049
tree48a190000f83ba3303a9a3cae797430303c883b9
parente7cf3f92a98cd2bd94d9d94fb361819b6ab89e8d

Use `flock` instead of `fcntl` to lock files

`flock` locks based on the file handle, instead of the process id. This brings the file locking on unix based systems closer to file locking on Windows.

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
122122pub extern "c" fn tcgetattr(fd: fd_t, termios_p: *termios) c_int;
123123pub extern "c" fn tcsetattr(fd: fd_t, optional_action: TCSA, termios_p: *const termios) c_int;
124124pub extern "c" fn fcntl(fd: fd_t, cmd: c_int, ...) c_int;
125pub extern "c" fn flock(fd: fd_t, operation: c_int) c_int;
125126pub extern "c" fn uname(buf: *utsname) c_int;
126127
127128pub extern "c" fn gethostname(name: [*]u8, len: usize) c_int;
lib/std/fs.zig+9-62
......@@ -617,14 +617,9 @@ pub const Dir = struct {
617617 else
618618 try os.openatZ(self.fd, sub_path, os_flags, 0);
619619
620 // use fcntl file locking if no lock flag was given
621620 if (flags.lock and lock_flag == 0) {
622621 // TODO: integrate async I/O
623 // mem.zeroes is used here because flock's structure can vary across architectures and systems
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));
622 _ = try os.flock(fd, if (flags.write) os.LOCK_EX else os.LOCK_SH);
628623 }
629624
630625 return File{
......@@ -695,10 +690,7 @@ pub const Dir = struct {
695690 if (flags.lock and lock_flag == 0) {
696691 // TODO: integrate async I/O
697692 // mem.zeroes is used here because flock's structure can vary across architectures and systems
698 var flock = mem.zeroes(os.Flock);
699 flock.l_type = os.F_WRLCK;
700 flock.l_whence = os.SEEK_SET;
701 _ = try os.fcntl(fd, os.F_SETLKW, @ptrToInt(&flock));
693 _ = try os.flock(fd, os.LOCK_EX);
702694 }
703695
704696 return File{ .handle = fd, .io_mode = .blocking };
......@@ -1801,59 +1793,14 @@ const FileLockTestContext = struct {
18011793};
18021794
18031795fn run_lock_file_test(contexts: []FileLockTestContext) !void {
1804 var shared_mem: if (builtin.os.tag == .windows) void else []align(mem.page_size) u8 = undefined;
1805
1806 var ctxs: []FileLockTestContext = undefined;
1807 if (builtin.os.tag == .windows) {
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 }
1796 var threads = std.ArrayList(*std.Thread).init(std.testing.allocator);
1797 defer {
1798 for (threads.toSlice()) |thread| {
1799 thread.wait();
18491800 }
1801 threads.deinit();
18501802 }
1851
1852 if (builtin.os.tag != .windows) {
1853 // Copy contexts out of shared memory
1854 for (ctxs) |ctx, idx| {
1855 contexts[idx] = ctx;
1856 }
1857 std.os.munmap(shared_mem);
1803 for (contexts) |*ctx, idx| {
1804 try threads.append(try std.Thread.spawn(ctx, FileLockTestContext.run));
18581805 }
18591806}
lib/std/fs/file.zig+1-1
......@@ -34,7 +34,7 @@ pub const File = struct {
3434 else => 0o666,
3535 };
3636
37 pub const OpenError = windows.CreateFileError || os.OpenError || os.FcntlError;
37 pub const OpenError = windows.CreateFileError || os.OpenError || os.FlockError;
3838
3939 /// TODO https://github.com/ziglang/zig/issues/3802
4040 pub const OpenFlags = struct {
lib/std/os.zig+23-9
......@@ -819,36 +819,28 @@ pub const OpenError = error{
819819 SystemFdQuotaExceeded,
820820 NoDevice,
821821 FileNotFound,
822
823822 /// The path exceeded `MAX_PATH_BYTES` bytes.
824823 NameTooLong,
825
826824 /// Insufficient kernel memory was available, or
827825 /// the named file is a FIFO and per-user hard limit on
828826 /// memory allocation for pipes has been reached.
829827 SystemResources,
830
831828 /// The file is too large to be opened. This error is unreachable
832829 /// for 64-bit targets, as well as when opening directories.
833830 FileTooBig,
834
835831 /// The path refers to directory but the `O_DIRECTORY` flag was not provided.
836832 IsDir,
837
838833 /// A new path cannot be created because the device has no room for the new file.
839834 /// This error is only reachable when the `O_CREAT` flag is provided.
840835 NoSpaceLeft,
841
842836 /// A component used as a directory in the path was not, in fact, a directory, or
843837 /// `O_DIRECTORY` was specified and the path was not a directory.
844838 NotDir,
845
846839 /// The path already exists and the `O_CREAT` and `O_EXCL` flags were provided.
847840 PathAlreadyExists,
848841 DeviceBusy,
849
850842 /// The underlying filesystem does not support file locks
851 FileLocksNotSupported
843 FileLocksNotSupported,
852844} || UnexpectedError;
853845
854846/// 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 {
32223214 }
32233215}
32243216
3217pub const FlockError = error{
3218 WouldBlock,
3219
3220 /// The kernel ran out of memory for allocating file locks
3221 SystemResources,
3222} || UnexpectedError;
3223
3224pub 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
32253239pub const RealPathError = error{
32263240 FileNotFound,
32273241 AccessDenied,
lib/std/os/bits/darwin.zig+5
......@@ -1394,3 +1394,8 @@ pub const F_UNLCK = 2;
13941394
13951395/// exclusive or write lock
13961396pub const F_WRLCK = 3;
1397
1398pub const LOCK_SH = 1;
1399pub const LOCK_EX = 2;
1400pub const LOCK_UN = 8;
1401pub const LOCK_NB = 4;
lib/std/os/bits/linux/arm-eabi.zig+5
......@@ -462,6 +462,11 @@ pub const F_GETOWN_EX = 16;
462462
463463pub const F_GETOWNER_UIDS = 17;
464464
465pub const LOCK_SH = 1;
466pub const LOCK_EX = 2;
467pub const LOCK_UN = 8;
468pub const LOCK_NB = 4;
469
465470/// stack-like segment
466471pub const MAP_GROWSDOWN = 0x0100;
467472
lib/std/os/bits/linux/arm64.zig+5
......@@ -349,6 +349,11 @@ pub const F_RDLCK = 0;
349349pub const F_WRLCK = 1;
350350pub const F_UNLCK = 2;
351351
352pub const LOCK_SH = 1;
353pub const LOCK_EX = 2;
354pub const LOCK_UN = 8;
355pub const LOCK_NB = 4;
356
352357pub const F_SETOWN_EX = 15;
353358pub const F_GETOWN_EX = 16;
354359
lib/std/os/bits/linux/i386.zig+5
......@@ -482,6 +482,11 @@ pub const F_RDLCK = 0;
482482pub const F_WRLCK = 1;
483483pub const F_UNLCK = 2;
484484
485pub const LOCK_SH = 1;
486pub const LOCK_EX = 2;
487pub const LOCK_UN = 8;
488pub const LOCK_NB = 4;
489
485490pub const F_SETOWN_EX = 15;
486491pub const F_GETOWN_EX = 16;
487492
lib/std/os/bits/linux/mipsel.zig+5
......@@ -424,6 +424,11 @@ pub const F_RDLCK = 0;
424424pub const F_WRLCK = 1;
425425pub const F_UNLCK = 2;
426426
427pub const LOCK_SH = 1;
428pub const LOCK_EX = 2;
429pub const LOCK_UN = 8;
430pub const LOCK_NB = 4;
431
427432pub const F_SETOWN_EX = 15;
428433pub const F_GETOWN_EX = 16;
429434
lib/std/os/bits/linux/riscv64.zig+5
......@@ -343,6 +343,11 @@ pub const F_RDLCK = 0;
343343pub const F_WRLCK = 1;
344344pub const F_UNLCK = 2;
345345
346pub const LOCK_SH = 1;
347pub const LOCK_EX = 2;
348pub const LOCK_UN = 8;
349pub const LOCK_NB = 4;
350
346351pub const F_SETOWN_EX = 15;
347352pub const F_GETOWN_EX = 16;
348353
lib/std/os/bits/linux/x86_64.zig+5
......@@ -462,6 +462,11 @@ pub const REG_TRAPNO = 20;
462462pub const REG_OLDMASK = 21;
463463pub const REG_CR2 = 22;
464464
465pub const LOCK_SH = 1;
466pub const LOCK_EX = 2;
467pub const LOCK_UN = 8;
468pub const LOCK_NB = 4;
469
465470pub const F_RDLCK = 0;
466471pub const F_WRLCK = 1;
467472pub 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 {
592592 return syscall3(.fcntl, @bitCast(usize, @as(isize, fd)), @bitCast(usize, @as(isize, cmd)), arg);
593593}
594594
595pub fn flock(fd: fd_t, operation: i32) usize {
596 return syscall2(.flock, @bitCast(usize, @as(isize, fd)), @bitCast(usize, @as(isize, operation)));
597}
598
595599var vdso_clock_gettime = @ptrCast(?*const c_void, init_vdso_clock_gettime);
596600
597601// We must follow the C calling convention when we call into the VDSO