forked from oven-sh/bun
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfd.zig
403 lines (362 loc) · 15.8 KB
/
fd.zig
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
const std = @import("std");
const posix = std.posix;
const bun = @import("root").bun;
const env = bun.Environment;
const JSC = bun.JSC;
const JSValue = JSC.JSValue;
const libuv = bun.windows.libuv;
const allow_assert = env.allow_assert;
const log = bun.sys.syslog;
fn handleToNumber(handle: FDImpl.System) FDImpl.SystemAsInt {
if (env.os == .windows) {
// intCast fails if 'fd > 2^62'
// possible with handleToNumber(GetCurrentProcess());
return @intCast(@intFromPtr(handle));
} else {
return handle;
}
}
fn numberToHandle(handle: FDImpl.SystemAsInt) FDImpl.System {
if (env.os == .windows) {
if (!@inComptime()) {
bun.assert(handle != FDImpl.invalid_value);
}
return @ptrFromInt(handle);
} else {
return handle;
}
}
pub fn uv_get_osfhandle(in: c_int) libuv.uv_os_fd_t {
const out = libuv.uv_get_osfhandle(in);
return out;
}
pub fn uv_open_osfhandle(in: libuv.uv_os_fd_t) error{SystemFdQuotaExceeded}!c_int {
const out = libuv.uv_open_osfhandle(in);
bun.assert(out >= -1);
if (out == -1) return error.SystemFdQuotaExceeded;
return out;
}
/// Abstraction over file descriptors. This struct does nothing on non-windows operating systems.
///
/// bun.FileDescriptor is the bitcast of this struct, which is essentially a tagged pointer.
///
/// You can acquire one with FDImpl.decode(fd), and convert back to it with FDImpl.encode(fd).
///
/// On Windows builds we have two kinds of file descriptors:
/// - system: A "std.os.windows.HANDLE" that windows APIs can interact with.
/// In this case it is actually just an "*anyopaque" that points to some windows internals.
/// - uv: A libuv file descriptor that looks like a linux file descriptor.
/// (technically a c runtime file descriptor, libuv might do extra stuff though)
///
/// When converting UVFDs into Windows FDs, they are still said to be owned by libuv,
/// and they say to NOT close the handle.
pub const FDImpl = packed struct {
value: Value,
kind: Kind,
const invalid_value = std.math.maxInt(SystemAsInt);
pub const invalid = FDImpl{
.kind = .system,
.value = .{ .as_system = invalid_value },
};
pub const System = posix.fd_t;
pub const SystemAsInt = switch (env.os) {
.windows => u63,
else => System,
};
pub const UV = switch (env.os) {
.windows => bun.windows.libuv.uv_file,
else => System,
};
pub const Value = if (env.os == .windows)
packed union { as_system: SystemAsInt, as_uv: UV }
else
packed union { as_system: SystemAsInt };
pub const Kind = if (env.os == .windows)
enum(u1) { system = 0, uv = 1 }
else
enum(u0) { system };
comptime {
bun.assert(@sizeOf(FDImpl) == @sizeOf(System));
if (env.os == .windows) {
// we want the conversion from FD to fd_t to be a integer truncate
bun.assert(@as(FDImpl, @bitCast(@as(u64, 512))).value.as_system == 512);
}
}
pub fn fromSystemWithoutAssertion(system_fd: System) FDImpl {
return FDImpl{
.kind = .system,
.value = .{ .as_system = handleToNumber(system_fd) },
};
}
pub fn fromSystem(system_fd: System) FDImpl {
if (env.os == .windows) {
// the current process fd is max usize
// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getcurrentprocess
bun.assert(@intFromPtr(system_fd) <= std.math.maxInt(SystemAsInt));
}
return fromSystemWithoutAssertion(system_fd);
}
pub fn fromUV(uv_fd: UV) FDImpl {
return switch (env.os) {
else => FDImpl{
.kind = .system,
.value = .{ .as_system = uv_fd },
},
.windows => FDImpl{
.kind = .uv,
.value = .{ .as_uv = uv_fd },
},
};
}
pub fn isValid(this: FDImpl) bool {
return switch (env.os) {
// the 'zero' value on posix is debatable. it can be standard in.
// TODO(@paperdave): steamroll away every use of bun.FileDescriptor.zero
else => this.value.as_system != invalid_value,
.windows => switch (this.kind) {
// zero is not allowed in addition to the invalid value (zero would be a null ptr)
.system => this.value.as_system != invalid_value and this.value.as_system != 0,
// the libuv tag is always fine
.uv => true,
},
};
}
/// When calling this function, you may not be able to close the returned fd.
/// To close the fd, you have to call `.close()` on the FD.
pub fn system(this: FDImpl) System {
return switch (env.os == .windows) {
false => numberToHandle(this.value.as_system),
true => switch (this.kind) {
.system => numberToHandle(this.value.as_system),
.uv => uv_get_osfhandle(this.value.as_uv),
},
};
}
/// Convert to bun.FileDescriptor
pub fn encode(this: FDImpl) bun.FileDescriptor {
// https://github.com/ziglang/zig/issues/18462
return @enumFromInt(@as(bun.FileDescriptorInt, @bitCast(this)));
}
pub fn decode(fd: bun.FileDescriptor) FDImpl {
return @bitCast(@intFromEnum(fd));
}
/// When calling this function, you should consider the FD struct to now be invalid.
/// Calling `.close()` on the FD at that point may not work.
pub fn uv(this: FDImpl) UV {
return switch (env.os) {
else => numberToHandle(this.value.as_system),
.windows => switch (this.kind) {
.system => std.debug.panic(
\\Cast {} -> FDImpl.UV makes closing impossible!
\\
\\The supplier of this FileDescriptor should call 'bun.toLibUVOwnedFD'
\\or 'FDImpl.makeLibUVOwned', probably where open() was called.
,
.{this},
),
.uv => this.value.as_uv,
},
};
}
/// This function will prevent stdout and stderr from being closed.
pub fn close(this: FDImpl) ?bun.sys.Error {
if (env.os != .windows or this.kind == .uv) {
// This branch executes always on linux (uv() is no-op),
// or on Windows when given a UV file descriptor.
const fd = this.uv();
if (fd == 1 or fd == 2) {
log("close({}) SKIPPED", .{fd});
return null;
}
}
return this.closeAllowingStdoutAndStderr();
}
/// Assumes given a valid file descriptor
/// If error, the handle has not been closed
pub fn makeLibUVOwned(this: FDImpl) !FDImpl {
this.assertValid();
return switch (env.os) {
else => this,
.windows => switch (this.kind) {
.system => fd: {
break :fd FDImpl.fromUV(try uv_open_osfhandle(numberToHandle(this.value.as_system)));
},
.uv => this,
},
};
}
pub fn closeAllowingStdoutAndStderr(this: FDImpl) ?bun.sys.Error {
if (allow_assert) {
bun.assert(this.value.as_system != invalid_value); // probably a UAF
}
// Format the file descriptor for logging BEFORE closing it.
// Otherwise the file descriptor is always invalid after closing it.
var buf: if (env.isDebug) [1050]u8 else void = undefined;
const this_fmt = if (env.isDebug) std.fmt.bufPrint(&buf, "{}", .{this}) catch unreachable;
const result: ?bun.sys.Error = switch (env.os) {
.linux => result: {
const fd = this.encode();
bun.assert(fd != bun.invalid_fd);
bun.assert(fd.cast() >= 0);
break :result switch (bun.C.getErrno(bun.sys.system.close(fd.cast()))) {
.BADF => bun.sys.Error{ .errno = @intFromEnum(posix.E.BADF), .syscall = .close, .fd = fd },
else => null,
};
},
.mac => result: {
const fd = this.encode();
bun.assert(fd != bun.invalid_fd);
bun.assert(fd.cast() >= 0);
break :result switch (bun.C.getErrno(bun.sys.system.@"close$NOCANCEL"(fd.cast()))) {
.BADF => bun.sys.Error{ .errno = @intFromEnum(posix.E.BADF), .syscall = .close, .fd = fd },
else => null,
};
},
.windows => result: {
switch (this.kind) {
.uv => {
var req: libuv.fs_t = libuv.fs_t.uninitialized;
defer req.deinit();
const rc = libuv.uv_fs_close(libuv.Loop.get(), &req, this.value.as_uv, null);
break :result if (rc.errno()) |errno|
.{ .errno = errno, .syscall = .close, .fd = this.encode() }
else
null;
},
.system => {
bun.assert(this.value.as_system != 0);
const handle: System = @ptrFromInt(@as(u64, this.value.as_system));
break :result switch (bun.windows.NtClose(handle)) {
.SUCCESS => null,
else => |rc| bun.sys.Error{
.errno = if (bun.windows.Win32Error.fromNTStatus(rc).toSystemErrno()) |errno| @intFromEnum(errno) else 1,
.syscall = .CloseHandle,
.fd = this.encode(),
},
};
},
}
},
else => @compileError("FD.close() not implemented for this platform"),
};
if (env.isDebug) {
if (result) |err| {
if (err.errno == @intFromEnum(posix.E.BADF)) {
// TODO(@paperdave): Zig Compiler Bug, if you remove `this` from the log. An error is correctly printed, but with the wrong reference trace
bun.Output.debugWarn("close({s}) = EBADF. This is an indication of a file descriptor UAF", .{this_fmt});
} else {
log("close({s}) = {}", .{ this_fmt, err });
}
} else {
log("close({s})", .{this_fmt});
}
}
return result;
}
/// This "fails" if not given an int32, returning null in that case
pub fn fromJS(value: JSValue) ?FDImpl {
if (!value.isInt32()) return null;
const fd = value.asInt32();
if (comptime env.isWindows) {
return switch (bun.FDTag.get(fd)) {
.stdin => FDImpl.decode(bun.STDIN_FD),
.stdout => FDImpl.decode(bun.STDOUT_FD),
.stderr => FDImpl.decode(bun.STDERR_FD),
else => FDImpl.fromUV(fd),
};
}
return FDImpl.fromUV(fd);
}
// If a non-number is given, returns null.
// If the given number is not an fd (negative), an error is thrown and error.JSException is returned.
pub fn fromJSValidated(value: JSValue, global: *JSC.JSGlobalObject, exception_ref: JSC.C.ExceptionRef) !?FDImpl {
if (!value.isInt32()) return null;
const fd = value.asInt32();
if (!JSC.Node.Valid.fileDescriptor(fd, global, exception_ref)) {
return error.JSException;
}
if (comptime env.isWindows) {
return switch (bun.FDTag.get(fd)) {
.stdin => FDImpl.decode(bun.STDIN_FD),
.stdout => FDImpl.decode(bun.STDOUT_FD),
.stderr => FDImpl.decode(bun.STDERR_FD),
else => FDImpl.fromUV(fd),
};
}
return FDImpl.fromUV(fd);
}
/// After calling, the input file descriptor is no longer valid and must not be used.
/// If an error is thrown, the file descriptor is cleaned up for you.
pub fn toJS(value: FDImpl, global: *JSC.JSGlobalObject) JSValue {
const fd = value.makeLibUVOwned() catch {
_ = value.close();
global.throwValue((JSC.SystemError{
.message = bun.String.static("EMFILE, too many open files"),
.code = bun.String.static("EMFILE"),
}).toErrorInstance(global));
return .zero;
};
return JSValue.jsNumberFromInt32(fd.uv());
}
pub fn format(this: FDImpl, comptime fmt: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
if (!this.isValid()) {
try writer.writeAll("[invalid_fd]");
return;
}
if (fmt.len != 0) {
// The reason for this error is because formatting FD as an integer on windows is
// ambiguous and almost certainly a mistake. You probably meant to format fd.cast().
//
// Remember this formatter will
// - on posix, print the numebr
// - on windows, print if it is a handle or a libuv file descriptor
// - in debug on all platforms, print the path of the file descriptor
//
// Not having this error caused a linux+debug only crash in bun.sys.getFdPath because
// we forgot to change the thing being printed to "fd.cast()" when FDImpl was introduced.
@compileError("invalid format string for FDImpl.format. must be empty like '{}'");
}
switch (env.os) {
else => {
const fd = this.system();
try writer.print("{d}", .{fd});
if (env.isDebug and fd >= 3) print_with_path: {
var path_buf: bun.PathBuffer = undefined;
const path = std.os.getFdPath(fd, &path_buf) catch break :print_with_path;
try writer.print("[{s}]", .{path});
}
},
.windows => {
switch (this.kind) {
.system => {
if (env.isDebug) {
const peb = std.os.windows.peb();
const handle = this.system();
if (handle == peb.ProcessParameters.hStdInput) {
return try writer.print("{d}[stdin handle]", .{this.value.as_system});
} else if (handle == peb.ProcessParameters.hStdOutput) {
return try writer.print("{d}[stdout handle]", .{this.value.as_system});
} else if (handle == peb.ProcessParameters.hStdError) {
return try writer.print("{d}[stderr handle]", .{this.value.as_system});
} else if (handle == peb.ProcessParameters.CurrentDirectory.Handle) {
return try writer.print("{d}[cwd handle]", .{this.value.as_system});
} else print_with_path: {
var fd_path: bun.WPathBuffer = undefined;
const path = std.os.windows.GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, &fd_path) catch break :print_with_path;
return try writer.print("{d}[{}]", .{
this.value.as_system,
bun.fmt.utf16(path),
});
}
}
try writer.print("{d}[handle]", .{this.value.as_system});
},
.uv => try writer.print("{d}[libuv]", .{this.value.as_uv}),
}
},
}
}
pub fn assertValid(this: FDImpl) void {
bun.assert(this.isValid());
}
};