diff --git a/std/event/fs.zig b/std/event/fs.zig index 7af81bd672..a4361cbaf2 100644 --- a/std/event/fs.zig +++ b/std/event/fs.zig @@ -20,17 +20,18 @@ pub const Request = struct { PWriteV: PWriteV, PReadV: PReadV, OpenRead: OpenRead, + OpenRW: OpenRW, Close: Close, WriteFile: WriteFile, End, // special - means the fs thread should exit pub const PWriteV = struct { fd: os.FileHandle, - data: []const []const u8, + iov: []os.linux.iovec_const, offset: usize, result: Error!void, - pub const Error = error{}; + pub const Error = os.File.WriteError; }; pub const PReadV = struct { @@ -50,6 +51,15 @@ pub const Request = struct { pub const Error = os.File.OpenError; }; + pub const OpenRW = struct { + /// must be null terminated. TODO https://github.com/ziglang/zig/issues/265 + path: []const u8, + result: Error!os.FileHandle, + mode: os.File.Mode, + + pub const Error = os.File.OpenError; + }; + pub const WriteFile = struct { /// must be null terminated. TODO https://github.com/ziglang/zig/issues/265 path: []const u8, @@ -66,7 +76,7 @@ pub const Request = struct { }; }; -/// data - both the outer and inner references - must live until pwritev promise completes. +/// data - just the inner references - must live until pwritev promise completes. pub async fn pwritev(loop: *event.Loop, fd: os.FileHandle, offset: usize, data: []const []const u8) !void { //const data_dupe = try mem.dupe(loop.allocator, []const u8, data); //defer loop.allocator.free(data_dupe); @@ -78,13 +88,23 @@ pub async fn pwritev(loop: *event.Loop, fd: os.FileHandle, offset: usize, data: resume p; } + const iovecs = try loop.allocator.alloc(os.linux.iovec_const, data.len); + defer loop.allocator.free(iovecs); + + for (data) |buf, i| { + iovecs[i] = os.linux.iovec_const{ + .iov_base = buf.ptr, + .iov_len = buf.len, + }; + } + var req_node = RequestNode{ .next = undefined, .data = Request{ .msg = Request.Msg{ .PWriteV = Request.Msg.PWriteV{ .fd = fd, - .data = data, + .iov = iovecs, .offset = offset, .result = undefined, }, @@ -162,12 +182,15 @@ pub async fn openRead(loop: *event.Loop, path: []const u8) os.File.OpenError!os. resume p; } + const path_with_null = try std.cstr.addNullByte(loop.allocator, path); + defer loop.allocator.free(path_with_null); + var req_node = RequestNode{ .next = undefined, .data = Request{ .msg = Request.Msg{ .OpenRead = Request.Msg.OpenRead{ - .path = path, + .path = path_with_null[0..path.len], .result = undefined, }, }, @@ -187,6 +210,48 @@ pub async fn openRead(loop: *event.Loop, path: []const u8) os.File.OpenError!os. return req_node.data.msg.OpenRead.result; } +/// Creates if does not exist. Does not truncate. +pub async fn openReadWrite( + loop: *event.Loop, + path: []const u8, + mode: os.File.Mode, +) os.File.OpenError!os.FileHandle { + // workaround for https://github.com/ziglang/zig/issues/1194 + var my_handle: promise = undefined; + suspend |p| { + my_handle = p; + resume p; + } + + const path_with_null = try std.cstr.addNullByte(loop.allocator, path); + defer loop.allocator.free(path_with_null); + + var req_node = RequestNode{ + .next = undefined, + .data = Request{ + .msg = Request.Msg{ + .OpenRW = Request.Msg.OpenRW{ + .path = path_with_null[0..path.len], + .mode = mode, + .result = undefined, + }, + }, + .finish = Request.Finish{ + .TickNode = event.Loop.NextTickNode{ + .next = undefined, + .data = my_handle, + }, + }, + }, + }; + + suspend |_| { + loop.linuxFsRequest(&req_node); + } + + return req_node.data.msg.OpenRW.result; +} + /// This abstraction helps to close file handles in defer expressions /// without suspending. Start a CloseOperation before opening a file. pub const CloseOperation = struct { @@ -302,6 +367,113 @@ pub async fn readFile(loop: *event.Loop, file_path: []const u8, max_size: usize) } } +pub const Watch = struct { + channel: *event.Channel(Event), + putter: promise, + + pub const Event = union(enum) { + CloseWrite, + Err: Error, + }; + + pub const Error = error{ + UserResourceLimitReached, + SystemResources, + }; + + pub fn destroy(self: *Watch) void { + // TODO https://github.com/ziglang/zig/issues/1261 + cancel self.putter; + } +}; + +pub fn watchFile(loop: *event.Loop, file_path: []const u8) !*Watch { + const path_with_null = try std.cstr.addNullByte(loop.allocator, file_path); + defer loop.allocator.free(path_with_null); + + const inotify_fd = try os.linuxINotifyInit1(os.linux.IN_NONBLOCK | os.linux.IN_CLOEXEC); + errdefer os.close(inotify_fd); + + const wd = try os.linuxINotifyAddWatchC(inotify_fd, path_with_null.ptr, os.linux.IN_CLOSE_WRITE); + errdefer os.close(wd); + + const channel = try event.Channel(Watch.Event).create(loop, 0); + errdefer channel.destroy(); + + var result: *Watch = undefined; + _ = try async watchEventPutter(inotify_fd, wd, channel, &result); + return result; +} + +async fn watchEventPutter(inotify_fd: i32, wd: i32, channel: *event.Channel(Watch.Event), out_watch: **Watch) void { + // TODO https://github.com/ziglang/zig/issues/1194 + var my_handle: promise = undefined; + suspend |p| { + my_handle = p; + resume p; + } + + var watch = Watch{ + .putter = my_handle, + .channel = channel, + }; + out_watch.* = &watch; + + const loop = channel.loop; + loop.beginOneEvent(); + + defer { + channel.destroy(); + os.close(wd); + os.close(inotify_fd); + loop.finishOneEvent(); + } + + var event_buf: [4096]u8 align(@alignOf(os.linux.inotify_event)) = undefined; + + while (true) { + const rc = os.linux.read(inotify_fd, &event_buf, event_buf.len); + const errno = os.linux.getErrno(rc); + switch (errno) { + 0 => { + // can't use @bytesToSlice because of the special variable length name field + var ptr = event_buf[0..].ptr; + const end_ptr = ptr + event_buf.len; + var ev: *os.linux.inotify_event = undefined; + while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) : (ptr += @sizeOf(os.linux.inotify_event) + ev.len) { + ev = @ptrCast(*os.linux.inotify_event, ptr); + if (ev.mask & os.linux.IN_CLOSE_WRITE == os.linux.IN_CLOSE_WRITE) { + await (async channel.put(Watch.Event.CloseWrite) catch unreachable); + } + } + }, + os.linux.EINTR => continue, + os.linux.EINVAL => unreachable, + os.linux.EFAULT => unreachable, + os.linux.EAGAIN => { + (await (async loop.linuxWaitFd( + inotify_fd, + os.linux.EPOLLET | os.linux.EPOLLIN, + ) catch unreachable)) catch |err| { + const transformed_err = switch (err) { + error.InvalidFileDescriptor => unreachable, + error.FileDescriptorAlreadyPresentInSet => unreachable, + error.InvalidSyscall => unreachable, + error.OperationCausesCircularLoop => unreachable, + error.FileDescriptorNotRegistered => unreachable, + error.SystemResources => error.SystemResources, + error.UserResourceLimitReached => error.UserResourceLimitReached, + error.FileDescriptorIncompatibleWithEpoll => unreachable, + error.Unexpected => unreachable, + }; + await (async channel.put(Watch.Event{ .Err = transformed_err }) catch unreachable); + }; + }, + else => unreachable, + } + } +} + const test_tmp_dir = "std_event_fs_test"; test "write a file, watch it, write it again" { @@ -338,10 +510,39 @@ async fn testFsWatch(loop: *event.Loop) !void { \\line 1 \\line 2 ; + const line2_offset = 7; // first just write then read the file try await try async writeFile(loop, file_path, contents); const read_contents = try await try async readFile(loop, file_path, 1024 * 1024); assert(mem.eql(u8, read_contents, contents)); + + // now watch the file + var watch = try watchFile(loop, file_path); + defer watch.destroy(); + + const ev = try async watch.channel.get(); + var ev_consumed = false; + defer if (!ev_consumed) cancel ev; + + // overwrite line 2 + const fd = try await try async openReadWrite(loop, file_path, os.File.default_mode); + { + defer os.close(fd); + + try await try async pwritev(loop, fd, line2_offset, []const []const u8{"lorem ipsum"}); + } + + ev_consumed = true; + switch (await ev) { + Watch.Event.CloseWrite => {}, + Watch.Event.Err => |err| return err, + } + + const contents_updated = try await try async readFile(loop, file_path, 1024 * 1024); + assert(mem.eql(u8, contents_updated, + \\line 1 + \\lorem ipsum + )); } diff --git a/std/event/loop.zig b/std/event/loop.zig index 416d6cb2c3..d0794d6975 100644 --- a/std/event/loop.zig +++ b/std/event/loop.zig @@ -318,45 +318,46 @@ pub const Loop = struct { } /// resume_node must live longer than the promise that it holds a reference to. - pub fn addFd(self: *Loop, fd: i32, resume_node: *ResumeNode) !void { - _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst); - errdefer { - self.finishOneEvent(); - } - try self.modFd( + /// flags must contain EPOLLET + pub fn linuxAddFd(self: *Loop, fd: i32, resume_node: *ResumeNode, flags: u32) !void { + assert(flags & posix.EPOLLET == posix.EPOLLET); + self.beginOneEvent(); + errdefer self.finishOneEvent(); + try self.linuxModFd( fd, posix.EPOLL_CTL_ADD, - os.linux.EPOLLIN | os.linux.EPOLLOUT | os.linux.EPOLLET, + flags, resume_node, ); } - pub fn modFd(self: *Loop, fd: i32, op: u32, events: u32, resume_node: *ResumeNode) !void { + pub fn linuxModFd(self: *Loop, fd: i32, op: u32, flags: u32, resume_node: *ResumeNode) !void { + assert(flags & posix.EPOLLET == posix.EPOLLET); var ev = os.linux.epoll_event{ - .events = events, + .events = flags, .data = os.linux.epoll_data{ .ptr = @ptrToInt(resume_node) }, }; try os.linuxEpollCtl(self.os_data.epollfd, op, fd, &ev); } - pub fn removeFd(self: *Loop, fd: i32) void { - self.removeFdNoCounter(fd); + pub fn linuxRemoveFd(self: *Loop, fd: i32) void { + self.linuxRemoveFdNoCounter(fd); self.finishOneEvent(); } - fn removeFdNoCounter(self: *Loop, fd: i32) void { + fn linuxRemoveFdNoCounter(self: *Loop, fd: i32) void { os.linuxEpollCtl(self.os_data.epollfd, os.linux.EPOLL_CTL_DEL, fd, undefined) catch {}; } - pub async fn waitFd(self: *Loop, fd: i32) !void { - defer self.removeFd(fd); + pub async fn linuxWaitFd(self: *Loop, fd: i32, flags: u32) !void { + defer self.linuxRemoveFd(fd); suspend |p| { // TODO explicitly put this memory in the coroutine frame #1194 var resume_node = ResumeNode{ .id = ResumeNode.Id.Basic, .handle = p, }; - try self.addFd(fd, &resume_node); + try self.linuxAddFd(fd, &resume_node, flags); } } @@ -382,7 +383,7 @@ pub const Loop = struct { // the pending count is already accounted for const epoll_events = posix.EPOLLONESHOT | os.linux.EPOLLIN | os.linux.EPOLLOUT | os.linux.EPOLLET; - self.modFd( + self.linuxModFd( eventfd_node.eventfd, eventfd_node.epoll_op, epoll_events, @@ -416,7 +417,7 @@ pub const Loop = struct { /// Bring your own linked list node. This means it can't fail. pub fn onNextTick(self: *Loop, node: *NextTickNode) void { - _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst); + self.beginOneEvent(); // finished in dispatch() self.next_tick_queue.put(node); self.dispatch(); } @@ -470,8 +471,14 @@ pub const Loop = struct { } } - fn finishOneEvent(self: *Loop) void { - if (@atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst) == 1) { + /// call finishOneEvent when done + pub fn beginOneEvent(self: *Loop) void { + _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst); + } + + pub fn finishOneEvent(self: *Loop) void { + const prev = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst); + if (prev == 1) { // cause all the threads to stop switch (builtin.os) { builtin.Os.linux => { @@ -593,7 +600,7 @@ pub const Loop = struct { } fn linuxFsRequest(self: *Loop, request_node: *fs.RequestNode) void { - _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst); + self.beginOneEvent(); // finished in linuxFsRun after processing the msg self.os_data.fs_queue.put(request_node); _ = @atomicRmw(i32, &self.os_data.fs_queue_len, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst); // let this wrap const rc = os.linux.futex_wake(@ptrToInt(&self.os_data.fs_queue_len), os.linux.FUTEX_WAKE, 1); @@ -610,14 +617,21 @@ pub const Loop = struct { while (self.os_data.fs_queue.get()) |node| { processed_count +%= 1; switch (node.data.msg) { - @TagType(fs.Request.Msg).PWriteV => @panic("TODO"), + @TagType(fs.Request.Msg).End => return, + @TagType(fs.Request.Msg).PWriteV => |*msg| { + msg.result = os.posix_pwritev(msg.fd, msg.iov.ptr, msg.iov.len, msg.offset); + }, @TagType(fs.Request.Msg).PReadV => |*msg| { msg.result = os.posix_preadv(msg.fd, msg.iov.ptr, msg.iov.len, msg.offset); }, @TagType(fs.Request.Msg).OpenRead => |*msg| { - const flags = posix.O_LARGEFILE | posix.O_RDONLY; + const flags = posix.O_LARGEFILE | posix.O_RDONLY | posix.O_CLOEXEC; msg.result = os.posixOpenC(msg.path.ptr, flags, 0); }, + @TagType(fs.Request.Msg).OpenRW => |*msg| { + const flags = posix.O_LARGEFILE | posix.O_RDWR | posix.O_CREAT | posix.O_CLOEXEC; + msg.result = os.posixOpenC(msg.path.ptr, flags, msg.mode); + }, @TagType(fs.Request.Msg).Close => |*msg| os.close(msg.fd), @TagType(fs.Request.Msg).WriteFile => |*msg| blk: { const flags = posix.O_LARGEFILE | posix.O_WRONLY | posix.O_CREAT | @@ -629,7 +643,6 @@ pub const Loop = struct { defer os.close(fd); msg.result = os.posixWrite(fd, msg.contents); }, - @TagType(fs.Request.Msg).End => return, } switch (node.data.finish) { @TagType(fs.Request.Finish).TickNode => |*tick_node| self.onNextTick(tick_node), diff --git a/std/event/tcp.zig b/std/event/tcp.zig index 416a8c07dc..42018b03e5 100644 --- a/std/event/tcp.zig +++ b/std/event/tcp.zig @@ -55,7 +55,7 @@ pub const Server = struct { errdefer cancel self.accept_coro.?; self.listen_resume_node.handle = self.accept_coro.?; - try self.loop.addFd(sockfd, &self.listen_resume_node); + try self.loop.linuxAddFd(sockfd, &self.listen_resume_node, posix.EPOLLIN | posix.EPOLLOUT | posix.EPOLLET); errdefer self.loop.removeFd(sockfd); } @@ -116,7 +116,7 @@ pub async fn connect(loop: *Loop, _address: *const std.net.Address) !std.os.File errdefer std.os.close(sockfd); try std.os.posixConnectAsync(sockfd, &address.os_addr); - try await try async loop.waitFd(sockfd); + try await try async loop.linuxWaitFd(sockfd, posix.EPOLLIN | posix.EPOLLOUT); try std.os.posixGetSockOptConnectError(sockfd); return std.os.File.openHandle(sockfd); @@ -181,4 +181,3 @@ async fn doAsyncTest(loop: *Loop, address: *const std.net.Address, server: *Serv assert(mem.eql(u8, msg, "hello from server\n")); server.close(); } - diff --git a/std/os/file.zig b/std/os/file.zig index c402aa0522..24c3128350 100644 --- a/std/os/file.zig +++ b/std/os/file.zig @@ -29,7 +29,6 @@ pub const File = struct { /// `path` needs to be copied in memory to add a null terminating byte, hence the allocator. /// Call close to clean up. - /// TODO deprecated, just use open pub fn openRead(allocator: *mem.Allocator, path: []const u8) OpenError!File { if (is_posix) { const flags = posix.O_LARGEFILE | posix.O_RDONLY; @@ -51,7 +50,6 @@ pub const File = struct { } /// Calls `openWriteMode` with os.File.default_mode for the mode. - /// TODO deprecated, just use open pub fn openWrite(allocator: *mem.Allocator, path: []const u8) OpenError!File { return openWriteMode(allocator, path, os.File.default_mode); } @@ -60,7 +58,6 @@ pub const File = struct { /// If a file already exists in the destination it will be truncated. /// `path` needs to be copied in memory to add a null terminating byte, hence the allocator. /// Call close to clean up. - /// TODO deprecated, just use open pub fn openWriteMode(allocator: *mem.Allocator, path: []const u8, file_mode: Mode) OpenError!File { if (is_posix) { const flags = posix.O_LARGEFILE | posix.O_WRONLY | posix.O_CREAT | posix.O_CLOEXEC | posix.O_TRUNC; @@ -85,7 +82,6 @@ pub const File = struct { /// If a file already exists in the destination this returns OpenError.PathAlreadyExists /// `path` needs to be copied in memory to add a null terminating byte, hence the allocator. /// Call close to clean up. - /// TODO deprecated, just use open pub fn openWriteNoClobber(allocator: *mem.Allocator, path: []const u8, file_mode: Mode) OpenError!File { if (is_posix) { const flags = posix.O_LARGEFILE | posix.O_WRONLY | posix.O_CREAT | posix.O_CLOEXEC | posix.O_EXCL; diff --git a/std/os/index.zig b/std/os/index.zig index 727c71c435..943e1259b3 100644 --- a/std/os/index.zig +++ b/std/os/index.zig @@ -310,6 +310,29 @@ pub fn posixWrite(fd: i32, bytes: []const u8) !void { } } +pub fn posix_pwritev(fd: i32, iov: [*]const posix.iovec_const, count: usize, offset: u64) PosixWriteError!void { + while (true) { + const rc = posix.pwritev(fd, iov, count, offset); + const err = posix.getErrno(rc); + switch (err) { + 0 => return, + posix.EINTR => continue, + posix.EINVAL => unreachable, + posix.EFAULT => unreachable, + posix.EAGAIN => return PosixWriteError.WouldBlock, + posix.EBADF => return PosixWriteError.FileClosed, + posix.EDESTADDRREQ => return PosixWriteError.DestinationAddressRequired, + posix.EDQUOT => return PosixWriteError.DiskQuota, + posix.EFBIG => return PosixWriteError.FileTooBig, + posix.EIO => return PosixWriteError.InputOutput, + posix.ENOSPC => return PosixWriteError.NoSpaceLeft, + posix.EPERM => return PosixWriteError.AccessDenied, + posix.EPIPE => return PosixWriteError.BrokenPipe, + else => return unexpectedErrorPosix(err), + } + } +} + pub const PosixOpenError = error{ OutOfMemory, AccessDenied, @@ -2913,3 +2936,44 @@ pub fn bsdKEvent( } } } + +pub fn linuxINotifyInit1(flags: u32) !i32 { + const rc = linux.inotify_init1(flags); + const err = posix.getErrno(rc); + switch (err) { + 0 => return @intCast(i32, rc), + posix.EINVAL => unreachable, + posix.EMFILE => return error.ProcessFdQuotaExceeded, + posix.ENFILE => return error.SystemFdQuotaExceeded, + posix.ENOMEM => return error.SystemResources, + else => return unexpectedErrorPosix(err), + } +} + +pub fn linuxINotifyAddWatchC(inotify_fd: i32, pathname: [*]const u8, mask: u32) !i32 { + const rc = linux.inotify_add_watch(inotify_fd, pathname, mask); + const err = posix.getErrno(rc); + switch (err) { + 0 => return @intCast(i32, rc), + posix.EACCES => return error.AccessDenied, + posix.EBADF => unreachable, + posix.EFAULT => unreachable, + posix.EINVAL => unreachable, + posix.ENAMETOOLONG => return error.NameTooLong, + posix.ENOENT => return error.FileNotFound, + posix.ENOMEM => return error.SystemResources, + posix.ENOSPC => return error.UserResourceLimitReached, + else => return unexpectedErrorPosix(err), + } +} + +pub fn linuxINotifyRmWatch(inotify_fd: i32, wd: i32) !void { + const rc = linux.inotify_rm_watch(inotify_fd, wd); + const err = posix.getErrno(rc); + switch (err) { + 0 => return rc, + posix.EBADF => unreachable, + posix.EINVAL => unreachable, + else => unreachable, + } +} diff --git a/std/os/linux/index.zig b/std/os/linux/index.zig index cf68e03ff0..2b0e5e8288 100644 --- a/std/os/linux/index.zig +++ b/std/os/linux/index.zig @@ -567,6 +567,37 @@ pub const MNT_DETACH = 2; pub const MNT_EXPIRE = 4; pub const UMOUNT_NOFOLLOW = 8; +pub const IN_CLOEXEC = O_CLOEXEC; +pub const IN_NONBLOCK = O_NONBLOCK; + +pub const IN_ACCESS = 0x00000001; +pub const IN_MODIFY = 0x00000002; +pub const IN_ATTRIB = 0x00000004; +pub const IN_CLOSE_WRITE = 0x00000008; +pub const IN_CLOSE_NOWRITE = 0x00000010; +pub const IN_CLOSE = IN_CLOSE_WRITE | IN_CLOSE_NOWRITE; +pub const IN_OPEN = 0x00000020; +pub const IN_MOVED_FROM = 0x00000040; +pub const IN_MOVED_TO = 0x00000080; +pub const IN_MOVE = IN_MOVED_FROM | IN_MOVED_TO; +pub const IN_CREATE = 0x00000100; +pub const IN_DELETE = 0x00000200; +pub const IN_DELETE_SELF = 0x00000400; +pub const IN_MOVE_SELF = 0x00000800; +pub const IN_ALL_EVENTS = 0x00000fff; + +pub const IN_UNMOUNT = 0x00002000; +pub const IN_Q_OVERFLOW = 0x00004000; +pub const IN_IGNORED = 0x00008000; + +pub const IN_ONLYDIR = 0x01000000; +pub const IN_DONT_FOLLOW = 0x02000000; +pub const IN_EXCL_UNLINK = 0x04000000; +pub const IN_MASK_ADD = 0x20000000; + +pub const IN_ISDIR = 0x40000000; +pub const IN_ONESHOT = 0x80000000; + pub const S_IFMT = 0o170000; pub const S_IFDIR = 0o040000; @@ -704,6 +735,18 @@ pub fn getdents(fd: i32, dirp: [*]u8, count: usize) usize { return syscall3(SYS_getdents, @intCast(usize, fd), @ptrToInt(dirp), count); } +pub fn inotify_init1(flags: u32) usize { + return syscall1(SYS_inotify_init1, flags); +} + +pub fn inotify_add_watch(fd: i32, pathname: [*]const u8, mask: u32) usize { + return syscall3(SYS_inotify_add_watch, @intCast(usize, fd), @ptrToInt(pathname), mask); +} + +pub fn inotify_rm_watch(fd: i32, wd: i32) usize { + return syscall2(SYS_inotify_rm_watch, @intCast(usize, fd), @intCast(usize, wd)); +} + pub fn isatty(fd: i32) bool { var wsz: winsize = undefined; return syscall3(SYS_ioctl, @intCast(usize, fd), TIOCGWINSZ, @ptrToInt(&wsz)) == 0; @@ -750,6 +793,10 @@ pub fn preadv(fd: i32, iov: [*]const iovec, count: usize, offset: u64) usize { return syscall4(SYS_preadv, @intCast(usize, fd), @ptrToInt(iov), count, offset); } +pub fn pwritev(fd: i32, iov: [*]const iovec_const, count: usize, offset: u64) usize { + return syscall4(SYS_pwritev, @intCast(usize, fd), @ptrToInt(iov), count, offset); +} + // TODO https://github.com/ziglang/zig/issues/265 pub fn rmdir(path: [*]const u8) usize { return syscall1(SYS_rmdir, @ptrToInt(path)); @@ -1068,6 +1115,11 @@ pub const iovec = extern struct { iov_len: usize, }; +pub const iovec_const = extern struct { + iov_base: [*]const u8, + iov_len: usize, +}; + pub fn getsockname(fd: i32, noalias addr: *sockaddr, noalias len: *socklen_t) usize { return syscall3(SYS_getsockname, @intCast(usize, fd), @ptrToInt(addr), @ptrToInt(len)); } @@ -1376,6 +1428,14 @@ pub fn capset(hdrp: *cap_user_header_t, datap: *const cap_user_data_t) usize { return syscall2(SYS_capset, @ptrToInt(hdrp), @ptrToInt(datap)); } +pub const inotify_event = extern struct { + wd: i32, + mask: u32, + cookie: u32, + len: u32, + //name: [?]u8, +}; + test "import" { if (builtin.os == builtin.Os.linux) { _ = @import("test.zig");