authorgravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2019-11-24 14:08:51+02:00
committergravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2019-11-24 17:42:20+02:00
log20f5f5698600589b32f3c4af19e856b5faf578da
tree6b92238407377508a5bf2b63f56c39b9a1f21f54
parentab534cc9f16697da4846820b038e17fd1bc27f35
signaturelock-open Commit is signed but in an unrecognized format.

uncomment event.fs.watch


1 files changed, 590 insertions(+), 590 deletions(-)

lib/std/event/fs.zig+590-590
......@@ -720,602 +720,602 @@ fn hashString(s: []const u16) u32 {
720720 return @truncate(u32, std.hash.Wyhash.hash(0, @sliceToBytes(s)));
721721}
722722
723//pub const WatchEventError = error{
724// UserResourceLimitReached,
725// SystemResources,
726// AccessDenied,
727// Unexpected, // TODO remove this possibility
728//};
729//
730//pub fn Watch(comptime V: type) type {
731// return struct {
732// channel: *event.Channel(Event.Error!Event),
733// os_data: OsData,
734//
735// const OsData = switch (builtin.os) {
736// .macosx, .freebsd, .netbsd, .dragonfly => struct {
737// file_table: FileTable,
738// table_lock: event.Lock,
739//
740// const FileTable = std.StringHashmap(*Put);
741// const Put = struct {
742// putter: anyframe,
743// value_ptr: *V,
744// };
745// },
746//
747// .linux => LinuxOsData,
748// .windows => WindowsOsData,
749//
750// else => @compileError("Unsupported OS"),
751// };
752//
753// const WindowsOsData = struct {
754// table_lock: event.Lock,
755// dir_table: DirTable,
756// all_putters: std.atomic.Queue(anyframe),
757// ref_count: std.atomic.Int(usize),
758//
759// const DirTable = std.StringHashMap(*Dir);
760// const FileTable = std.HashMap([]const u16, V, hashString, eqlString);
761//
762// const Dir = struct {
763// putter: anyframe,
764// file_table: FileTable,
765// table_lock: event.Lock,
766// };
767// };
768//
769// const LinuxOsData = struct {
770// putter: anyframe,
771// inotify_fd: i32,
772// wd_table: WdTable,
773// table_lock: event.Lock,
774//
775// const WdTable = std.AutoHashMap(i32, Dir);
776// const FileTable = std.StringHashMap(V);
777//
778// const Dir = struct {
779// dirname: []const u8,
780// file_table: FileTable,
781// };
782// };
783//
784// const FileToHandle = std.StringHashMap(anyframe);
785//
786// const Self = @This();
787//
788// pub const Event = struct {
789// id: Id,
790// data: V,
791//
792// pub const Id = WatchEventId;
793// pub const Error = WatchEventError;
794// };
795//
796// pub fn create(loop: *Loop, event_buf_count: usize) !*Self {
797// const channel = try event.Channel(Self.Event.Error!Self.Event).create(loop, event_buf_count);
798// errdefer channel.destroy();
799//
800// switch (builtin.os) {
801// .linux => {
802// const inotify_fd = try os.inotify_init1(os.linux.IN_NONBLOCK | os.linux.IN_CLOEXEC);
803// errdefer os.close(inotify_fd);
804//
805// var result: *Self = undefined;
806// _ = try async<loop.allocator> linuxEventPutter(inotify_fd, channel, &result);
807// return result;
808// },
809//
810// .windows => {
811// const self = try loop.allocator.create(Self);
812// errdefer loop.allocator.destroy(self);
813// self.* = Self{
814// .channel = channel,
815// .os_data = OsData{
816// .table_lock = event.Lock.init(loop),
817// .dir_table = OsData.DirTable.init(loop.allocator),
818// .ref_count = std.atomic.Int(usize).init(1),
819// .all_putters = std.atomic.Queue(anyframe).init(),
820// },
821// };
822// return self;
823// },
824//
825// .macosx, .freebsd, .netbsd, .dragonfly => {
826// const self = try loop.allocator.create(Self);
827// errdefer loop.allocator.destroy(self);
828//
829// self.* = Self{
830// .channel = channel,
831// .os_data = OsData{
832// .table_lock = event.Lock.init(loop),
833// .file_table = OsData.FileTable.init(loop.allocator),
834// },
835// };
836// return self;
837// },
838// else => @compileError("Unsupported OS"),
839// }
840// }
841//
842// /// All addFile calls and removeFile calls must have completed.
843// pub fn destroy(self: *Self) void {
844// switch (builtin.os) {
845// .macosx, .freebsd, .netbsd, .dragonfly => {
846// // TODO we need to cancel the frames before destroying the lock
847// self.os_data.table_lock.deinit();
848// var it = self.os_data.file_table.iterator();
849// while (it.next()) |entry| {
850// cancel entry.value.putter;
851// self.channel.loop.allocator.free(entry.key);
852// }
853// self.channel.destroy();
854// },
855// .linux => cancel self.os_data.putter,
856// .windows => {
857// while (self.os_data.all_putters.get()) |putter_node| {
858// cancel putter_node.data;
859// }
860// self.deref();
861// },
862// else => @compileError("Unsupported OS"),
863// }
864// }
865//
866// fn ref(self: *Self) void {
867// _ = self.os_data.ref_count.incr();
868// }
869//
870// fn deref(self: *Self) void {
871// if (self.os_data.ref_count.decr() == 1) {
872// const allocator = self.channel.loop.allocator;
873// self.os_data.table_lock.deinit();
874// var it = self.os_data.dir_table.iterator();
875// while (it.next()) |entry| {
876// allocator.free(entry.key);
877// allocator.destroy(entry.value);
878// }
879// self.os_data.dir_table.deinit();
880// self.channel.destroy();
881// allocator.destroy(self);
882// }
883// }
884//
885// pub async fn addFile(self: *Self, file_path: []const u8, value: V) !?V {
886// switch (builtin.os) {
887// .macosx, .freebsd, .netbsd, .dragonfly => return await (async addFileKEvent(self, file_path, value) catch unreachable),
888// .linux => return await (async addFileLinux(self, file_path, value) catch unreachable),
889// .windows => return await (async addFileWindows(self, file_path, value) catch unreachable),
890// else => @compileError("Unsupported OS"),
891// }
892// }
893//
894// async fn addFileKEvent(self: *Self, file_path: []const u8, value: V) !?V {
895// const resolved_path = try std.fs.path.resolve(self.channel.loop.allocator, [_][]const u8{file_path});
896// var resolved_path_consumed = false;
897// defer if (!resolved_path_consumed) self.channel.loop.allocator.free(resolved_path);
898//
899// var close_op = try CloseOperation.start(self.channel.loop);
900// var close_op_consumed = false;
901// defer if (!close_op_consumed) close_op.finish();
902//
903// const flags = if (comptime std.Target.current.isDarwin()) os.O_SYMLINK | os.O_EVTONLY else 0;
904// const mode = 0;
905// const fd = try await (async openPosix(self.channel.loop, resolved_path, flags, mode) catch unreachable);
906// close_op.setHandle(fd);
907//
908// var put_data: *OsData.Put = undefined;
909// const putter = try async self.kqPutEvents(close_op, value, &put_data);
910// close_op_consumed = true;
911// errdefer cancel putter;
912//
913// const result = blk: {
914// const held = await (async self.os_data.table_lock.acquire() catch unreachable);
915// defer held.release();
916//
917// const gop = try self.os_data.file_table.getOrPut(resolved_path);
918// if (gop.found_existing) {
919// const prev_value = gop.kv.value.value_ptr.*;
920// cancel gop.kv.value.putter;
921// gop.kv.value = put_data;
922// break :blk prev_value;
923// } else {
924// resolved_path_consumed = true;
925// gop.kv.value = put_data;
926// break :blk null;
927// }
928// };
929//
930// return result;
931// }
932//
933// async fn kqPutEvents(self: *Self, close_op: *CloseOperation, value: V, out_put: **OsData.Put) void {
934// var value_copy = value;
935// var put = OsData.Put{
936// .putter = @frame(),
937// .value_ptr = &value_copy,
938// };
939// out_put.* = &put;
940// self.channel.loop.beginOneEvent();
941//
942// defer {
943// close_op.finish();
944// self.channel.loop.finishOneEvent();
945// }
946//
947// while (true) {
948// if (await (async self.channel.loop.bsdWaitKev(
949// @intCast(usize, close_op.getHandle()),
950// os.EVFILT_VNODE,
951// os.NOTE_WRITE | os.NOTE_DELETE,
952// ) catch unreachable)) |kev| {
953// // TODO handle EV_ERROR
954// if (kev.fflags & os.NOTE_DELETE != 0) {
955// await (async self.channel.put(Self.Event{
956// .id = Event.Id.Delete,
957// .data = value_copy,
958// }) catch unreachable);
959// } else if (kev.fflags & os.NOTE_WRITE != 0) {
960// await (async self.channel.put(Self.Event{
961// .id = Event.Id.CloseWrite,
962// .data = value_copy,
963// }) catch unreachable);
964// }
965// } else |err| switch (err) {
966// error.EventNotFound => unreachable,
967// error.ProcessNotFound => unreachable,
968// error.Overflow => unreachable,
969// error.AccessDenied, error.SystemResources => |casted_err| {
970// await (async self.channel.put(casted_err) catch unreachable);
971// },
972// }
973// }
974// }
975//
976// async fn addFileLinux(self: *Self, file_path: []const u8, value: V) !?V {
977// const value_copy = value;
978//
979// const dirname = std.fs.path.dirname(file_path) orelse ".";
980// const dirname_with_null = try std.cstr.addNullByte(self.channel.loop.allocator, dirname);
981// var dirname_with_null_consumed = false;
982// defer if (!dirname_with_null_consumed) self.channel.loop.allocator.free(dirname_with_null);
983//
984// const basename = std.fs.path.basename(file_path);
985// const basename_with_null = try std.cstr.addNullByte(self.channel.loop.allocator, basename);
986// var basename_with_null_consumed = false;
987// defer if (!basename_with_null_consumed) self.channel.loop.allocator.free(basename_with_null);
988//
989// const wd = try os.inotify_add_watchC(
990// self.os_data.inotify_fd,
991// dirname_with_null.ptr,
992// os.linux.IN_CLOSE_WRITE | os.linux.IN_ONLYDIR | os.linux.IN_EXCL_UNLINK,
993// );
994// // wd is either a newly created watch or an existing one.
995//
996// const held = await (async self.os_data.table_lock.acquire() catch unreachable);
997// defer held.release();
998//
999// const gop = try self.os_data.wd_table.getOrPut(wd);
1000// if (!gop.found_existing) {
1001// gop.kv.value = OsData.Dir{
1002// .dirname = dirname_with_null,
1003// .file_table = OsData.FileTable.init(self.channel.loop.allocator),
1004// };
1005// dirname_with_null_consumed = true;
1006// }
1007// const dir = &gop.kv.value;
1008//
1009// const file_table_gop = try dir.file_table.getOrPut(basename_with_null);
1010// if (file_table_gop.found_existing) {
1011// const prev_value = file_table_gop.kv.value;
1012// file_table_gop.kv.value = value_copy;
1013// return prev_value;
1014// } else {
1015// file_table_gop.kv.value = value_copy;
1016// basename_with_null_consumed = true;
1017// return null;
1018// }
1019// }
1020//
1021// async fn addFileWindows(self: *Self, file_path: []const u8, value: V) !?V {
1022// const value_copy = value;
1023// // TODO we might need to convert dirname and basename to canonical file paths ("short"?)
1024//
1025// const dirname = try std.mem.dupe(self.channel.loop.allocator, u8, std.fs.path.dirname(file_path) orelse ".");
1026// var dirname_consumed = false;
1027// defer if (!dirname_consumed) self.channel.loop.allocator.free(dirname);
1028//
1029// const dirname_utf16le = try std.unicode.utf8ToUtf16LeWithNull(self.channel.loop.allocator, dirname);
1030// defer self.channel.loop.allocator.free(dirname_utf16le);
1031//
1032// // TODO https://github.com/ziglang/zig/issues/265
1033// const basename = std.fs.path.basename(file_path);
1034// const basename_utf16le_null = try std.unicode.utf8ToUtf16LeWithNull(self.channel.loop.allocator, basename);
1035// var basename_utf16le_null_consumed = false;
1036// defer if (!basename_utf16le_null_consumed) self.channel.loop.allocator.free(basename_utf16le_null);
1037// const basename_utf16le_no_null = basename_utf16le_null[0 .. basename_utf16le_null.len - 1];
1038//
1039// const dir_handle = try windows.CreateFileW(
1040// dirname_utf16le.ptr,
1041// windows.FILE_LIST_DIRECTORY,
1042// windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE | windows.FILE_SHARE_WRITE,
1043// null,
1044// windows.OPEN_EXISTING,
1045// windows.FILE_FLAG_BACKUP_SEMANTICS | windows.FILE_FLAG_OVERLAPPED,
1046// null,
1047// );
1048// var dir_handle_consumed = false;
1049// defer if (!dir_handle_consumed) windows.CloseHandle(dir_handle);
1050//
1051// const held = await (async self.os_data.table_lock.acquire() catch unreachable);
1052// defer held.release();
1053//
1054// const gop = try self.os_data.dir_table.getOrPut(dirname);
1055// if (gop.found_existing) {
1056// const dir = gop.kv.value;
1057// const held_dir_lock = await (async dir.table_lock.acquire() catch unreachable);
1058// defer held_dir_lock.release();
1059//
1060// const file_gop = try dir.file_table.getOrPut(basename_utf16le_no_null);
1061// if (file_gop.found_existing) {
1062// const prev_value = file_gop.kv.value;
1063// file_gop.kv.value = value_copy;
1064// return prev_value;
1065// } else {
1066// file_gop.kv.value = value_copy;
1067// basename_utf16le_null_consumed = true;
1068// return null;
1069// }
1070// } else {
1071// errdefer _ = self.os_data.dir_table.remove(dirname);
1072// const dir = try self.channel.loop.allocator.create(OsData.Dir);
1073// errdefer self.channel.loop.allocator.destroy(dir);
1074//
1075// dir.* = OsData.Dir{
1076// .file_table = OsData.FileTable.init(self.channel.loop.allocator),
1077// .table_lock = event.Lock.init(self.channel.loop),
1078// .putter = undefined,
1079// };
1080// gop.kv.value = dir;
1081// assert((try dir.file_table.put(basename_utf16le_no_null, value_copy)) == null);
1082// basename_utf16le_null_consumed = true;
1083//
1084// dir.putter = try async self.windowsDirReader(dir_handle, dir);
1085// dir_handle_consumed = true;
1086//
1087// dirname_consumed = true;
1088//
1089// return null;
1090// }
1091// }
1092//
1093// async fn windowsDirReader(self: *Self, dir_handle: windows.HANDLE, dir: *OsData.Dir) void {
1094// self.ref();
1095// defer self.deref();
1096//
1097// defer os.close(dir_handle);
1098//
1099// var putter_node = std.atomic.Queue(anyframe).Node{
1100// .data = @frame(),
1101// .prev = null,
1102// .next = null,
1103// };
1104// self.os_data.all_putters.put(&putter_node);
1105// defer _ = self.os_data.all_putters.remove(&putter_node);
1106//
1107// var resume_node = Loop.ResumeNode.Basic{
1108// .base = Loop.ResumeNode{
1109// .id = Loop.ResumeNode.Id.Basic,
1110// .handle = @frame(),
1111// .overlapped = windows.OVERLAPPED{
1112// .Internal = 0,
1113// .InternalHigh = 0,
1114// .Offset = 0,
1115// .OffsetHigh = 0,
1116// .hEvent = null,
1117// },
1118// },
1119// };
1120// var event_buf: [4096]u8 align(@alignOf(windows.FILE_NOTIFY_INFORMATION)) = undefined;
1121//
1122// // TODO handle this error not in the channel but in the setup
1123// _ = windows.CreateIoCompletionPort(
1124// dir_handle,
1125// self.channel.loop.os_data.io_port,
1126// undefined,
1127// undefined,
1128// ) catch |err| {
1129// await (async self.channel.put(err) catch unreachable);
1130// return;
1131// };
1132//
1133// while (true) {
1134// {
1135// // TODO only 1 beginOneEvent for the whole function
1136// self.channel.loop.beginOneEvent();
1137// errdefer self.channel.loop.finishOneEvent();
1138// errdefer {
1139// _ = windows.kernel32.CancelIoEx(dir_handle, &resume_node.base.overlapped);
1140// }
1141// suspend {
1142// _ = windows.kernel32.ReadDirectoryChangesW(
1143// dir_handle,
1144// &event_buf,
1145// @intCast(windows.DWORD, event_buf.len),
1146// windows.FALSE, // watch subtree
1147// windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME |
1148// windows.FILE_NOTIFY_CHANGE_ATTRIBUTES | windows.FILE_NOTIFY_CHANGE_SIZE |
1149// windows.FILE_NOTIFY_CHANGE_LAST_WRITE | windows.FILE_NOTIFY_CHANGE_LAST_ACCESS |
1150// windows.FILE_NOTIFY_CHANGE_CREATION | windows.FILE_NOTIFY_CHANGE_SECURITY,
1151// null, // number of bytes transferred (unused for async)
1152// &resume_node.base.overlapped,
1153// null, // completion routine - unused because we use IOCP
1154// );
1155// }
1156// }
1157// var bytes_transferred: windows.DWORD = undefined;
1158// if (windows.kernel32.GetOverlappedResult(dir_handle, &resume_node.base.overlapped, &bytes_transferred, windows.FALSE) == 0) {
1159// const err = switch (windows.kernel32.GetLastError()) {
1160// else => |err| windows.unexpectedError(err),
1161// };
1162// await (async self.channel.put(err) catch unreachable);
1163// } else {
1164// // can't use @bytesToSlice because of the special variable length name field
1165// var ptr = event_buf[0..].ptr;
1166// const end_ptr = ptr + bytes_transferred;
1167// var ev: *windows.FILE_NOTIFY_INFORMATION = undefined;
1168// while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) : (ptr += ev.NextEntryOffset) {
1169// ev = @ptrCast(*windows.FILE_NOTIFY_INFORMATION, ptr);
1170// const emit = switch (ev.Action) {
1171// windows.FILE_ACTION_REMOVED => WatchEventId.Delete,
1172// windows.FILE_ACTION_MODIFIED => WatchEventId.CloseWrite,
1173// else => null,
1174// };
1175// if (emit) |id| {
1176// const basename_utf16le = ([*]u16)(&ev.FileName)[0 .. ev.FileNameLength / 2];
1177// const user_value = blk: {
1178// const held = await (async dir.table_lock.acquire() catch unreachable);
1179// defer held.release();
1180//
1181// if (dir.file_table.get(basename_utf16le)) |entry| {
1182// break :blk entry.value;
1183// } else {
1184// break :blk null;
1185// }
1186// };
1187// if (user_value) |v| {
1188// await (async self.channel.put(Event{
1189// .id = id,
1190// .data = v,
1191// }) catch unreachable);
1192// }
1193// }
1194// if (ev.NextEntryOffset == 0) break;
1195// }
1196// }
1197// }
1198// }
1199//
1200// pub async fn removeFile(self: *Self, file_path: []const u8) ?V {
1201// @panic("TODO");
1202// }
1203//
1204// async fn linuxEventPutter(inotify_fd: i32, channel: *event.Channel(Event.Error!Event), out_watch: **Self) void {
1205// const loop = channel.loop;
1206//
1207// var watch = Self{
1208// .channel = channel,
1209// .os_data = OsData{
1210// .putter = @frame(),
1211// .inotify_fd = inotify_fd,
1212// .wd_table = OsData.WdTable.init(loop.allocator),
1213// .table_lock = event.Lock.init(loop),
1214// },
1215// };
1216// out_watch.* = &watch;
1217//
1218// loop.beginOneEvent();
1219//
1220// defer {
1221// watch.os_data.table_lock.deinit();
1222// var wd_it = watch.os_data.wd_table.iterator();
1223// while (wd_it.next()) |wd_entry| {
1224// var file_it = wd_entry.value.file_table.iterator();
1225// while (file_it.next()) |file_entry| {
1226// loop.allocator.free(file_entry.key);
1227// }
1228// loop.allocator.free(wd_entry.value.dirname);
1229// }
1230// loop.finishOneEvent();
1231// os.close(inotify_fd);
1232// channel.destroy();
1233// }
1234//
1235// var event_buf: [4096]u8 align(@alignOf(os.linux.inotify_event)) = undefined;
1236//
1237// while (true) {
1238// const rc = os.linux.read(inotify_fd, &event_buf, event_buf.len);
1239// const errno = os.linux.getErrno(rc);
1240// switch (errno) {
1241// 0 => {
1242// // can't use @bytesToSlice because of the special variable length name field
1243// var ptr = event_buf[0..].ptr;
1244// const end_ptr = ptr + event_buf.len;
1245// var ev: *os.linux.inotify_event = undefined;
1246// while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) : (ptr += @sizeOf(os.linux.inotify_event) + ev.len) {
1247// ev = @ptrCast(*os.linux.inotify_event, ptr);
1248// if (ev.mask & os.linux.IN_CLOSE_WRITE == os.linux.IN_CLOSE_WRITE) {
1249// const basename_ptr = ptr + @sizeOf(os.linux.inotify_event);
1250// const basename_with_null = basename_ptr[0 .. std.mem.len(u8, basename_ptr) + 1];
1251// const user_value = blk: {
1252// const held = await (async watch.os_data.table_lock.acquire() catch unreachable);
1253// defer held.release();
1254//
1255// const dir = &watch.os_data.wd_table.get(ev.wd).?.value;
1256// if (dir.file_table.get(basename_with_null)) |entry| {
1257// break :blk entry.value;
1258// } else {
1259// break :blk null;
1260// }
1261// };
1262// if (user_value) |v| {
1263// await (async channel.put(Event{
1264// .id = WatchEventId.CloseWrite,
1265// .data = v,
1266// }) catch unreachable);
1267// }
1268// }
1269// }
1270// },
1271// os.linux.EINTR => continue,
1272// os.linux.EINVAL => unreachable,
1273// os.linux.EFAULT => unreachable,
1274// os.linux.EAGAIN => {
1275// (await (async loop.linuxWaitFd(
1276// inotify_fd,
1277// os.linux.EPOLLET | os.linux.EPOLLIN,
1278// ) catch unreachable)) catch |err| {
1279// const transformed_err = switch (err) {
1280// error.FileDescriptorAlreadyPresentInSet => unreachable,
1281// error.OperationCausesCircularLoop => unreachable,
1282// error.FileDescriptorNotRegistered => unreachable,
1283// error.FileDescriptorIncompatibleWithEpoll => unreachable,
1284// error.Unexpected => unreachable,
1285// else => |e| e,
1286// };
1287// await (async channel.put(transformed_err) catch unreachable);
1288// };
1289// },
1290// else => unreachable,
1291// }
1292// }
1293// }
1294// };
1295//}
723pub const WatchEventError = error{
724 UserResourceLimitReached,
725 SystemResources,
726 AccessDenied,
727 Unexpected, // TODO remove this possibility
728};
729
730pub fn Watch(comptime V: type) type {
731 return struct {
732 channel: *event.Channel(Event.Error!Event),
733 os_data: OsData,
734
735 const OsData = switch (builtin.os) {
736 .macosx, .freebsd, .netbsd, .dragonfly => struct {
737 file_table: FileTable,
738 table_lock: event.Lock,
739
740 const FileTable = std.StringHashmap(*Put);
741 const Put = struct {
742 putter: anyframe,
743 value_ptr: *V,
744 };
745 },
746
747 .linux => LinuxOsData,
748 .windows => WindowsOsData,
749
750 else => @compileError("Unsupported OS"),
751 };
752
753 const WindowsOsData = struct {
754 table_lock: event.Lock,
755 dir_table: DirTable,
756 all_putters: std.atomic.Queue(anyframe),
757 ref_count: std.atomic.Int(usize),
758
759 const DirTable = std.StringHashMap(*Dir);
760 const FileTable = std.HashMap([]const u16, V, hashString, eqlString);
761
762 const Dir = struct {
763 putter: anyframe,
764 file_table: FileTable,
765 table_lock: event.Lock,
766 };
767 };
768
769 const LinuxOsData = struct {
770 putter: anyframe,
771 inotify_fd: i32,
772 wd_table: WdTable,
773 table_lock: event.Lock,
774
775 const WdTable = std.AutoHashMap(i32, Dir);
776 const FileTable = std.StringHashMap(V);
777
778 const Dir = struct {
779 dirname: []const u8,
780 file_table: FileTable,
781 };
782 };
783
784 const FileToHandle = std.StringHashMap(anyframe);
785
786 const Self = @This();
787
788 pub const Event = struct {
789 id: Id,
790 data: V,
791
792 pub const Id = WatchEventId;
793 pub const Error = WatchEventError;
794 };
795
796 pub fn create(loop: *Loop, event_buf_count: usize) !*Self {
797 const channel = try event.Channel(Self.Event.Error!Self.Event).create(loop, event_buf_count);
798 errdefer channel.destroy();
799
800 switch (builtin.os) {
801 .linux => {
802 const inotify_fd = try os.inotify_init1(os.linux.IN_NONBLOCK | os.linux.IN_CLOEXEC);
803 errdefer os.close(inotify_fd);
804
805 var result: *Self = undefined;
806// _ = try async<loop.allocator> linuxEventPutter(inotify_fd, channel, &result);
807 return result;
808 },
809
810 .windows => {
811 const self = try loop.allocator.create(Self);
812 errdefer loop.allocator.destroy(self);
813 self.* = Self{
814 .channel = channel,
815 .os_data = OsData{
816 .table_lock = event.Lock.init(loop),
817 .dir_table = OsData.DirTable.init(loop.allocator),
818 .ref_count = std.atomic.Int(usize).init(1),
819 .all_putters = std.atomic.Queue(anyframe).init(),
820 },
821 };
822 return self;
823 },
824
825 .macosx, .freebsd, .netbsd, .dragonfly => {
826 const self = try loop.allocator.create(Self);
827 errdefer loop.allocator.destroy(self);
828
829 self.* = Self{
830 .channel = channel,
831 .os_data = OsData{
832 .table_lock = event.Lock.init(loop),
833 .file_table = OsData.FileTable.init(loop.allocator),
834 },
835 };
836 return self;
837 },
838 else => @compileError("Unsupported OS"),
839 }
840 }
841
842 /// All addFile calls and removeFile calls must have completed.
843 pub fn destroy(self: *Self) void {
844 switch (builtin.os) {
845 .macosx, .freebsd, .netbsd, .dragonfly => {
846 // TODO we need to cancel the frames before destroying the lock
847 self.os_data.table_lock.deinit();
848 var it = self.os_data.file_table.iterator();
849 while (it.next()) |entry| {
850// cancel entry.value.putter;
851 self.channel.loop.allocator.free(entry.key);
852 }
853 self.channel.destroy();
854 },
855// .linux => cancel self.os_data.putter,
856 .windows => {
857 while (self.os_data.all_putters.get()) |putter_node| {
858// cancel putter_node.data;
859 }
860 self.deref();
861 },
862 else => @compileError("Unsupported OS"),
863 }
864 }
865
866 fn ref(self: *Self) void {
867 _ = self.os_data.ref_count.incr();
868 }
869
870 fn deref(self: *Self) void {
871 if (self.os_data.ref_count.decr() == 1) {
872 const allocator = self.channel.loop.allocator;
873 self.os_data.table_lock.deinit();
874 var it = self.os_data.dir_table.iterator();
875 while (it.next()) |entry| {
876 allocator.free(entry.key);
877 allocator.destroy(entry.value);
878 }
879 self.os_data.dir_table.deinit();
880 self.channel.destroy();
881 allocator.destroy(self);
882 }
883 }
884
885 pub async fn addFile(self: *Self, file_path: []const u8, value: V) !?V {
886 switch (builtin.os) {
887 .macosx, .freebsd, .netbsd, .dragonfly => return await (async addFileKEvent(self, file_path, value) catch unreachable),
888 .linux => return await (async addFileLinux(self, file_path, value) catch unreachable),
889 .windows => return await (async addFileWindows(self, file_path, value) catch unreachable),
890 else => @compileError("Unsupported OS"),
891 }
892 }
893
894 async fn addFileKEvent(self: *Self, file_path: []const u8, value: V) !?V {
895 const resolved_path = try std.fs.path.resolve(self.channel.loop.allocator, [_][]const u8{file_path});
896 var resolved_path_consumed = false;
897 defer if (!resolved_path_consumed) self.channel.loop.allocator.free(resolved_path);
898
899 var close_op = try CloseOperation.start(self.channel.loop);
900 var close_op_consumed = false;
901 defer if (!close_op_consumed) close_op.finish();
902
903 const flags = if (comptime std.Target.current.isDarwin()) os.O_SYMLINK | os.O_EVTONLY else 0;
904 const mode = 0;
905 const fd = try await (async openPosix(self.channel.loop, resolved_path, flags, mode) catch unreachable);
906 close_op.setHandle(fd);
907
908 var put_data: *OsData.Put = undefined;
909 const putter = try async self.kqPutEvents(close_op, value, &put_data);
910 close_op_consumed = true;
911// errdefer cancel putter;
912
913 const result = blk: {
914 const held = await (async self.os_data.table_lock.acquire() catch unreachable);
915 defer held.release();
916
917 const gop = try self.os_data.file_table.getOrPut(resolved_path);
918 if (gop.found_existing) {
919 const prev_value = gop.kv.value.value_ptr.*;
920// cancel gop.kv.value.putter;
921 gop.kv.value = put_data;
922 break :blk prev_value;
923 } else {
924 resolved_path_consumed = true;
925 gop.kv.value = put_data;
926 break :blk null;
927 }
928 };
929
930 return result;
931 }
932
933 async fn kqPutEvents(self: *Self, close_op: *CloseOperation, value: V, out_put: **OsData.Put) void {
934 var value_copy = value;
935 var put = OsData.Put{
936 .putter = @frame(),
937 .value_ptr = &value_copy,
938 };
939 out_put.* = &put;
940 self.channel.loop.beginOneEvent();
941
942 defer {
943 close_op.finish();
944 self.channel.loop.finishOneEvent();
945 }
946
947 while (true) {
948 if (await (async self.channel.loop.bsdWaitKev(
949 @intCast(usize, close_op.getHandle()),
950 os.EVFILT_VNODE,
951 os.NOTE_WRITE | os.NOTE_DELETE,
952 ) catch unreachable)) |kev| {
953 // TODO handle EV_ERROR
954 if (kev.fflags & os.NOTE_DELETE != 0) {
955 await (async self.channel.put(Self.Event{
956 .id = Event.Id.Delete,
957 .data = value_copy,
958 }) catch unreachable);
959 } else if (kev.fflags & os.NOTE_WRITE != 0) {
960 await (async self.channel.put(Self.Event{
961 .id = Event.Id.CloseWrite,
962 .data = value_copy,
963 }) catch unreachable);
964 }
965 } else |err| switch (err) {
966 error.EventNotFound => unreachable,
967 error.ProcessNotFound => unreachable,
968 error.Overflow => unreachable,
969 error.AccessDenied, error.SystemResources => |casted_err| {
970 await (async self.channel.put(casted_err) catch unreachable);
971 },
972 }
973 }
974 }
975
976 async fn addFileLinux(self: *Self, file_path: []const u8, value: V) !?V {
977 const value_copy = value;
978
979 const dirname = std.fs.path.dirname(file_path) orelse ".";
980 const dirname_with_null = try std.cstr.addNullByte(self.channel.loop.allocator, dirname);
981 var dirname_with_null_consumed = false;
982 defer if (!dirname_with_null_consumed) self.channel.loop.allocator.free(dirname_with_null);
983
984 const basename = std.fs.path.basename(file_path);
985 const basename_with_null = try std.cstr.addNullByte(self.channel.loop.allocator, basename);
986 var basename_with_null_consumed = false;
987 defer if (!basename_with_null_consumed) self.channel.loop.allocator.free(basename_with_null);
988
989 const wd = try os.inotify_add_watchC(
990 self.os_data.inotify_fd,
991 dirname_with_null.ptr,
992 os.linux.IN_CLOSE_WRITE | os.linux.IN_ONLYDIR | os.linux.IN_EXCL_UNLINK,
993 );
994 // wd is either a newly created watch or an existing one.
995
996 const held = await (async self.os_data.table_lock.acquire() catch unreachable);
997 defer held.release();
998
999 const gop = try self.os_data.wd_table.getOrPut(wd);
1000 if (!gop.found_existing) {
1001 gop.kv.value = OsData.Dir{
1002 .dirname = dirname_with_null,
1003 .file_table = OsData.FileTable.init(self.channel.loop.allocator),
1004 };
1005 dirname_with_null_consumed = true;
1006 }
1007 const dir = &gop.kv.value;
1008
1009 const file_table_gop = try dir.file_table.getOrPut(basename_with_null);
1010 if (file_table_gop.found_existing) {
1011 const prev_value = file_table_gop.kv.value;
1012 file_table_gop.kv.value = value_copy;
1013 return prev_value;
1014 } else {
1015 file_table_gop.kv.value = value_copy;
1016 basename_with_null_consumed = true;
1017 return null;
1018 }
1019 }
1020
1021 async fn addFileWindows(self: *Self, file_path: []const u8, value: V) !?V {
1022 const value_copy = value;
1023 // TODO we might need to convert dirname and basename to canonical file paths ("short"?)
1024
1025 const dirname = try std.mem.dupe(self.channel.loop.allocator, u8, std.fs.path.dirname(file_path) orelse ".");
1026 var dirname_consumed = false;
1027 defer if (!dirname_consumed) self.channel.loop.allocator.free(dirname);
1028
1029 const dirname_utf16le = try std.unicode.utf8ToUtf16LeWithNull(self.channel.loop.allocator, dirname);
1030 defer self.channel.loop.allocator.free(dirname_utf16le);
1031
1032 // TODO https://github.com/ziglang/zig/issues/265
1033 const basename = std.fs.path.basename(file_path);
1034 const basename_utf16le_null = try std.unicode.utf8ToUtf16LeWithNull(self.channel.loop.allocator, basename);
1035 var basename_utf16le_null_consumed = false;
1036 defer if (!basename_utf16le_null_consumed) self.channel.loop.allocator.free(basename_utf16le_null);
1037 const basename_utf16le_no_null = basename_utf16le_null[0 .. basename_utf16le_null.len - 1];
1038
1039 const dir_handle = try windows.CreateFileW(
1040 dirname_utf16le.ptr,
1041 windows.FILE_LIST_DIRECTORY,
1042 windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE | windows.FILE_SHARE_WRITE,
1043 null,
1044 windows.OPEN_EXISTING,
1045 windows.FILE_FLAG_BACKUP_SEMANTICS | windows.FILE_FLAG_OVERLAPPED,
1046 null,
1047 );
1048 var dir_handle_consumed = false;
1049 defer if (!dir_handle_consumed) windows.CloseHandle(dir_handle);
1050
1051 const held = await (async self.os_data.table_lock.acquire() catch unreachable);
1052 defer held.release();
1053
1054 const gop = try self.os_data.dir_table.getOrPut(dirname);
1055 if (gop.found_existing) {
1056 const dir = gop.kv.value;
1057 const held_dir_lock = await (async dir.table_lock.acquire() catch unreachable);
1058 defer held_dir_lock.release();
1059
1060 const file_gop = try dir.file_table.getOrPut(basename_utf16le_no_null);
1061 if (file_gop.found_existing) {
1062 const prev_value = file_gop.kv.value;
1063 file_gop.kv.value = value_copy;
1064 return prev_value;
1065 } else {
1066 file_gop.kv.value = value_copy;
1067 basename_utf16le_null_consumed = true;
1068 return null;
1069 }
1070 } else {
1071 errdefer _ = self.os_data.dir_table.remove(dirname);
1072 const dir = try self.channel.loop.allocator.create(OsData.Dir);
1073 errdefer self.channel.loop.allocator.destroy(dir);
1074
1075 dir.* = OsData.Dir{
1076 .file_table = OsData.FileTable.init(self.channel.loop.allocator),
1077 .table_lock = event.Lock.init(self.channel.loop),
1078 .putter = undefined,
1079 };
1080 gop.kv.value = dir;
1081 assert((try dir.file_table.put(basename_utf16le_no_null, value_copy)) == null);
1082 basename_utf16le_null_consumed = true;
1083
1084 dir.putter = try async self.windowsDirReader(dir_handle, dir);
1085 dir_handle_consumed = true;
1086
1087 dirname_consumed = true;
1088
1089 return null;
1090 }
1091 }
1092
1093 async fn windowsDirReader(self: *Self, dir_handle: windows.HANDLE, dir: *OsData.Dir) void {
1094 self.ref();
1095 defer self.deref();
1096
1097 defer os.close(dir_handle);
1098
1099 var putter_node = std.atomic.Queue(anyframe).Node{
1100 .data = @frame(),
1101 .prev = null,
1102 .next = null,
1103 };
1104 self.os_data.all_putters.put(&putter_node);
1105 defer _ = self.os_data.all_putters.remove(&putter_node);
1106
1107 var resume_node = Loop.ResumeNode.Basic{
1108 .base = Loop.ResumeNode{
1109 .id = Loop.ResumeNode.Id.Basic,
1110 .handle = @frame(),
1111 .overlapped = windows.OVERLAPPED{
1112 .Internal = 0,
1113 .InternalHigh = 0,
1114 .Offset = 0,
1115 .OffsetHigh = 0,
1116 .hEvent = null,
1117 },
1118 },
1119 };
1120 var event_buf: [4096]u8 align(@alignOf(windows.FILE_NOTIFY_INFORMATION)) = undefined;
1121
1122 // TODO handle this error not in the channel but in the setup
1123 _ = windows.CreateIoCompletionPort(
1124 dir_handle,
1125 self.channel.loop.os_data.io_port,
1126 undefined,
1127 undefined,
1128 ) catch |err| {
1129 await (async self.channel.put(err) catch unreachable);
1130 return;
1131 };
1132
1133 while (true) {
1134 {
1135 // TODO only 1 beginOneEvent for the whole function
1136 self.channel.loop.beginOneEvent();
1137 errdefer self.channel.loop.finishOneEvent();
1138 errdefer {
1139 _ = windows.kernel32.CancelIoEx(dir_handle, &resume_node.base.overlapped);
1140 }
1141 suspend {
1142 _ = windows.kernel32.ReadDirectoryChangesW(
1143 dir_handle,
1144 &event_buf,
1145 @intCast(windows.DWORD, event_buf.len),
1146 windows.FALSE, // watch subtree
1147 windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME |
1148 windows.FILE_NOTIFY_CHANGE_ATTRIBUTES | windows.FILE_NOTIFY_CHANGE_SIZE |
1149 windows.FILE_NOTIFY_CHANGE_LAST_WRITE | windows.FILE_NOTIFY_CHANGE_LAST_ACCESS |
1150 windows.FILE_NOTIFY_CHANGE_CREATION | windows.FILE_NOTIFY_CHANGE_SECURITY,
1151 null, // number of bytes transferred (unused for async)
1152 &resume_node.base.overlapped,
1153 null, // completion routine - unused because we use IOCP
1154 );
1155 }
1156 }
1157 var bytes_transferred: windows.DWORD = undefined;
1158 if (windows.kernel32.GetOverlappedResult(dir_handle, &resume_node.base.overlapped, &bytes_transferred, windows.FALSE) == 0) {
1159 const err = switch (windows.kernel32.GetLastError()) {
1160 else => |err| windows.unexpectedError(err),
1161 };
1162 await (async self.channel.put(err) catch unreachable);
1163 } else {
1164 // can't use @bytesToSlice because of the special variable length name field
1165 var ptr = event_buf[0..].ptr;
1166 const end_ptr = ptr + bytes_transferred;
1167 var ev: *windows.FILE_NOTIFY_INFORMATION = undefined;
1168 while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) : (ptr += ev.NextEntryOffset) {
1169 ev = @ptrCast(*windows.FILE_NOTIFY_INFORMATION, ptr);
1170 const emit = switch (ev.Action) {
1171 windows.FILE_ACTION_REMOVED => WatchEventId.Delete,
1172 windows.FILE_ACTION_MODIFIED => WatchEventId.CloseWrite,
1173 else => null,
1174 };
1175 if (emit) |id| {
1176 const basename_utf16le = ([*]u16)(&ev.FileName)[0 .. ev.FileNameLength / 2];
1177 const user_value = blk: {
1178 const held = await (async dir.table_lock.acquire() catch unreachable);
1179 defer held.release();
1180
1181 if (dir.file_table.get(basename_utf16le)) |entry| {
1182 break :blk entry.value;
1183 } else {
1184 break :blk null;
1185 }
1186 };
1187 if (user_value) |v| {
1188 await (async self.channel.put(Event{
1189 .id = id,
1190 .data = v,
1191 }) catch unreachable);
1192 }
1193 }
1194 if (ev.NextEntryOffset == 0) break;
1195 }
1196 }
1197 }
1198 }
1199
1200 pub async fn removeFile(self: *Self, file_path: []const u8) ?V {
1201 @panic("TODO");
1202 }
1203
1204 async fn linuxEventPutter(inotify_fd: i32, channel: *event.Channel(Event.Error!Event), out_watch: **Self) void {
1205 const loop = channel.loop;
1206
1207 var watch = Self{
1208 .channel = channel,
1209 .os_data = OsData{
1210 .putter = @frame(),
1211 .inotify_fd = inotify_fd,
1212 .wd_table = OsData.WdTable.init(loop.allocator),
1213 .table_lock = event.Lock.init(loop),
1214 },
1215 };
1216 out_watch.* = &watch;
1217
1218 loop.beginOneEvent();
1219
1220 defer {
1221 watch.os_data.table_lock.deinit();
1222 var wd_it = watch.os_data.wd_table.iterator();
1223 while (wd_it.next()) |wd_entry| {
1224 var file_it = wd_entry.value.file_table.iterator();
1225 while (file_it.next()) |file_entry| {
1226 loop.allocator.free(file_entry.key);
1227 }
1228 loop.allocator.free(wd_entry.value.dirname);
1229 }
1230 loop.finishOneEvent();
1231 os.close(inotify_fd);
1232 channel.destroy();
1233 }
1234
1235 var event_buf: [4096]u8 align(@alignOf(os.linux.inotify_event)) = undefined;
1236
1237 while (true) {
1238 const rc = os.linux.read(inotify_fd, &event_buf, event_buf.len);
1239 const errno = os.linux.getErrno(rc);
1240 switch (errno) {
1241 0 => {
1242 // can't use @bytesToSlice because of the special variable length name field
1243 var ptr = event_buf[0..].ptr;
1244 const end_ptr = ptr + event_buf.len;
1245 var ev: *os.linux.inotify_event = undefined;
1246 while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) : (ptr += @sizeOf(os.linux.inotify_event) + ev.len) {
1247 ev = @ptrCast(*os.linux.inotify_event, ptr);
1248 if (ev.mask & os.linux.IN_CLOSE_WRITE == os.linux.IN_CLOSE_WRITE) {
1249 const basename_ptr = ptr + @sizeOf(os.linux.inotify_event);
1250 const basename_with_null = basename_ptr[0 .. std.mem.len(u8, basename_ptr) + 1];
1251 const user_value = blk: {
1252 const held = await (async watch.os_data.table_lock.acquire() catch unreachable);
1253 defer held.release();
1254
1255 const dir = &watch.os_data.wd_table.get(ev.wd).?.value;
1256 if (dir.file_table.get(basename_with_null)) |entry| {
1257 break :blk entry.value;
1258 } else {
1259 break :blk null;
1260 }
1261 };
1262 if (user_value) |v| {
1263 await (async channel.put(Event{
1264 .id = WatchEventId.CloseWrite,
1265 .data = v,
1266 }) catch unreachable);
1267 }
1268 }
1269 }
1270 },
1271 os.linux.EINTR => continue,
1272 os.linux.EINVAL => unreachable,
1273 os.linux.EFAULT => unreachable,
1274 os.linux.EAGAIN => {
1275 (await (async loop.linuxWaitFd(
1276 inotify_fd,
1277 os.linux.EPOLLET | os.linux.EPOLLIN,
1278 ) catch unreachable)) catch |err| {
1279 const transformed_err = switch (err) {
1280 error.FileDescriptorAlreadyPresentInSet => unreachable,
1281 error.OperationCausesCircularLoop => unreachable,
1282 error.FileDescriptorNotRegistered => unreachable,
1283 error.FileDescriptorIncompatibleWithEpoll => unreachable,
1284 error.Unexpected => unreachable,
1285 else => |e| e,
1286 };
1287 await (async channel.put(transformed_err) catch unreachable);
1288 };
1289 },
1290 else => unreachable,
1291 }
1292 }
1293 }
1294 };
1295}
12961296
12971297const test_tmp_dir = "std_event_fs_test";
12981298
12991299// TODO this test is disabled until the async function rewrite is finished.
1300//test "write a file, watch it, write it again" {
1301// return error.SkipZigTest;
1302// const allocator = std.heap.direct_allocator;
1303//
1304// // TODO move this into event loop too
1305// try os.makePath(allocator, test_tmp_dir);
1306// defer os.deleteTree(test_tmp_dir) catch {};
1307//
1308// var loop: Loop = undefined;
1309// try loop.initMultiThreaded(allocator);
1310// defer loop.deinit();
1311//
1312// var result: anyerror!void = error.ResultNeverWritten;
1300test "write a file, watch it, write it again" {
1301 return error.SkipZigTest;
1302 const allocator = std.heap.direct_allocator;
1303
1304 // TODO move this into event loop too
1305 try os.makePath(allocator, test_tmp_dir);
1306 defer os.deleteTree(test_tmp_dir) catch {};
1307
1308 var loop: Loop = undefined;
1309 try loop.initMultiThreaded(allocator);
1310 defer loop.deinit();
1311
1312 var result: anyerror!void = error.ResultNeverWritten;
13131313// const handle = try async<allocator> testFsWatchCantFail(&loop, &result);
13141314// defer cancel handle;
1315//
1316// loop.run();
1317// return result;
1318//}
1315
1316 loop.run();
1317 return result;
1318}
13191319
13201320fn testFsWatchCantFail(loop: *Loop, result: *(anyerror!void)) void {
13211321 result.* = testFsWatch(loop);