std: file system watching for linux

This commit is contained in:
Andrew Kelley 2018-07-29 23:27:21 -04:00
parent a870228ab4
commit 3c8d4e04ea
6 changed files with 368 additions and 35 deletions

View File

@ -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<loop.allocator> 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
));
}

View File

@ -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),

View File

@ -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();
}

View File

@ -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;

View File

@ -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,
}
}

View File

@ -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");