authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2018-07-29 23:27:21-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2018-07-30 13:46:09-04:00
log3c8d4e04ea000d087af4e77331340db1c8b1cef3
tree8f6cc5c2e61d2659189f235529e7208b341270e7
parenta870228ab467906ecc663bf89ecd042b49e116f5

std: file system watching for linux


6 files changed, 368 insertions(+), 35 deletions(-)

std/event/fs.zig+206-5
...@@ -20,17 +20,18 @@ pub const Request = struct {...@@ -20,17 +20,18 @@ pub const Request = struct {
20 PWriteV: PWriteV,20 PWriteV: PWriteV,
21 PReadV: PReadV,21 PReadV: PReadV,
22 OpenRead: OpenRead,22 OpenRead: OpenRead,
23 OpenRW: OpenRW,
23 Close: Close,24 Close: Close,
24 WriteFile: WriteFile,25 WriteFile: WriteFile,
25 End, // special - means the fs thread should exit26 End, // special - means the fs thread should exit
2627
27 pub const PWriteV = struct {28 pub const PWriteV = struct {
28 fd: os.FileHandle,29 fd: os.FileHandle,
29 data: []const []const u8,30 iov: []os.linux.iovec_const,
30 offset: usize,31 offset: usize,
31 result: Error!void,32 result: Error!void,
3233
33 pub const Error = error{};34 pub const Error = os.File.WriteError;
34 };35 };
3536
36 pub const PReadV = struct {37 pub const PReadV = struct {
...@@ -50,6 +51,15 @@ pub const Request = struct {...@@ -50,6 +51,15 @@ pub const Request = struct {
50 pub const Error = os.File.OpenError;51 pub const Error = os.File.OpenError;
51 };52 };
5253
54 pub const OpenRW = struct {
55 /// must be null terminated. TODO https://github.com/ziglang/zig/issues/265
56 path: []const u8,
57 result: Error!os.FileHandle,
58 mode: os.File.Mode,
59
60 pub const Error = os.File.OpenError;
61 };
62
53 pub const WriteFile = struct {63 pub const WriteFile = struct {
54 /// must be null terminated. TODO https://github.com/ziglang/zig/issues/26564 /// must be null terminated. TODO https://github.com/ziglang/zig/issues/265
55 path: []const u8,65 path: []const u8,
...@@ -66,7 +76,7 @@ pub const Request = struct {...@@ -66,7 +76,7 @@ pub const Request = struct {
66 };76 };
67};77};
6878
69/// data - both the outer and inner references - must live until pwritev promise completes.79/// data - just the inner references - must live until pwritev promise completes.
70pub async fn pwritev(loop: *event.Loop, fd: os.FileHandle, offset: usize, data: []const []const u8) !void {80pub async fn pwritev(loop: *event.Loop, fd: os.FileHandle, offset: usize, data: []const []const u8) !void {
71 //const data_dupe = try mem.dupe(loop.allocator, []const u8, data);81 //const data_dupe = try mem.dupe(loop.allocator, []const u8, data);
72 //defer loop.allocator.free(data_dupe);82 //defer loop.allocator.free(data_dupe);
...@@ -78,13 +88,23 @@ pub async fn pwritev(loop: *event.Loop, fd: os.FileHandle, offset: usize, data:...@@ -78,13 +88,23 @@ pub async fn pwritev(loop: *event.Loop, fd: os.FileHandle, offset: usize, data:
78 resume p;88 resume p;
79 }89 }
8090
91 const iovecs = try loop.allocator.alloc(os.linux.iovec_const, data.len);
92 defer loop.allocator.free(iovecs);
93
94 for (data) |buf, i| {
95 iovecs[i] = os.linux.iovec_const{
96 .iov_base = buf.ptr,
97 .iov_len = buf.len,
98 };
99 }
100
81 var req_node = RequestNode{101 var req_node = RequestNode{
82 .next = undefined,102 .next = undefined,
83 .data = Request{103 .data = Request{
84 .msg = Request.Msg{104 .msg = Request.Msg{
85 .PWriteV = Request.Msg.PWriteV{105 .PWriteV = Request.Msg.PWriteV{
86 .fd = fd,106 .fd = fd,
87 .data = data,107 .iov = iovecs,
88 .offset = offset,108 .offset = offset,
89 .result = undefined,109 .result = undefined,
90 },110 },
...@@ -162,12 +182,15 @@ pub async fn openRead(loop: *event.Loop, path: []const u8) os.File.OpenError!os....@@ -162,12 +182,15 @@ pub async fn openRead(loop: *event.Loop, path: []const u8) os.File.OpenError!os.
162 resume p;182 resume p;
163 }183 }
164184
185 const path_with_null = try std.cstr.addNullByte(loop.allocator, path);
186 defer loop.allocator.free(path_with_null);
187
165 var req_node = RequestNode{188 var req_node = RequestNode{
166 .next = undefined,189 .next = undefined,
167 .data = Request{190 .data = Request{
168 .msg = Request.Msg{191 .msg = Request.Msg{
169 .OpenRead = Request.Msg.OpenRead{192 .OpenRead = Request.Msg.OpenRead{
170 .path = path,193 .path = path_with_null[0..path.len],
171 .result = undefined,194 .result = undefined,
172 },195 },
173 },196 },
...@@ -187,6 +210,48 @@ pub async fn openRead(loop: *event.Loop, path: []const u8) os.File.OpenError!os....@@ -187,6 +210,48 @@ pub async fn openRead(loop: *event.Loop, path: []const u8) os.File.OpenError!os.
187 return req_node.data.msg.OpenRead.result;210 return req_node.data.msg.OpenRead.result;
188}211}
189212
213/// Creates if does not exist. Does not truncate.
214pub async fn openReadWrite(
215 loop: *event.Loop,
216 path: []const u8,
217 mode: os.File.Mode,
218) os.File.OpenError!os.FileHandle {
219 // workaround for https://github.com/ziglang/zig/issues/1194
220 var my_handle: promise = undefined;
221 suspend |p| {
222 my_handle = p;
223 resume p;
224 }
225
226 const path_with_null = try std.cstr.addNullByte(loop.allocator, path);
227 defer loop.allocator.free(path_with_null);
228
229 var req_node = RequestNode{
230 .next = undefined,
231 .data = Request{
232 .msg = Request.Msg{
233 .OpenRW = Request.Msg.OpenRW{
234 .path = path_with_null[0..path.len],
235 .mode = mode,
236 .result = undefined,
237 },
238 },
239 .finish = Request.Finish{
240 .TickNode = event.Loop.NextTickNode{
241 .next = undefined,
242 .data = my_handle,
243 },
244 },
245 },
246 };
247
248 suspend |_| {
249 loop.linuxFsRequest(&req_node);
250 }
251
252 return req_node.data.msg.OpenRW.result;
253}
254
190/// This abstraction helps to close file handles in defer expressions255/// This abstraction helps to close file handles in defer expressions
191/// without suspending. Start a CloseOperation before opening a file.256/// without suspending. Start a CloseOperation before opening a file.
192pub const CloseOperation = struct {257pub const CloseOperation = struct {
...@@ -302,6 +367,113 @@ pub async fn readFile(loop: *event.Loop, file_path: []const u8, max_size: usize)...@@ -302,6 +367,113 @@ pub async fn readFile(loop: *event.Loop, file_path: []const u8, max_size: usize)
302 }367 }
303}368}
304369
370pub const Watch = struct {
371 channel: *event.Channel(Event),
372 putter: promise,
373
374 pub const Event = union(enum) {
375 CloseWrite,
376 Err: Error,
377 };
378
379 pub const Error = error{
380 UserResourceLimitReached,
381 SystemResources,
382 };
383
384 pub fn destroy(self: *Watch) void {
385 // TODO https://github.com/ziglang/zig/issues/1261
386 cancel self.putter;
387 }
388};
389
390pub fn watchFile(loop: *event.Loop, file_path: []const u8) !*Watch {
391 const path_with_null = try std.cstr.addNullByte(loop.allocator, file_path);
392 defer loop.allocator.free(path_with_null);
393
394 const inotify_fd = try os.linuxINotifyInit1(os.linux.IN_NONBLOCK | os.linux.IN_CLOEXEC);
395 errdefer os.close(inotify_fd);
396
397 const wd = try os.linuxINotifyAddWatchC(inotify_fd, path_with_null.ptr, os.linux.IN_CLOSE_WRITE);
398 errdefer os.close(wd);
399
400 const channel = try event.Channel(Watch.Event).create(loop, 0);
401 errdefer channel.destroy();
402
403 var result: *Watch = undefined;
404 _ = try async<loop.allocator> watchEventPutter(inotify_fd, wd, channel, &result);
405 return result;
406}
407
408async fn watchEventPutter(inotify_fd: i32, wd: i32, channel: *event.Channel(Watch.Event), out_watch: **Watch) void {
409 // TODO https://github.com/ziglang/zig/issues/1194
410 var my_handle: promise = undefined;
411 suspend |p| {
412 my_handle = p;
413 resume p;
414 }
415
416 var watch = Watch{
417 .putter = my_handle,
418 .channel = channel,
419 };
420 out_watch.* = &watch;
421
422 const loop = channel.loop;
423 loop.beginOneEvent();
424
425 defer {
426 channel.destroy();
427 os.close(wd);
428 os.close(inotify_fd);
429 loop.finishOneEvent();
430 }
431
432 var event_buf: [4096]u8 align(@alignOf(os.linux.inotify_event)) = undefined;
433
434 while (true) {
435 const rc = os.linux.read(inotify_fd, &event_buf, event_buf.len);
436 const errno = os.linux.getErrno(rc);
437 switch (errno) {
438 0 => {
439 // can't use @bytesToSlice because of the special variable length name field
440 var ptr = event_buf[0..].ptr;
441 const end_ptr = ptr + event_buf.len;
442 var ev: *os.linux.inotify_event = undefined;
443 while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) : (ptr += @sizeOf(os.linux.inotify_event) + ev.len) {
444 ev = @ptrCast(*os.linux.inotify_event, ptr);
445 if (ev.mask & os.linux.IN_CLOSE_WRITE == os.linux.IN_CLOSE_WRITE) {
446 await (async channel.put(Watch.Event.CloseWrite) catch unreachable);
447 }
448 }
449 },
450 os.linux.EINTR => continue,
451 os.linux.EINVAL => unreachable,
452 os.linux.EFAULT => unreachable,
453 os.linux.EAGAIN => {
454 (await (async loop.linuxWaitFd(
455 inotify_fd,
456 os.linux.EPOLLET | os.linux.EPOLLIN,
457 ) catch unreachable)) catch |err| {
458 const transformed_err = switch (err) {
459 error.InvalidFileDescriptor => unreachable,
460 error.FileDescriptorAlreadyPresentInSet => unreachable,
461 error.InvalidSyscall => unreachable,
462 error.OperationCausesCircularLoop => unreachable,
463 error.FileDescriptorNotRegistered => unreachable,
464 error.SystemResources => error.SystemResources,
465 error.UserResourceLimitReached => error.UserResourceLimitReached,
466 error.FileDescriptorIncompatibleWithEpoll => unreachable,
467 error.Unexpected => unreachable,
468 };
469 await (async channel.put(Watch.Event{ .Err = transformed_err }) catch unreachable);
470 };
471 },
472 else => unreachable,
473 }
474 }
475}
476
305const test_tmp_dir = "std_event_fs_test";477const test_tmp_dir = "std_event_fs_test";
306478
307test "write a file, watch it, write it again" {479test "write a file, watch it, write it again" {
...@@ -338,10 +510,39 @@ async fn testFsWatch(loop: *event.Loop) !void {...@@ -338,10 +510,39 @@ async fn testFsWatch(loop: *event.Loop) !void {
338 \\line 1510 \\line 1
339 \\line 2511 \\line 2
340 ;512 ;
513 const line2_offset = 7;
341514
342 // first just write then read the file515 // first just write then read the file
343 try await try async writeFile(loop, file_path, contents);516 try await try async writeFile(loop, file_path, contents);
344517
345 const read_contents = try await try async readFile(loop, file_path, 1024 * 1024);518 const read_contents = try await try async readFile(loop, file_path, 1024 * 1024);
346 assert(mem.eql(u8, read_contents, contents));519 assert(mem.eql(u8, read_contents, contents));
520
521 // now watch the file
522 var watch = try watchFile(loop, file_path);
523 defer watch.destroy();
524
525 const ev = try async watch.channel.get();
526 var ev_consumed = false;
527 defer if (!ev_consumed) cancel ev;
528
529 // overwrite line 2
530 const fd = try await try async openReadWrite(loop, file_path, os.File.default_mode);
531 {
532 defer os.close(fd);
533
534 try await try async pwritev(loop, fd, line2_offset, []const []const u8{"lorem ipsum"});
535 }
536
537 ev_consumed = true;
538 switch (await ev) {
539 Watch.Event.CloseWrite => {},
540 Watch.Event.Err => |err| return err,
541 }
542
543 const contents_updated = try await try async readFile(loop, file_path, 1024 * 1024);
544 assert(mem.eql(u8, contents_updated,
545 \\line 1
546 \\lorem ipsum
547 ));
347}548}
std/event/loop.zig+36-23
...@@ -318,45 +318,46 @@ pub const Loop = struct {...@@ -318,45 +318,46 @@ pub const Loop = struct {
318 }318 }
319319
320 /// resume_node must live longer than the promise that it holds a reference to.320 /// resume_node must live longer than the promise that it holds a reference to.
321 pub fn addFd(self: *Loop, fd: i32, resume_node: *ResumeNode) !void {321 /// flags must contain EPOLLET
322 _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst);322 pub fn linuxAddFd(self: *Loop, fd: i32, resume_node: *ResumeNode, flags: u32) !void {
323 errdefer {323 assert(flags & posix.EPOLLET == posix.EPOLLET);
324 self.finishOneEvent();324 self.beginOneEvent();
325 }325 errdefer self.finishOneEvent();
326 try self.modFd(326 try self.linuxModFd(
327 fd,327 fd,
328 posix.EPOLL_CTL_ADD,328 posix.EPOLL_CTL_ADD,
329 os.linux.EPOLLIN | os.linux.EPOLLOUT | os.linux.EPOLLET,329 flags,
330 resume_node,330 resume_node,
331 );331 );
332 }332 }
333333
334 pub fn modFd(self: *Loop, fd: i32, op: u32, events: u32, resume_node: *ResumeNode) !void {334 pub fn linuxModFd(self: *Loop, fd: i32, op: u32, flags: u32, resume_node: *ResumeNode) !void {
335 assert(flags & posix.EPOLLET == posix.EPOLLET);
335 var ev = os.linux.epoll_event{336 var ev = os.linux.epoll_event{
336 .events = events,337 .events = flags,
337 .data = os.linux.epoll_data{ .ptr = @ptrToInt(resume_node) },338 .data = os.linux.epoll_data{ .ptr = @ptrToInt(resume_node) },
338 };339 };
339 try os.linuxEpollCtl(self.os_data.epollfd, op, fd, &ev);340 try os.linuxEpollCtl(self.os_data.epollfd, op, fd, &ev);
340 }341 }
341342
342 pub fn removeFd(self: *Loop, fd: i32) void {343 pub fn linuxRemoveFd(self: *Loop, fd: i32) void {
343 self.removeFdNoCounter(fd);344 self.linuxRemoveFdNoCounter(fd);
344 self.finishOneEvent();345 self.finishOneEvent();
345 }346 }
346347
347 fn removeFdNoCounter(self: *Loop, fd: i32) void {348 fn linuxRemoveFdNoCounter(self: *Loop, fd: i32) void {
348 os.linuxEpollCtl(self.os_data.epollfd, os.linux.EPOLL_CTL_DEL, fd, undefined) catch {};349 os.linuxEpollCtl(self.os_data.epollfd, os.linux.EPOLL_CTL_DEL, fd, undefined) catch {};
349 }350 }
350351
351 pub async fn waitFd(self: *Loop, fd: i32) !void {352 pub async fn linuxWaitFd(self: *Loop, fd: i32, flags: u32) !void {
352 defer self.removeFd(fd);353 defer self.linuxRemoveFd(fd);
353 suspend |p| {354 suspend |p| {
354 // TODO explicitly put this memory in the coroutine frame #1194355 // TODO explicitly put this memory in the coroutine frame #1194
355 var resume_node = ResumeNode{356 var resume_node = ResumeNode{
356 .id = ResumeNode.Id.Basic,357 .id = ResumeNode.Id.Basic,
357 .handle = p,358 .handle = p,
358 };359 };
359 try self.addFd(fd, &resume_node);360 try self.linuxAddFd(fd, &resume_node, flags);
360 }361 }
361 }362 }
362363
...@@ -382,7 +383,7 @@ pub const Loop = struct {...@@ -382,7 +383,7 @@ pub const Loop = struct {
382 // the pending count is already accounted for383 // the pending count is already accounted for
383 const epoll_events = posix.EPOLLONESHOT | os.linux.EPOLLIN | os.linux.EPOLLOUT |384 const epoll_events = posix.EPOLLONESHOT | os.linux.EPOLLIN | os.linux.EPOLLOUT |
384 os.linux.EPOLLET;385 os.linux.EPOLLET;
385 self.modFd(386 self.linuxModFd(
386 eventfd_node.eventfd,387 eventfd_node.eventfd,
387 eventfd_node.epoll_op,388 eventfd_node.epoll_op,
388 epoll_events,389 epoll_events,
...@@ -416,7 +417,7 @@ pub const Loop = struct {...@@ -416,7 +417,7 @@ pub const Loop = struct {
416417
417 /// Bring your own linked list node. This means it can't fail.418 /// Bring your own linked list node. This means it can't fail.
418 pub fn onNextTick(self: *Loop, node: *NextTickNode) void {419 pub fn onNextTick(self: *Loop, node: *NextTickNode) void {
419 _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst);420 self.beginOneEvent(); // finished in dispatch()
420 self.next_tick_queue.put(node);421 self.next_tick_queue.put(node);
421 self.dispatch();422 self.dispatch();
422 }423 }
...@@ -470,8 +471,14 @@ pub const Loop = struct {...@@ -470,8 +471,14 @@ pub const Loop = struct {
470 }471 }
471 }472 }
472473
473 fn finishOneEvent(self: *Loop) void {474 /// call finishOneEvent when done
474 if (@atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst) == 1) {475 pub fn beginOneEvent(self: *Loop) void {
476 _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst);
477 }
478
479 pub fn finishOneEvent(self: *Loop) void {
480 const prev = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst);
481 if (prev == 1) {
475 // cause all the threads to stop482 // cause all the threads to stop
476 switch (builtin.os) {483 switch (builtin.os) {
477 builtin.Os.linux => {484 builtin.Os.linux => {
...@@ -593,7 +600,7 @@ pub const Loop = struct {...@@ -593,7 +600,7 @@ pub const Loop = struct {
593 }600 }
594601
595 fn linuxFsRequest(self: *Loop, request_node: *fs.RequestNode) void {602 fn linuxFsRequest(self: *Loop, request_node: *fs.RequestNode) void {
596 _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst);603 self.beginOneEvent(); // finished in linuxFsRun after processing the msg
597 self.os_data.fs_queue.put(request_node);604 self.os_data.fs_queue.put(request_node);
598 _ = @atomicRmw(i32, &self.os_data.fs_queue_len, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst); // let this wrap605 _ = @atomicRmw(i32, &self.os_data.fs_queue_len, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst); // let this wrap
599 const rc = os.linux.futex_wake(@ptrToInt(&self.os_data.fs_queue_len), os.linux.FUTEX_WAKE, 1);606 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 {...@@ -610,14 +617,21 @@ pub const Loop = struct {
610 while (self.os_data.fs_queue.get()) |node| {617 while (self.os_data.fs_queue.get()) |node| {
611 processed_count +%= 1;618 processed_count +%= 1;
612 switch (node.data.msg) {619 switch (node.data.msg) {
613 @TagType(fs.Request.Msg).PWriteV => @panic("TODO"),620 @TagType(fs.Request.Msg).End => return,
621 @TagType(fs.Request.Msg).PWriteV => |*msg| {
622 msg.result = os.posix_pwritev(msg.fd, msg.iov.ptr, msg.iov.len, msg.offset);
623 },
614 @TagType(fs.Request.Msg).PReadV => |*msg| {624 @TagType(fs.Request.Msg).PReadV => |*msg| {
615 msg.result = os.posix_preadv(msg.fd, msg.iov.ptr, msg.iov.len, msg.offset);625 msg.result = os.posix_preadv(msg.fd, msg.iov.ptr, msg.iov.len, msg.offset);
616 },626 },
617 @TagType(fs.Request.Msg).OpenRead => |*msg| {627 @TagType(fs.Request.Msg).OpenRead => |*msg| {
618 const flags = posix.O_LARGEFILE | posix.O_RDONLY;628 const flags = posix.O_LARGEFILE | posix.O_RDONLY | posix.O_CLOEXEC;
619 msg.result = os.posixOpenC(msg.path.ptr, flags, 0);629 msg.result = os.posixOpenC(msg.path.ptr, flags, 0);
620 },630 },
631 @TagType(fs.Request.Msg).OpenRW => |*msg| {
632 const flags = posix.O_LARGEFILE | posix.O_RDWR | posix.O_CREAT | posix.O_CLOEXEC;
633 msg.result = os.posixOpenC(msg.path.ptr, flags, msg.mode);
634 },
621 @TagType(fs.Request.Msg).Close => |*msg| os.close(msg.fd),635 @TagType(fs.Request.Msg).Close => |*msg| os.close(msg.fd),
622 @TagType(fs.Request.Msg).WriteFile => |*msg| blk: {636 @TagType(fs.Request.Msg).WriteFile => |*msg| blk: {
623 const flags = posix.O_LARGEFILE | posix.O_WRONLY | posix.O_CREAT |637 const flags = posix.O_LARGEFILE | posix.O_WRONLY | posix.O_CREAT |
...@@ -629,7 +643,6 @@ pub const Loop = struct {...@@ -629,7 +643,6 @@ pub const Loop = struct {
629 defer os.close(fd);643 defer os.close(fd);
630 msg.result = os.posixWrite(fd, msg.contents);644 msg.result = os.posixWrite(fd, msg.contents);
631 },645 },
632 @TagType(fs.Request.Msg).End => return,
633 }646 }
634 switch (node.data.finish) {647 switch (node.data.finish) {
635 @TagType(fs.Request.Finish).TickNode => |*tick_node| self.onNextTick(tick_node),648 @TagType(fs.Request.Finish).TickNode => |*tick_node| self.onNextTick(tick_node),
std/event/tcp.zig+2-3
...@@ -55,7 +55,7 @@ pub const Server = struct {...@@ -55,7 +55,7 @@ pub const Server = struct {
55 errdefer cancel self.accept_coro.?;55 errdefer cancel self.accept_coro.?;
5656
57 self.listen_resume_node.handle = self.accept_coro.?;57 self.listen_resume_node.handle = self.accept_coro.?;
58 try self.loop.addFd(sockfd, &self.listen_resume_node);58 try self.loop.linuxAddFd(sockfd, &self.listen_resume_node, posix.EPOLLIN | posix.EPOLLOUT | posix.EPOLLET);
59 errdefer self.loop.removeFd(sockfd);59 errdefer self.loop.removeFd(sockfd);
60 }60 }
6161
...@@ -116,7 +116,7 @@ pub async fn connect(loop: *Loop, _address: *const std.net.Address) !std.os.File...@@ -116,7 +116,7 @@ pub async fn connect(loop: *Loop, _address: *const std.net.Address) !std.os.File
116 errdefer std.os.close(sockfd);116 errdefer std.os.close(sockfd);
117117
118 try std.os.posixConnectAsync(sockfd, &address.os_addr);118 try std.os.posixConnectAsync(sockfd, &address.os_addr);
119 try await try async loop.waitFd(sockfd);119 try await try async loop.linuxWaitFd(sockfd, posix.EPOLLIN | posix.EPOLLOUT);
120 try std.os.posixGetSockOptConnectError(sockfd);120 try std.os.posixGetSockOptConnectError(sockfd);
121121
122 return std.os.File.openHandle(sockfd);122 return std.os.File.openHandle(sockfd);
...@@ -181,4 +181,3 @@ async fn doAsyncTest(loop: *Loop, address: *const std.net.Address, server: *Serv...@@ -181,4 +181,3 @@ async fn doAsyncTest(loop: *Loop, address: *const std.net.Address, server: *Serv
181 assert(mem.eql(u8, msg, "hello from server\n"));181 assert(mem.eql(u8, msg, "hello from server\n"));
182 server.close();182 server.close();
183}183}
184
std/os/file.zig-4
...@@ -29,7 +29,6 @@ pub const File = struct {...@@ -29,7 +29,6 @@ pub const File = struct {
2929
30 /// `path` needs to be copied in memory to add a null terminating byte, hence the allocator.30 /// `path` needs to be copied in memory to add a null terminating byte, hence the allocator.
31 /// Call close to clean up.31 /// Call close to clean up.
32 /// TODO deprecated, just use open
33 pub fn openRead(allocator: *mem.Allocator, path: []const u8) OpenError!File {32 pub fn openRead(allocator: *mem.Allocator, path: []const u8) OpenError!File {
34 if (is_posix) {33 if (is_posix) {
35 const flags = posix.O_LARGEFILE | posix.O_RDONLY;34 const flags = posix.O_LARGEFILE | posix.O_RDONLY;
...@@ -51,7 +50,6 @@ pub const File = struct {...@@ -51,7 +50,6 @@ pub const File = struct {
51 }50 }
5251
53 /// Calls `openWriteMode` with os.File.default_mode for the mode.52 /// Calls `openWriteMode` with os.File.default_mode for the mode.
54 /// TODO deprecated, just use open
55 pub fn openWrite(allocator: *mem.Allocator, path: []const u8) OpenError!File {53 pub fn openWrite(allocator: *mem.Allocator, path: []const u8) OpenError!File {
56 return openWriteMode(allocator, path, os.File.default_mode);54 return openWriteMode(allocator, path, os.File.default_mode);
57 }55 }
...@@ -60,7 +58,6 @@ pub const File = struct {...@@ -60,7 +58,6 @@ pub const File = struct {
60 /// If a file already exists in the destination it will be truncated.58 /// If a file already exists in the destination it will be truncated.
61 /// `path` needs to be copied in memory to add a null terminating byte, hence the allocator.59 /// `path` needs to be copied in memory to add a null terminating byte, hence the allocator.
62 /// Call close to clean up.60 /// Call close to clean up.
63 /// TODO deprecated, just use open
64 pub fn openWriteMode(allocator: *mem.Allocator, path: []const u8, file_mode: Mode) OpenError!File {61 pub fn openWriteMode(allocator: *mem.Allocator, path: []const u8, file_mode: Mode) OpenError!File {
65 if (is_posix) {62 if (is_posix) {
66 const flags = posix.O_LARGEFILE | posix.O_WRONLY | posix.O_CREAT | posix.O_CLOEXEC | posix.O_TRUNC;63 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 {...@@ -85,7 +82,6 @@ pub const File = struct {
85 /// If a file already exists in the destination this returns OpenError.PathAlreadyExists82 /// If a file already exists in the destination this returns OpenError.PathAlreadyExists
86 /// `path` needs to be copied in memory to add a null terminating byte, hence the allocator.83 /// `path` needs to be copied in memory to add a null terminating byte, hence the allocator.
87 /// Call close to clean up.84 /// Call close to clean up.
88 /// TODO deprecated, just use open
89 pub fn openWriteNoClobber(allocator: *mem.Allocator, path: []const u8, file_mode: Mode) OpenError!File {85 pub fn openWriteNoClobber(allocator: *mem.Allocator, path: []const u8, file_mode: Mode) OpenError!File {
90 if (is_posix) {86 if (is_posix) {
91 const flags = posix.O_LARGEFILE | posix.O_WRONLY | posix.O_CREAT | posix.O_CLOEXEC | posix.O_EXCL;87 const flags = posix.O_LARGEFILE | posix.O_WRONLY | posix.O_CREAT | posix.O_CLOEXEC | posix.O_EXCL;
std/os/index.zig+64
...@@ -310,6 +310,29 @@ pub fn posixWrite(fd: i32, bytes: []const u8) !void {...@@ -310,6 +310,29 @@ pub fn posixWrite(fd: i32, bytes: []const u8) !void {
310 }310 }
311}311}
312312
313pub fn posix_pwritev(fd: i32, iov: [*]const posix.iovec_const, count: usize, offset: u64) PosixWriteError!void {
314 while (true) {
315 const rc = posix.pwritev(fd, iov, count, offset);
316 const err = posix.getErrno(rc);
317 switch (err) {
318 0 => return,
319 posix.EINTR => continue,
320 posix.EINVAL => unreachable,
321 posix.EFAULT => unreachable,
322 posix.EAGAIN => return PosixWriteError.WouldBlock,
323 posix.EBADF => return PosixWriteError.FileClosed,
324 posix.EDESTADDRREQ => return PosixWriteError.DestinationAddressRequired,
325 posix.EDQUOT => return PosixWriteError.DiskQuota,
326 posix.EFBIG => return PosixWriteError.FileTooBig,
327 posix.EIO => return PosixWriteError.InputOutput,
328 posix.ENOSPC => return PosixWriteError.NoSpaceLeft,
329 posix.EPERM => return PosixWriteError.AccessDenied,
330 posix.EPIPE => return PosixWriteError.BrokenPipe,
331 else => return unexpectedErrorPosix(err),
332 }
333 }
334}
335
313pub const PosixOpenError = error{336pub const PosixOpenError = error{
314 OutOfMemory,337 OutOfMemory,
315 AccessDenied,338 AccessDenied,
...@@ -2913,3 +2936,44 @@ pub fn bsdKEvent(...@@ -2913,3 +2936,44 @@ pub fn bsdKEvent(
2913 }2936 }
2914 }2937 }
2915}2938}
2939
2940pub fn linuxINotifyInit1(flags: u32) !i32 {
2941 const rc = linux.inotify_init1(flags);
2942 const err = posix.getErrno(rc);
2943 switch (err) {
2944 0 => return @intCast(i32, rc),
2945 posix.EINVAL => unreachable,
2946 posix.EMFILE => return error.ProcessFdQuotaExceeded,
2947 posix.ENFILE => return error.SystemFdQuotaExceeded,
2948 posix.ENOMEM => return error.SystemResources,
2949 else => return unexpectedErrorPosix(err),
2950 }
2951}
2952
2953pub fn linuxINotifyAddWatchC(inotify_fd: i32, pathname: [*]const u8, mask: u32) !i32 {
2954 const rc = linux.inotify_add_watch(inotify_fd, pathname, mask);
2955 const err = posix.getErrno(rc);
2956 switch (err) {
2957 0 => return @intCast(i32, rc),
2958 posix.EACCES => return error.AccessDenied,
2959 posix.EBADF => unreachable,
2960 posix.EFAULT => unreachable,
2961 posix.EINVAL => unreachable,
2962 posix.ENAMETOOLONG => return error.NameTooLong,
2963 posix.ENOENT => return error.FileNotFound,
2964 posix.ENOMEM => return error.SystemResources,
2965 posix.ENOSPC => return error.UserResourceLimitReached,
2966 else => return unexpectedErrorPosix(err),
2967 }
2968}
2969
2970pub fn linuxINotifyRmWatch(inotify_fd: i32, wd: i32) !void {
2971 const rc = linux.inotify_rm_watch(inotify_fd, wd);
2972 const err = posix.getErrno(rc);
2973 switch (err) {
2974 0 => return rc,
2975 posix.EBADF => unreachable,
2976 posix.EINVAL => unreachable,
2977 else => unreachable,
2978 }
2979}
std/os/linux/index.zig+60
...@@ -567,6 +567,37 @@ pub const MNT_DETACH = 2;...@@ -567,6 +567,37 @@ pub const MNT_DETACH = 2;
567pub const MNT_EXPIRE = 4;567pub const MNT_EXPIRE = 4;
568pub const UMOUNT_NOFOLLOW = 8;568pub const UMOUNT_NOFOLLOW = 8;
569569
570pub const IN_CLOEXEC = O_CLOEXEC;
571pub const IN_NONBLOCK = O_NONBLOCK;
572
573pub const IN_ACCESS = 0x00000001;
574pub const IN_MODIFY = 0x00000002;
575pub const IN_ATTRIB = 0x00000004;
576pub const IN_CLOSE_WRITE = 0x00000008;
577pub const IN_CLOSE_NOWRITE = 0x00000010;
578pub const IN_CLOSE = IN_CLOSE_WRITE | IN_CLOSE_NOWRITE;
579pub const IN_OPEN = 0x00000020;
580pub const IN_MOVED_FROM = 0x00000040;
581pub const IN_MOVED_TO = 0x00000080;
582pub const IN_MOVE = IN_MOVED_FROM | IN_MOVED_TO;
583pub const IN_CREATE = 0x00000100;
584pub const IN_DELETE = 0x00000200;
585pub const IN_DELETE_SELF = 0x00000400;
586pub const IN_MOVE_SELF = 0x00000800;
587pub const IN_ALL_EVENTS = 0x00000fff;
588
589pub const IN_UNMOUNT = 0x00002000;
590pub const IN_Q_OVERFLOW = 0x00004000;
591pub const IN_IGNORED = 0x00008000;
592
593pub const IN_ONLYDIR = 0x01000000;
594pub const IN_DONT_FOLLOW = 0x02000000;
595pub const IN_EXCL_UNLINK = 0x04000000;
596pub const IN_MASK_ADD = 0x20000000;
597
598pub const IN_ISDIR = 0x40000000;
599pub const IN_ONESHOT = 0x80000000;
600
570pub const S_IFMT = 0o170000;601pub const S_IFMT = 0o170000;
571602
572pub const S_IFDIR = 0o040000;603pub const S_IFDIR = 0o040000;
...@@ -704,6 +735,18 @@ pub fn getdents(fd: i32, dirp: [*]u8, count: usize) usize {...@@ -704,6 +735,18 @@ pub fn getdents(fd: i32, dirp: [*]u8, count: usize) usize {
704 return syscall3(SYS_getdents, @intCast(usize, fd), @ptrToInt(dirp), count);735 return syscall3(SYS_getdents, @intCast(usize, fd), @ptrToInt(dirp), count);
705}736}
706737
738pub fn inotify_init1(flags: u32) usize {
739 return syscall1(SYS_inotify_init1, flags);
740}
741
742pub fn inotify_add_watch(fd: i32, pathname: [*]const u8, mask: u32) usize {
743 return syscall3(SYS_inotify_add_watch, @intCast(usize, fd), @ptrToInt(pathname), mask);
744}
745
746pub fn inotify_rm_watch(fd: i32, wd: i32) usize {
747 return syscall2(SYS_inotify_rm_watch, @intCast(usize, fd), @intCast(usize, wd));
748}
749
707pub fn isatty(fd: i32) bool {750pub fn isatty(fd: i32) bool {
708 var wsz: winsize = undefined;751 var wsz: winsize = undefined;
709 return syscall3(SYS_ioctl, @intCast(usize, fd), TIOCGWINSZ, @ptrToInt(&wsz)) == 0;752 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 {...@@ -750,6 +793,10 @@ pub fn preadv(fd: i32, iov: [*]const iovec, count: usize, offset: u64) usize {
750 return syscall4(SYS_preadv, @intCast(usize, fd), @ptrToInt(iov), count, offset);793 return syscall4(SYS_preadv, @intCast(usize, fd), @ptrToInt(iov), count, offset);
751}794}
752795
796pub fn pwritev(fd: i32, iov: [*]const iovec_const, count: usize, offset: u64) usize {
797 return syscall4(SYS_pwritev, @intCast(usize, fd), @ptrToInt(iov), count, offset);
798}
799
753// TODO https://github.com/ziglang/zig/issues/265800// TODO https://github.com/ziglang/zig/issues/265
754pub fn rmdir(path: [*]const u8) usize {801pub fn rmdir(path: [*]const u8) usize {
755 return syscall1(SYS_rmdir, @ptrToInt(path));802 return syscall1(SYS_rmdir, @ptrToInt(path));
...@@ -1068,6 +1115,11 @@ pub const iovec = extern struct {...@@ -1068,6 +1115,11 @@ pub const iovec = extern struct {
1068 iov_len: usize,1115 iov_len: usize,
1069};1116};
10701117
1118pub const iovec_const = extern struct {
1119 iov_base: [*]const u8,
1120 iov_len: usize,
1121};
1122
1071pub fn getsockname(fd: i32, noalias addr: *sockaddr, noalias len: *socklen_t) usize {1123pub fn getsockname(fd: i32, noalias addr: *sockaddr, noalias len: *socklen_t) usize {
1072 return syscall3(SYS_getsockname, @intCast(usize, fd), @ptrToInt(addr), @ptrToInt(len));1124 return syscall3(SYS_getsockname, @intCast(usize, fd), @ptrToInt(addr), @ptrToInt(len));
1073}1125}
...@@ -1376,6 +1428,14 @@ pub fn capset(hdrp: *cap_user_header_t, datap: *const cap_user_data_t) usize {...@@ -1376,6 +1428,14 @@ pub fn capset(hdrp: *cap_user_header_t, datap: *const cap_user_data_t) usize {
1376 return syscall2(SYS_capset, @ptrToInt(hdrp), @ptrToInt(datap));1428 return syscall2(SYS_capset, @ptrToInt(hdrp), @ptrToInt(datap));
1377}1429}
13781430
1431pub const inotify_event = extern struct {
1432 wd: i32,
1433 mask: u32,
1434 cookie: u32,
1435 len: u32,
1436 //name: [?]u8,
1437};
1438
1379test "import" {1439test "import" {
1380 if (builtin.os == builtin.Os.linux) {1440 if (builtin.os == builtin.Os.linux) {
1381 _ = @import("test.zig");1441 _ = @import("test.zig");