| ... | ... | @@ -1,8 +1,10 @@ |
| 1 | const builtin = @import("builtin"); |
| 1 | 2 | const std = @import("../index.zig"); |
| 2 | 3 | const event = std.event; |
| 3 | 4 | const assert = std.debug.assert; |
| 4 | 5 | const os = std.os; |
| 5 | 6 | const mem = std.mem; |
| 7 | const posix = os.posix; |
| 6 | 8 | |
| 7 | 9 | pub const RequestNode = std.atomic.Queue(Request).Node; |
| 8 | 10 | |
| ... | ... | @@ -19,8 +21,7 @@ pub const Request = struct { |
| 19 | 21 | pub const Msg = union(enum) { |
| 20 | 22 | PWriteV: PWriteV, |
| 21 | 23 | PReadV: PReadV, |
| 22 | | OpenRead: OpenRead, |
| 23 | | OpenRW: OpenRW, |
| 24 | Open: Open, |
| 24 | 25 | Close: Close, |
| 25 | 26 | WriteFile: WriteFile, |
| 26 | 27 | End, // special - means the fs thread should exit |
| ... | ... | @@ -43,19 +44,12 @@ pub const Request = struct { |
| 43 | 44 | pub const Error = os.File.ReadError; |
| 44 | 45 | }; |
| 45 | 46 | |
| 46 | | pub const OpenRead = struct { |
| 47 | pub const Open = struct { |
| 47 | 48 | /// must be null terminated. TODO https://github.com/ziglang/zig/issues/265 |
| 48 | 49 | path: []const u8, |
| 49 | | result: Error!os.FileHandle, |
| 50 | | |
| 51 | | pub const Error = os.File.OpenError; |
| 52 | | }; |
| 53 | | |
| 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, |
| 50 | flags: u32, |
| 58 | 51 | mode: os.File.Mode, |
| 52 | result: Error!os.FileHandle, |
| 59 | 53 | |
| 60 | 54 | pub const Error = os.File.OpenError; |
| 61 | 55 | }; |
| ... | ... | @@ -77,7 +71,7 @@ pub const Request = struct { |
| 77 | 71 | }; |
| 78 | 72 | |
| 79 | 73 | /// data - just the inner references - must live until pwritev promise completes. |
| 80 | | pub async fn pwritev(loop: *event.Loop, fd: os.FileHandle, offset: usize, data: []const []const u8) !void { |
| 74 | pub async fn pwritev(loop: *event.Loop, fd: os.FileHandle, data: []const []const u8, offset: usize) !void { |
| 81 | 75 | // workaround for https://github.com/ziglang/zig/issues/1194 |
| 82 | 76 | suspend { |
| 83 | 77 | resume @handle(); |
| ... | ... | @@ -94,8 +88,8 @@ pub async fn pwritev(loop: *event.Loop, fd: os.FileHandle, offset: usize, data: |
| 94 | 88 | } |
| 95 | 89 | |
| 96 | 90 | var req_node = RequestNode{ |
| 97 | | .prev = undefined, |
| 98 | | .next = undefined, |
| 91 | .prev = null, |
| 92 | .next = null, |
| 99 | 93 | .data = Request{ |
| 100 | 94 | .msg = Request.Msg{ |
| 101 | 95 | .PWriteV = Request.Msg.PWriteV{ |
| ... | ... | @@ -107,14 +101,16 @@ pub async fn pwritev(loop: *event.Loop, fd: os.FileHandle, offset: usize, data: |
| 107 | 101 | }, |
| 108 | 102 | .finish = Request.Finish{ |
| 109 | 103 | .TickNode = event.Loop.NextTickNode{ |
| 110 | | .prev = undefined, |
| 111 | | .next = undefined, |
| 104 | .prev = null, |
| 105 | .next = null, |
| 112 | 106 | .data = @handle(), |
| 113 | 107 | }, |
| 114 | 108 | }, |
| 115 | 109 | }, |
| 116 | 110 | }; |
| 117 | 111 | |
| 112 | errdefer loop.posixFsCancel(&req_node); |
| 113 | |
| 118 | 114 | suspend { |
| 119 | 115 | loop.posixFsRequest(&req_node); |
| 120 | 116 | } |
| ... | ... | @@ -123,7 +119,7 @@ pub async fn pwritev(loop: *event.Loop, fd: os.FileHandle, offset: usize, data: |
| 123 | 119 | } |
| 124 | 120 | |
| 125 | 121 | /// data - just the inner references - must live until pwritev promise completes. |
| 126 | | pub async fn preadv(loop: *event.Loop, fd: os.FileHandle, offset: usize, data: []const []u8) !usize { |
| 122 | pub async fn preadv(loop: *event.Loop, fd: os.FileHandle, data: []const []u8, offset: usize) !usize { |
| 127 | 123 | //const data_dupe = try mem.dupe(loop.allocator, []const u8, data); |
| 128 | 124 | //defer loop.allocator.free(data_dupe); |
| 129 | 125 | |
| ... | ... | @@ -143,8 +139,8 @@ pub async fn preadv(loop: *event.Loop, fd: os.FileHandle, offset: usize, data: [ |
| 143 | 139 | } |
| 144 | 140 | |
| 145 | 141 | var req_node = RequestNode{ |
| 146 | | .prev = undefined, |
| 147 | | .next = undefined, |
| 142 | .prev = null, |
| 143 | .next = null, |
| 148 | 144 | .data = Request{ |
| 149 | 145 | .msg = Request.Msg{ |
| 150 | 146 | .PReadV = Request.Msg.PReadV{ |
| ... | ... | @@ -156,14 +152,16 @@ pub async fn preadv(loop: *event.Loop, fd: os.FileHandle, offset: usize, data: [ |
| 156 | 152 | }, |
| 157 | 153 | .finish = Request.Finish{ |
| 158 | 154 | .TickNode = event.Loop.NextTickNode{ |
| 159 | | .prev = undefined, |
| 160 | | .next = undefined, |
| 155 | .prev = null, |
| 156 | .next = null, |
| 161 | 157 | .data = @handle(), |
| 162 | 158 | }, |
| 163 | 159 | }, |
| 164 | 160 | }, |
| 165 | 161 | }; |
| 166 | 162 | |
| 163 | errdefer loop.posixFsCancel(&req_node); |
| 164 | |
| 167 | 165 | suspend { |
| 168 | 166 | loop.posixFsRequest(&req_node); |
| 169 | 167 | } |
| ... | ... | @@ -171,7 +169,9 @@ pub async fn preadv(loop: *event.Loop, fd: os.FileHandle, offset: usize, data: [ |
| 171 | 169 | return req_node.data.msg.PReadV.result; |
| 172 | 170 | } |
| 173 | 171 | |
| 174 | | pub async fn openRead(loop: *event.Loop, path: []const u8) os.File.OpenError!os.FileHandle { |
| 172 | pub async fn open( |
| 173 | loop: *event.Loop, path: []const u8, flags: u32, mode: os.File.Mode, |
| 174 | ) os.File.OpenError!os.FileHandle { |
| 175 | 175 | // workaround for https://github.com/ziglang/zig/issues/1194 |
| 176 | 176 | suspend { |
| 177 | 177 | resume @handle(); |
| ... | ... | @@ -181,30 +181,39 @@ pub async fn openRead(loop: *event.Loop, path: []const u8) os.File.OpenError!os. |
| 181 | 181 | defer loop.allocator.free(path_with_null); |
| 182 | 182 | |
| 183 | 183 | var req_node = RequestNode{ |
| 184 | | .prev = undefined, |
| 185 | | .next = undefined, |
| 184 | .prev = null, |
| 185 | .next = null, |
| 186 | 186 | .data = Request{ |
| 187 | 187 | .msg = Request.Msg{ |
| 188 | | .OpenRead = Request.Msg.OpenRead{ |
| 188 | .Open = Request.Msg.Open{ |
| 189 | 189 | .path = path_with_null[0..path.len], |
| 190 | .flags = flags, |
| 191 | .mode = mode, |
| 190 | 192 | .result = undefined, |
| 191 | 193 | }, |
| 192 | 194 | }, |
| 193 | 195 | .finish = Request.Finish{ |
| 194 | 196 | .TickNode = event.Loop.NextTickNode{ |
| 195 | | .prev = undefined, |
| 196 | | .next = undefined, |
| 197 | .prev = null, |
| 198 | .next = null, |
| 197 | 199 | .data = @handle(), |
| 198 | 200 | }, |
| 199 | 201 | }, |
| 200 | 202 | }, |
| 201 | 203 | }; |
| 202 | 204 | |
| 205 | errdefer loop.posixFsCancel(&req_node); |
| 206 | |
| 203 | 207 | suspend { |
| 204 | 208 | loop.posixFsRequest(&req_node); |
| 205 | 209 | } |
| 206 | 210 | |
| 207 | | return req_node.data.msg.OpenRead.result; |
| 211 | return req_node.data.msg.Open.result; |
| 212 | } |
| 213 | |
| 214 | pub async fn openRead(loop: *event.Loop, path: []const u8) os.File.OpenError!os.FileHandle { |
| 215 | const flags = posix.O_LARGEFILE | posix.O_RDONLY | posix.O_CLOEXEC; |
| 216 | return await (async open(loop, path, flags, 0) catch unreachable); |
| 208 | 217 | } |
| 209 | 218 | |
| 210 | 219 | /// Creates if does not exist. Does not truncate. |
| ... | ... | @@ -213,59 +222,29 @@ pub async fn openReadWrite( |
| 213 | 222 | path: []const u8, |
| 214 | 223 | mode: os.File.Mode, |
| 215 | 224 | ) os.File.OpenError!os.FileHandle { |
| 216 | | // workaround for https://github.com/ziglang/zig/issues/1194 |
| 217 | | suspend { |
| 218 | | resume @handle(); |
| 219 | | } |
| 220 | | |
| 221 | | const path_with_null = try std.cstr.addNullByte(loop.allocator, path); |
| 222 | | defer loop.allocator.free(path_with_null); |
| 223 | | |
| 224 | | var req_node = RequestNode{ |
| 225 | | .prev = undefined, |
| 226 | | .next = undefined, |
| 227 | | .data = Request{ |
| 228 | | .msg = Request.Msg{ |
| 229 | | .OpenRW = Request.Msg.OpenRW{ |
| 230 | | .path = path_with_null[0..path.len], |
| 231 | | .mode = mode, |
| 232 | | .result = undefined, |
| 233 | | }, |
| 234 | | }, |
| 235 | | .finish = Request.Finish{ |
| 236 | | .TickNode = event.Loop.NextTickNode{ |
| 237 | | .prev = undefined, |
| 238 | | .next = undefined, |
| 239 | | .data = @handle(), |
| 240 | | }, |
| 241 | | }, |
| 242 | | }, |
| 243 | | }; |
| 244 | | |
| 245 | | suspend { |
| 246 | | loop.posixFsRequest(&req_node); |
| 247 | | } |
| 248 | | |
| 249 | | return req_node.data.msg.OpenRW.result; |
| 225 | const flags = posix.O_LARGEFILE | posix.O_RDWR | posix.O_CREAT | posix.O_CLOEXEC; |
| 226 | return await (async open(loop, path, flags, mode) catch unreachable); |
| 250 | 227 | } |
| 251 | 228 | |
| 252 | 229 | /// This abstraction helps to close file handles in defer expressions |
| 253 | 230 | /// without the possibility of failure and without the use of suspend points. |
| 254 | 231 | /// Start a `CloseOperation` before opening a file, so that you can defer |
| 255 | | /// `CloseOperation.deinit`. |
| 232 | /// `CloseOperation.finish`. |
| 233 | /// If you call `setHandle` then finishing will close the fd; otherwise finishing |
| 234 | /// will deallocate the `CloseOperation`. |
| 256 | 235 | pub const CloseOperation = struct { |
| 257 | 236 | loop: *event.Loop, |
| 258 | 237 | have_fd: bool, |
| 259 | 238 | close_req_node: RequestNode, |
| 260 | 239 | |
| 261 | | pub fn create(loop: *event.Loop) (error{OutOfMemory}!*CloseOperation) { |
| 240 | pub fn start(loop: *event.Loop) (error{OutOfMemory}!*CloseOperation) { |
| 262 | 241 | const self = try loop.allocator.createOne(CloseOperation); |
| 263 | 242 | self.* = CloseOperation{ |
| 264 | 243 | .loop = loop, |
| 265 | 244 | .have_fd = false, |
| 266 | 245 | .close_req_node = RequestNode{ |
| 267 | | .prev = undefined, |
| 268 | | .next = undefined, |
| 246 | .prev = null, |
| 247 | .next = null, |
| 269 | 248 | .data = Request{ |
| 270 | 249 | .msg = Request.Msg{ |
| 271 | 250 | .Close = Request.Msg.Close{ .fd = undefined }, |
| ... | ... | @@ -278,7 +257,7 @@ pub const CloseOperation = struct { |
| 278 | 257 | } |
| 279 | 258 | |
| 280 | 259 | /// Defer this after creating. |
| 281 | | pub fn deinit(self: *CloseOperation) void { |
| 260 | pub fn finish(self: *CloseOperation) void { |
| 282 | 261 | if (self.have_fd) { |
| 283 | 262 | self.loop.posixFsRequest(&self.close_req_node); |
| 284 | 263 | } else { |
| ... | ... | @@ -290,6 +269,16 @@ pub const CloseOperation = struct { |
| 290 | 269 | self.close_req_node.data.msg.Close.fd = handle; |
| 291 | 270 | self.have_fd = true; |
| 292 | 271 | } |
| 272 | |
| 273 | /// Undo a `setHandle`. |
| 274 | pub fn clearHandle(self: *CloseOperation) void { |
| 275 | self.have_fd = false; |
| 276 | } |
| 277 | |
| 278 | pub fn getHandle(self: *CloseOperation) os.FileHandle { |
| 279 | assert(self.have_fd); |
| 280 | return self.close_req_node.data.msg.Close.fd; |
| 281 | } |
| 293 | 282 | }; |
| 294 | 283 | |
| 295 | 284 | /// contents must remain alive until writeFile completes. |
| ... | ... | @@ -308,8 +297,8 @@ pub async fn writeFileMode(loop: *event.Loop, path: []const u8, contents: []cons |
| 308 | 297 | defer loop.allocator.free(path_with_null); |
| 309 | 298 | |
| 310 | 299 | var req_node = RequestNode{ |
| 311 | | .prev = undefined, |
| 312 | | .next = undefined, |
| 300 | .prev = null, |
| 301 | .next = null, |
| 313 | 302 | .data = Request{ |
| 314 | 303 | .msg = Request.Msg{ |
| 315 | 304 | .WriteFile = Request.Msg.WriteFile{ |
| ... | ... | @@ -321,14 +310,16 @@ pub async fn writeFileMode(loop: *event.Loop, path: []const u8, contents: []cons |
| 321 | 310 | }, |
| 322 | 311 | .finish = Request.Finish{ |
| 323 | 312 | .TickNode = event.Loop.NextTickNode{ |
| 324 | | .prev = undefined, |
| 325 | | .next = undefined, |
| 313 | .prev = null, |
| 314 | .next = null, |
| 326 | 315 | .data = @handle(), |
| 327 | 316 | }, |
| 328 | 317 | }, |
| 329 | 318 | }, |
| 330 | 319 | }; |
| 331 | 320 | |
| 321 | errdefer loop.posixFsCancel(&req_node); |
| 322 | |
| 332 | 323 | suspend { |
| 333 | 324 | loop.posixFsRequest(&req_node); |
| 334 | 325 | } |
| ... | ... | @@ -340,8 +331,8 @@ pub async fn writeFileMode(loop: *event.Loop, path: []const u8, contents: []cons |
| 340 | 331 | /// is closed. |
| 341 | 332 | /// Caller owns returned memory. |
| 342 | 333 | pub async fn readFile(loop: *event.Loop, file_path: []const u8, max_size: usize) ![]u8 { |
| 343 | | var close_op = try CloseOperation.create(loop); |
| 344 | | defer close_op.deinit(); |
| 334 | var close_op = try CloseOperation.start(loop); |
| 335 | defer close_op.finish(); |
| 345 | 336 | |
| 346 | 337 | const path_with_null = try std.cstr.addNullByte(loop.allocator, file_path); |
| 347 | 338 | defer loop.allocator.free(path_with_null); |
| ... | ... | @@ -356,7 +347,7 @@ pub async fn readFile(loop: *event.Loop, file_path: []const u8, max_size: usize) |
| 356 | 347 | try list.ensureCapacity(list.len + os.page_size); |
| 357 | 348 | const buf = list.items[list.len..]; |
| 358 | 349 | const buf_array = [][]u8{buf}; |
| 359 | | const amt = try await (async preadv(loop, fd, list.len, buf_array) catch unreachable); |
| 350 | const amt = try await (async preadv(loop, fd, buf_array, list.len) catch unreachable); |
| 360 | 351 | list.len += amt; |
| 361 | 352 | if (list.len > max_size) { |
| 362 | 353 | return error.FileTooBig; |
| ... | ... | @@ -370,19 +361,38 @@ pub async fn readFile(loop: *event.Loop, file_path: []const u8, max_size: usize) |
| 370 | 361 | pub fn Watch(comptime V: type) type { |
| 371 | 362 | return struct { |
| 372 | 363 | channel: *event.Channel(Event), |
| 373 | | putter: promise, |
| 374 | | wd_table: WdTable, |
| 375 | | table_lock: event.Lock, |
| 376 | | inotify_fd: i32, |
| 364 | os_data: OsData, |
| 365 | |
| 366 | const OsData = switch (builtin.os) { |
| 367 | builtin.Os.macosx => struct{ |
| 368 | file_table: FileTable, |
| 369 | table_lock: event.Lock, |
| 370 | |
| 371 | const FileTable = std.AutoHashMap([]const u8, *Put); |
| 372 | const Put = struct { |
| 373 | putter: promise, |
| 374 | value_ptr: *V, |
| 375 | }; |
| 376 | }, |
| 377 | builtin.Os.linux => struct { |
| 378 | putter: promise, |
| 379 | inotify_fd: i32, |
| 380 | wd_table: WdTable, |
| 381 | table_lock: event.Lock, |
| 382 | |
| 383 | const FileTable = std.AutoHashMap([]const u8, V); |
| 384 | }, |
| 385 | else => @compileError("Unsupported OS"), |
| 386 | }; |
| 377 | 387 | |
| 378 | 388 | const WdTable = std.AutoHashMap(i32, Dir); |
| 379 | | const FileTable = std.AutoHashMap([]const u8, V); |
| 389 | const FileToHandle = std.AutoHashMap([]const u8, promise); |
| 380 | 390 | |
| 381 | 391 | const Self = this; |
| 382 | 392 | |
| 383 | 393 | const Dir = struct { |
| 384 | 394 | dirname: []const u8, |
| 385 | | file_table: FileTable, |
| 395 | file_table: OsData.FileTable, |
| 386 | 396 | }; |
| 387 | 397 | |
| 388 | 398 | pub const Event = union(enum) { |
| ... | ... | @@ -392,26 +402,140 @@ pub fn Watch(comptime V: type) type { |
| 392 | 402 | pub const Error = error{ |
| 393 | 403 | UserResourceLimitReached, |
| 394 | 404 | SystemResources, |
| 405 | AccessDenied, |
| 395 | 406 | }; |
| 396 | 407 | }; |
| 397 | 408 | |
| 398 | 409 | pub fn create(loop: *event.Loop, event_buf_count: usize) !*Self { |
| 399 | | const inotify_fd = try os.linuxINotifyInit1(os.linux.IN_NONBLOCK | os.linux.IN_CLOEXEC); |
| 400 | | errdefer os.close(inotify_fd); |
| 401 | | |
| 402 | 410 | const channel = try event.Channel(Self.Event).create(loop, event_buf_count); |
| 403 | 411 | errdefer channel.destroy(); |
| 404 | 412 | |
| 405 | | var result: *Self = undefined; |
| 406 | | _ = try async<loop.allocator> eventPutter(inotify_fd, channel, &result); |
| 407 | | return result; |
| 413 | switch (builtin.os) { |
| 414 | builtin.Os.linux => { |
| 415 | const inotify_fd = try os.linuxINotifyInit1(os.linux.IN_NONBLOCK | os.linux.IN_CLOEXEC); |
| 416 | errdefer os.close(inotify_fd); |
| 417 | |
| 418 | var result: *Self = undefined; |
| 419 | _ = try async<loop.allocator> linuxEventPutter(inotify_fd, channel, &result); |
| 420 | return result; |
| 421 | }, |
| 422 | builtin.Os.macosx => { |
| 423 | const self = try loop.allocator.createOne(Self); |
| 424 | errdefer loop.allocator.destroy(self); |
| 425 | |
| 426 | self.* = Self{ |
| 427 | .channel = channel, |
| 428 | .os_data = OsData{ |
| 429 | .table_lock = event.Lock.init(loop), |
| 430 | .file_table = OsData.FileTable.init(loop.allocator), |
| 431 | }, |
| 432 | }; |
| 433 | return self; |
| 434 | }, |
| 435 | else => @compileError("Unsupported OS"), |
| 436 | } |
| 408 | 437 | } |
| 409 | 438 | |
| 410 | 439 | pub fn destroy(self: *Self) void { |
| 411 | | cancel self.putter; |
| 440 | switch (builtin.os) { |
| 441 | builtin.Os.macosx => { |
| 442 | self.os_data.table_lock.deinit(); |
| 443 | var it = self.os_data.file_table.iterator(); |
| 444 | while (it.next()) |entry| { |
| 445 | cancel entry.value.putter; |
| 446 | self.channel.loop.allocator.free(entry.key); |
| 447 | } |
| 448 | self.channel.destroy(); |
| 449 | }, |
| 450 | builtin.Os.linux => cancel self.os_data.putter, |
| 451 | else => @compileError("Unsupported OS"), |
| 452 | } |
| 412 | 453 | } |
| 413 | 454 | |
| 414 | 455 | pub async fn addFile(self: *Self, file_path: []const u8, value: V) !?V { |
| 456 | switch (builtin.os) { |
| 457 | builtin.Os.macosx => return await (async addFileMacosx(self, file_path, value) catch unreachable), |
| 458 | builtin.Os.linux => return await (async addFileLinux(self, file_path, value) catch unreachable), |
| 459 | else => @compileError("Unsupported OS"), |
| 460 | } |
| 461 | } |
| 462 | |
| 463 | async fn addFileMacosx(self: *Self, file_path: []const u8, value: V) !?V { |
| 464 | const resolved_path = try os.path.resolve(self.channel.loop.allocator, file_path); |
| 465 | var resolved_path_consumed = false; |
| 466 | defer if (!resolved_path_consumed) self.channel.loop.allocator.free(resolved_path); |
| 467 | |
| 468 | var close_op = try CloseOperation.start(self.channel.loop); |
| 469 | var close_op_consumed = false; |
| 470 | defer if (!close_op_consumed) close_op.finish(); |
| 471 | |
| 472 | const flags = posix.O_SYMLINK|posix.O_EVTONLY; |
| 473 | const mode = 0; |
| 474 | const fd = try await (async open(self.channel.loop, resolved_path, flags, mode) catch unreachable); |
| 475 | close_op.setHandle(fd); |
| 476 | |
| 477 | var put_data: *OsData.Put = undefined; |
| 478 | const putter = try async self.kqPutEvents(close_op, value, &put_data); |
| 479 | close_op_consumed = true; |
| 480 | errdefer cancel putter; |
| 481 | |
| 482 | const result = blk: { |
| 483 | const held = await (async self.os_data.table_lock.acquire() catch unreachable); |
| 484 | defer held.release(); |
| 485 | |
| 486 | const gop = try self.os_data.file_table.getOrPut(resolved_path); |
| 487 | if (gop.found_existing) { |
| 488 | const prev_value = gop.kv.value.value_ptr.*; |
| 489 | cancel gop.kv.value.putter; |
| 490 | gop.kv.value = put_data; |
| 491 | break :blk prev_value; |
| 492 | } else { |
| 493 | resolved_path_consumed = true; |
| 494 | gop.kv.value = put_data; |
| 495 | break :blk null; |
| 496 | } |
| 497 | }; |
| 498 | |
| 499 | return result; |
| 500 | } |
| 501 | |
| 502 | async fn kqPutEvents(self: *Self, close_op: *CloseOperation, value: V, out_put: **OsData.Put) void { |
| 503 | // TODO https://github.com/ziglang/zig/issues/1194 |
| 504 | suspend { |
| 505 | resume @handle(); |
| 506 | } |
| 507 | |
| 508 | var value_copy = value; |
| 509 | var put = OsData.Put{ |
| 510 | .putter = @handle(), |
| 511 | .value_ptr = &value_copy, |
| 512 | }; |
| 513 | out_put.* = &put; |
| 514 | self.channel.loop.beginOneEvent(); |
| 515 | |
| 516 | defer { |
| 517 | close_op.finish(); |
| 518 | self.channel.loop.finishOneEvent(); |
| 519 | } |
| 520 | |
| 521 | while (true) { |
| 522 | (await (async self.channel.loop.bsdWaitKev( |
| 523 | @intCast(usize, close_op.getHandle()), posix.EVFILT_VNODE, posix.NOTE_WRITE, |
| 524 | ) catch unreachable)) catch |err| switch (err) { |
| 525 | error.EventNotFound => unreachable, |
| 526 | error.ProcessNotFound => unreachable, |
| 527 | error.AccessDenied, error.SystemResources => { |
| 528 | // TODO https://github.com/ziglang/zig/issues/769 |
| 529 | const casted_err = @errSetCast(error{AccessDenied,SystemResources}, err); |
| 530 | await (async self.channel.put(Self.Event{ .Err = casted_err }) catch unreachable); |
| 531 | }, |
| 532 | }; |
| 533 | |
| 534 | await (async self.channel.put(Self.Event{ .CloseWrite = value_copy }) catch unreachable); |
| 535 | } |
| 536 | } |
| 537 | |
| 538 | async fn addFileLinux(self: *Self, file_path: []const u8, value: V) !?V { |
| 415 | 539 | const dirname = os.path.dirname(file_path) orelse "."; |
| 416 | 540 | const dirname_with_null = try std.cstr.addNullByte(self.channel.loop.allocator, dirname); |
| 417 | 541 | var dirname_with_null_consumed = false; |
| ... | ... | @@ -423,20 +547,20 @@ pub fn Watch(comptime V: type) type { |
| 423 | 547 | defer if (!basename_with_null_consumed) self.channel.loop.allocator.free(basename_with_null); |
| 424 | 548 | |
| 425 | 549 | const wd = try os.linuxINotifyAddWatchC( |
| 426 | | self.inotify_fd, |
| 550 | self.os_data.inotify_fd, |
| 427 | 551 | dirname_with_null.ptr, |
| 428 | 552 | os.linux.IN_CLOSE_WRITE | os.linux.IN_ONLYDIR | os.linux.IN_EXCL_UNLINK, |
| 429 | 553 | ); |
| 430 | 554 | // wd is either a newly created watch or an existing one. |
| 431 | 555 | |
| 432 | | const held = await (async self.table_lock.acquire() catch unreachable); |
| 556 | const held = await (async self.os_data.table_lock.acquire() catch unreachable); |
| 433 | 557 | defer held.release(); |
| 434 | 558 | |
| 435 | | const gop = try self.wd_table.getOrPut(wd); |
| 559 | const gop = try self.os_data.wd_table.getOrPut(wd); |
| 436 | 560 | if (!gop.found_existing) { |
| 437 | 561 | gop.kv.value = Dir{ |
| 438 | 562 | .dirname = dirname_with_null, |
| 439 | | .file_table = FileTable.init(self.channel.loop.allocator), |
| 563 | .file_table = OsData.FileTable.init(self.channel.loop.allocator), |
| 440 | 564 | }; |
| 441 | 565 | dirname_with_null_consumed = true; |
| 442 | 566 | } |
| ... | ... | @@ -458,7 +582,7 @@ pub fn Watch(comptime V: type) type { |
| 458 | 582 | @panic("TODO"); |
| 459 | 583 | } |
| 460 | 584 | |
| 461 | | async fn eventPutter(inotify_fd: i32, channel: *event.Channel(Event), out_watch: **Self) void { |
| 585 | async fn linuxEventPutter(inotify_fd: i32, channel: *event.Channel(Event), out_watch: **Self) void { |
| 462 | 586 | // TODO https://github.com/ziglang/zig/issues/1194 |
| 463 | 587 | suspend { |
| 464 | 588 | resume @handle(); |
| ... | ... | @@ -467,27 +591,27 @@ pub fn Watch(comptime V: type) type { |
| 467 | 591 | const loop = channel.loop; |
| 468 | 592 | |
| 469 | 593 | var watch = Self{ |
| 470 | | .putter = @handle(), |
| 471 | 594 | .channel = channel, |
| 472 | | .wd_table = WdTable.init(loop.allocator), |
| 473 | | .table_lock = event.Lock.init(loop), |
| 474 | | .inotify_fd = inotify_fd, |
| 595 | .os_data = OsData{ |
| 596 | .putter = @handle(), |
| 597 | .inotify_fd = inotify_fd, |
| 598 | .wd_table = WdTable.init(loop.allocator), |
| 599 | .table_lock = event.Lock.init(loop), |
| 600 | }, |
| 475 | 601 | }; |
| 476 | 602 | out_watch.* = &watch; |
| 477 | 603 | |
| 478 | 604 | loop.beginOneEvent(); |
| 479 | 605 | |
| 480 | 606 | defer { |
| 481 | | watch.table_lock.deinit(); |
| 482 | | { |
| 483 | | var wd_it = watch.wd_table.iterator(); |
| 484 | | while (wd_it.next()) |wd_entry| { |
| 485 | | var file_it = wd_entry.value.file_table.iterator(); |
| 486 | | while (file_it.next()) |file_entry| { |
| 487 | | loop.allocator.free(file_entry.key); |
| 488 | | } |
| 489 | | loop.allocator.free(wd_entry.value.dirname); |
| 607 | watch.os_data.table_lock.deinit(); |
| 608 | var wd_it = watch.os_data.wd_table.iterator(); |
| 609 | while (wd_it.next()) |wd_entry| { |
| 610 | var file_it = wd_entry.value.file_table.iterator(); |
| 611 | while (file_it.next()) |file_entry| { |
| 612 | loop.allocator.free(file_entry.key); |
| 490 | 613 | } |
| 614 | loop.allocator.free(wd_entry.value.dirname); |
| 491 | 615 | } |
| 492 | 616 | loop.finishOneEvent(); |
| 493 | 617 | os.close(inotify_fd); |
| ... | ... | @@ -511,10 +635,10 @@ pub fn Watch(comptime V: type) type { |
| 511 | 635 | const basename_ptr = ptr + @sizeOf(os.linux.inotify_event); |
| 512 | 636 | const basename_with_null = basename_ptr[0 .. std.cstr.len(basename_ptr) + 1]; |
| 513 | 637 | const user_value = blk: { |
| 514 | | const held = await (async watch.table_lock.acquire() catch unreachable); |
| 638 | const held = await (async watch.os_data.table_lock.acquire() catch unreachable); |
| 515 | 639 | defer held.release(); |
| 516 | 640 | |
| 517 | | const dir = &watch.wd_table.get(ev.wd).?.value; |
| 641 | const dir = &watch.os_data.wd_table.get(ev.wd).?.value; |
| 518 | 642 | if (dir.file_table.get(basename_with_null)) |entry| { |
| 519 | 643 | break :blk entry.value; |
| 520 | 644 | } else { |
| ... | ... | @@ -572,7 +696,7 @@ test "write a file, watch it, write it again" { |
| 572 | 696 | try loop.initMultiThreaded(allocator); |
| 573 | 697 | defer loop.deinit(); |
| 574 | 698 | |
| 575 | | var result: error!void = undefined; |
| 699 | var result: error!void = error.ResultNeverWritten; |
| 576 | 700 | const handle = try async<allocator> testFsWatchCantFail(&loop, &result); |
| 577 | 701 | defer cancel handle; |
| 578 | 702 | |
| ... | ... | @@ -615,7 +739,7 @@ async fn testFsWatch(loop: *event.Loop) !void { |
| 615 | 739 | { |
| 616 | 740 | defer os.close(fd); |
| 617 | 741 | |
| 618 | | try await try async pwritev(loop, fd, line2_offset, []const []const u8{"lorem ipsum"}); |
| 742 | try await try async pwritev(loop, fd, []const []const u8{"lorem ipsum"}, line2_offset); |
| 619 | 743 | } |
| 620 | 744 | |
| 621 | 745 | ev_consumed = true; |