@@ -33,9 +33,12 @@ const Os = switch (builtin.os.tag) {
33
33
34
34
/// Keyed differently but indexes correspond 1:1 with `dir_table`.
35
35
handle_table : HandleTable ,
36
- poll_fds : [1 ]posix.pollfd ,
36
+ /// fanotify file descriptors are keyed by mount id since marks
37
+ /// are limited to a single filesystem.
38
+ poll_fds : std .AutoArrayHashMapUnmanaged (MountId , posix .pollfd ),
37
39
38
- const HandleTable = std .ArrayHashMapUnmanaged (FileHandle , ReactionSet , FileHandle .Adapter , false );
40
+ const MountId = i32 ;
41
+ const HandleTable = std .ArrayHashMapUnmanaged (FileHandle , struct { mount_id : MountId , reaction_set : ReactionSet }, FileHandle .Adapter , false );
39
42
40
43
const fan_mask : std.os.linux.fanotify.MarkMask = .{
41
44
.CLOSE_WRITE = true ,
@@ -92,53 +95,33 @@ const Os = switch (builtin.os.tag) {
92
95
};
93
96
94
97
fn init () ! Watch {
95
- const fan_fd = std .posix .fanotify_init (.{
96
- .CLASS = .NOTIF ,
97
- .CLOEXEC = true ,
98
- .NONBLOCK = true ,
99
- .REPORT_NAME = true ,
100
- .REPORT_DIR_FID = true ,
101
- .REPORT_FID = true ,
102
- .REPORT_TARGET_FID = true ,
103
- }, 0 ) catch | err | switch (err ) {
104
- error .UnsupportedFlags = > fatal ("fanotify_init failed due to old kernel; requires 5.17+" , .{}),
105
- else = > | e | return e ,
106
- };
107
98
return .{
108
99
.dir_table = .{},
109
100
.os = switch (builtin .os .tag ) {
110
101
.linux = > .{
111
102
.handle_table = .{},
112
- .poll_fds = .{
113
- .{
114
- .fd = fan_fd ,
115
- .events = std .posix .POLL .IN ,
116
- .revents = undefined ,
117
- },
118
- },
103
+ .poll_fds = .{},
119
104
},
120
105
else = > {},
121
106
},
122
107
.generation = 0 ,
123
108
};
124
109
}
125
110
126
- fn getDirHandle (gpa : Allocator , path : std.Build.Cache.Path ) ! FileHandle {
111
+ fn getDirHandle (gpa : Allocator , path : std.Build.Cache.Path , mount_id : * MountId ) ! FileHandle {
127
112
var file_handle_buffer : [@sizeOf (std .os .linux .file_handle ) + 128 ]u8 align (@alignOf (std .os .linux .file_handle )) = undefined ;
128
- var mount_id : i32 = undefined ;
129
113
var buf : [std .fs .max_path_bytes ]u8 = undefined ;
130
114
const adjusted_path = if (path .sub_path .len == 0 ) "./" else std .fmt .bufPrint (& buf , "{s}/" , .{
131
115
path .sub_path ,
132
116
}) catch return error .NameTooLong ;
133
117
const stack_ptr : * std.os.linux.file_handle = @ptrCast (& file_handle_buffer );
134
118
stack_ptr .handle_bytes = file_handle_buffer .len - @sizeOf (std .os .linux .file_handle );
135
- try posix .name_to_handle_at (path .root_dir .handle .fd , adjusted_path , stack_ptr , & mount_id , std .os .linux .AT .HANDLE_FID );
119
+ try posix .name_to_handle_at (path .root_dir .handle .fd , adjusted_path , stack_ptr , mount_id , std .os .linux .AT .HANDLE_FID );
136
120
const stack_lfh : FileHandle = .{ .handle = stack_ptr };
137
121
return stack_lfh .clone (gpa );
138
122
}
139
123
140
- fn markDirtySteps (w : * Watch , gpa : Allocator ) ! bool {
141
- const fan_fd = w .os .getFanFd ();
124
+ fn markDirtySteps (w : * Watch , gpa : Allocator , fan_fd : posix.fd_t ) ! bool {
142
125
const fanotify = std .os .linux .fanotify ;
143
126
const M = fanotify .event_metadata ;
144
127
var events_buf : [256 + 4096 ]u8 = undefined ;
@@ -167,10 +150,10 @@ const Os = switch (builtin.os.tag) {
167
150
const file_name_z : [* :0 ]u8 = @ptrCast ((& file_handle .f_handle ).ptr + file_handle .handle_bytes );
168
151
const file_name = std .mem .span (file_name_z );
169
152
const lfh : FileHandle = .{ .handle = file_handle };
170
- if (w .os .handle_table .getPtr (lfh )) | reaction_set | {
171
- if (reaction_set .getPtr ("." )) | glob_set |
153
+ if (w .os .handle_table .getPtr (lfh )) | value | {
154
+ if (value . reaction_set .getPtr ("." )) | glob_set |
172
155
any_dirty = markStepSetDirty (gpa , glob_set , any_dirty );
173
- if (reaction_set .getPtr (file_name )) | step_set |
156
+ if (value . reaction_set .getPtr (file_name )) | step_set |
174
157
any_dirty = markStepSetDirty (gpa , step_set , any_dirty );
175
158
}
176
159
},
@@ -180,19 +163,38 @@ const Os = switch (builtin.os.tag) {
180
163
}
181
164
}
182
165
183
- fn getFanFd (os : * const @This ()) posix.fd_t {
184
- return os .poll_fds [0 ].fd ;
185
- }
186
-
187
166
fn update (w : * Watch , gpa : Allocator , steps : []const * Step ) ! void {
188
- const fan_fd = w .os .getFanFd ();
189
167
// Add missing marks and note persisted ones.
190
168
for (steps ) | step | {
191
169
for (step .inputs .table .keys (), step .inputs .table .values ()) | path , * files | {
192
170
const reaction_set = rs : {
193
171
const gop = try w .dir_table .getOrPut (gpa , path );
194
172
if (! gop .found_existing ) {
195
- const dir_handle = try Os .getDirHandle (gpa , path );
173
+ var mount_id : MountId = undefined ;
174
+ const dir_handle = try Os .getDirHandle (gpa , path , & mount_id );
175
+ const fan_fd = blk : {
176
+ const fd_gop = try w .os .poll_fds .getOrPut (gpa , mount_id );
177
+ if (! fd_gop .found_existing ) {
178
+ const fan_fd = std .posix .fanotify_init (.{
179
+ .CLASS = .NOTIF ,
180
+ .CLOEXEC = true ,
181
+ .NONBLOCK = true ,
182
+ .REPORT_NAME = true ,
183
+ .REPORT_DIR_FID = true ,
184
+ .REPORT_FID = true ,
185
+ .REPORT_TARGET_FID = true ,
186
+ }, 0 ) catch | err | switch (err ) {
187
+ error .UnsupportedFlags = > fatal ("fanotify_init failed due to old kernel; requires 5.17+" , .{}),
188
+ else = > | e | return e ,
189
+ };
190
+ fd_gop .value_ptr .* = .{
191
+ .fd = fan_fd ,
192
+ .events = std .posix .POLL .IN ,
193
+ .revents = undefined ,
194
+ };
195
+ }
196
+ break :blk fd_gop .value_ptr .* .fd ;
197
+ };
196
198
// `dir_handle` may already be present in the table in
197
199
// the case that we have multiple Cache.Path instances
198
200
// that compare inequal but ultimately point to the same
@@ -204,17 +206,17 @@ const Os = switch (builtin.os.tag) {
204
206
_ = w .dir_table .pop ();
205
207
} else {
206
208
assert (dh_gop .index == gop .index );
207
- dh_gop .value_ptr .* = .{};
209
+ dh_gop .value_ptr .* = .{ . mount_id = mount_id , . reaction_set = .{} };
208
210
posix .fanotify_mark (fan_fd , .{
209
211
.ADD = true ,
210
212
.ONLYDIR = true ,
211
213
}, fan_mask , path .root_dir .handle .fd , path .subPathOrDot ()) catch | err | {
212
214
fatal ("unable to watch {}: {s}" , .{ path , @errorName (err ) });
213
215
};
214
216
}
215
- break :rs dh_gop .value_ptr ;
217
+ break :rs & dh_gop .value_ptr . reaction_set ;
216
218
}
217
- break :rs & w .os .handle_table .values ()[gop .index ];
219
+ break :rs & w .os .handle_table .values ()[gop .index ]. reaction_set ;
218
220
};
219
221
for (files .items ) | basename | {
220
222
const gop = try reaction_set .getOrPut (gpa , basename );
@@ -229,7 +231,7 @@ const Os = switch (builtin.os.tag) {
229
231
var i : usize = 0 ;
230
232
while (i < w .os .handle_table .entries .len ) {
231
233
{
232
- const reaction_set = & w .os .handle_table .values ()[i ];
234
+ const reaction_set = & w .os .handle_table .values ()[i ]. reaction_set ;
233
235
var step_set_i : usize = 0 ;
234
236
while (step_set_i < reaction_set .entries .len ) {
235
237
const step_set = & reaction_set .values ()[step_set_i ];
@@ -256,6 +258,8 @@ const Os = switch (builtin.os.tag) {
256
258
257
259
const path = w .dir_table .keys ()[i ];
258
260
261
+ const mount_id = w .os .handle_table .values ()[i ].mount_id ;
262
+ const fan_fd = w .os .poll_fds .getEntry (mount_id ).? .value_ptr .fd ;
259
263
posix .fanotify_mark (fan_fd , .{
260
264
.REMOVE = true ,
261
265
.ONLYDIR = true ,
@@ -272,13 +276,14 @@ const Os = switch (builtin.os.tag) {
272
276
}
273
277
274
278
fn wait (w : * Watch , gpa : Allocator , timeout : Timeout ) ! WaitResult {
275
- const events_len = try std .posix .poll (& w .os .poll_fds , timeout .to_i32_ms ());
276
- return if (events_len == 0 )
277
- .timeout
278
- else if (try Os .markDirtySteps (w , gpa ))
279
- .dirty
280
- else
281
- .clean ;
279
+ const events_len = try std .posix .poll (w .os .poll_fds .values (), timeout .to_i32_ms ());
280
+ if (events_len == 0 )
281
+ return .timeout ;
282
+ for (w .os .poll_fds .values ()) | poll_fd | {
283
+ if (poll_fd .revents & std .posix .POLL .IN == std .posix .POLL .IN and try Os .markDirtySteps (w , gpa , poll_fd .fd ))
284
+ return .dirty ;
285
+ }
286
+ return .clean ;
282
287
}
283
288
},
284
289
.windows = > struct {
@@ -838,7 +843,11 @@ pub const Match = struct {
838
843
};
839
844
840
845
fn markAllFilesDirty (w : * Watch , gpa : Allocator ) void {
841
- for (w .os .handle_table .values ()) | reaction_set | {
846
+ for (w .os .handle_table .values ()) | value | {
847
+ const reaction_set = switch (builtin .os .tag ) {
848
+ .linux = > value .reaction_set ,
849
+ else = > value ,
850
+ };
842
851
for (reaction_set .values ()) | step_set | {
843
852
for (step_set .keys ()) | step | {
844
853
step .recursiveReset (gpa );
0 commit comments