mirror of
https://github.com/oven-sh/bun
synced 2026-02-16 13:51:47 +00:00
1026 lines
42 KiB
Zig
1026 lines
42 KiB
Zig
pub fn createNodeOsBinding(global: *jsc.JSGlobalObject) bun.JSError!jsc.JSValue {
|
|
return (try jsc.JSObject.create(.{
|
|
.cpus = gen.createCpusCallback(global),
|
|
.freemem = gen.createFreememCallback(global),
|
|
.getPriority = gen.createGetPriorityCallback(global),
|
|
.homedir = gen.createHomedirCallback(global),
|
|
.hostname = gen.createHostnameCallback(global),
|
|
.loadavg = gen.createLoadavgCallback(global),
|
|
.networkInterfaces = gen.createNetworkInterfacesCallback(global),
|
|
.release = gen.createReleaseCallback(global),
|
|
.totalmem = gen.createTotalmemCallback(global),
|
|
.uptime = gen.createUptimeCallback(global),
|
|
.userInfo = gen.createUserInfoCallback(global),
|
|
.version = gen.createVersionCallback(global),
|
|
.setPriority = gen.createSetPriorityCallback(global),
|
|
}, global)).toJS();
|
|
}
|
|
|
|
const CPUTimes = struct {
|
|
user: u64 = 0,
|
|
nice: u64 = 0,
|
|
sys: u64 = 0,
|
|
idle: u64 = 0,
|
|
irq: u64 = 0,
|
|
|
|
pub fn toValue(self: CPUTimes, globalThis: *jsc.JSGlobalObject) jsc.JSValue {
|
|
const fields = comptime std.meta.fieldNames(CPUTimes);
|
|
const ret = jsc.JSValue.createEmptyObject(globalThis, fields.len);
|
|
inline for (fields) |fieldName| {
|
|
ret.put(globalThis, jsc.ZigString.static(fieldName), jsc.JSValue.jsNumberFromUint64(@field(self, fieldName)));
|
|
}
|
|
return ret;
|
|
}
|
|
};
|
|
|
|
pub fn cpus(global: *jsc.JSGlobalObject) bun.JSError!jsc.JSValue {
|
|
const cpusImpl = switch (Environment.os) {
|
|
.linux => cpusImplLinux,
|
|
.mac => cpusImplDarwin,
|
|
.windows => cpusImplWindows,
|
|
else => @compileError("Unsupported OS"),
|
|
};
|
|
|
|
return cpusImpl(global) catch {
|
|
const err = jsc.SystemError{
|
|
.message = bun.String.static("Failed to get CPU information"),
|
|
.code = bun.String.static(@tagName(jsc.Node.ErrorCode.ERR_SYSTEM_ERROR)),
|
|
};
|
|
return global.throwValue(err.toErrorInstance(global));
|
|
};
|
|
}
|
|
|
|
fn cpusImplLinux(globalThis: *jsc.JSGlobalObject) !jsc.JSValue {
|
|
// Create the return array
|
|
const values = try jsc.JSValue.createEmptyArray(globalThis, 0);
|
|
var num_cpus: u32 = 0;
|
|
|
|
var stack_fallback = std.heap.stackFallback(1024 * 8, bun.default_allocator);
|
|
var file_buf = std.ArrayList(u8).init(stack_fallback.get());
|
|
defer file_buf.deinit();
|
|
|
|
// Read /proc/stat to get number of CPUs and times
|
|
{
|
|
const file = try std.fs.openFileAbsolute("/proc/stat", .{});
|
|
defer file.close();
|
|
|
|
const read = try bun.sys.File.from(file).readToEndWithArrayList(&file_buf, true).unwrap();
|
|
defer file_buf.clearRetainingCapacity();
|
|
const contents = file_buf.items[0..read];
|
|
|
|
var line_iter = std.mem.tokenizeScalar(u8, contents, '\n');
|
|
|
|
// Skip the first line (aggregate of all CPUs)
|
|
_ = line_iter.next();
|
|
|
|
// Read each CPU line
|
|
while (line_iter.next()) |line| {
|
|
// CPU lines are formatted as `cpu0 user nice sys idle iowait irq softirq`
|
|
var toks = std.mem.tokenizeAny(u8, line, " \t");
|
|
const cpu_name = toks.next();
|
|
if (cpu_name == null or !std.mem.startsWith(u8, cpu_name.?, "cpu")) break; // done with CPUs
|
|
|
|
//NOTE: libuv assumes this is fixed on Linux, not sure that's actually the case
|
|
const scale = 10;
|
|
|
|
var times = CPUTimes{};
|
|
times.user = scale * try std.fmt.parseInt(u64, toks.next() orelse return error.eol, 10);
|
|
times.nice = scale * try std.fmt.parseInt(u64, toks.next() orelse return error.eol, 10);
|
|
times.sys = scale * try std.fmt.parseInt(u64, toks.next() orelse return error.eol, 10);
|
|
times.idle = scale * try std.fmt.parseInt(u64, toks.next() orelse return error.eol, 10);
|
|
_ = try (toks.next() orelse error.eol); // skip iowait
|
|
times.irq = scale * try std.fmt.parseInt(u64, toks.next() orelse return error.eol, 10);
|
|
|
|
// Actually create the JS object representing the CPU
|
|
const cpu = jsc.JSValue.createEmptyObject(globalThis, 3);
|
|
cpu.put(globalThis, jsc.ZigString.static("times"), times.toValue(globalThis));
|
|
try values.putIndex(globalThis, num_cpus, cpu);
|
|
|
|
num_cpus += 1;
|
|
}
|
|
}
|
|
|
|
// Read /proc/cpuinfo to get model information (optional)
|
|
if (std.fs.openFileAbsolute("/proc/cpuinfo", .{})) |file| {
|
|
defer file.close();
|
|
|
|
const read = try bun.sys.File.from(file).readToEndWithArrayList(&file_buf, true).unwrap();
|
|
defer file_buf.clearRetainingCapacity();
|
|
const contents = file_buf.items[0..read];
|
|
|
|
var line_iter = std.mem.tokenizeScalar(u8, contents, '\n');
|
|
|
|
const key_processor = "processor\t: ";
|
|
const key_model_name = "model name\t: ";
|
|
|
|
var cpu_index: u32 = 0;
|
|
var has_model_name = true;
|
|
while (line_iter.next()) |line| {
|
|
if (strings.hasPrefixComptime(line, key_processor)) {
|
|
if (!has_model_name) {
|
|
const cpu = try values.getIndex(globalThis, cpu_index);
|
|
cpu.put(globalThis, jsc.ZigString.static("model"), jsc.ZigString.static("unknown").withEncoding().toJS(globalThis));
|
|
}
|
|
// If this line starts a new processor, parse the index from the line
|
|
const digits = std.mem.trim(u8, line[key_processor.len..], " \t\n");
|
|
cpu_index = try std.fmt.parseInt(u32, digits, 10);
|
|
if (cpu_index >= num_cpus) return error.too_may_cpus;
|
|
has_model_name = false;
|
|
} else if (strings.hasPrefixComptime(line, key_model_name)) {
|
|
// If this is the model name, extract it and store on the current cpu
|
|
const model_name = line[key_model_name.len..];
|
|
const cpu = try values.getIndex(globalThis, cpu_index);
|
|
cpu.put(globalThis, jsc.ZigString.static("model"), jsc.ZigString.init(model_name).withEncoding().toJS(globalThis));
|
|
has_model_name = true;
|
|
}
|
|
}
|
|
if (!has_model_name) {
|
|
const cpu = try values.getIndex(globalThis, cpu_index);
|
|
cpu.put(globalThis, jsc.ZigString.static("model"), jsc.ZigString.static("unknown").withEncoding().toJS(globalThis));
|
|
}
|
|
} else |_| {
|
|
// Initialize model name to "unknown"
|
|
var it = try values.arrayIterator(globalThis);
|
|
while (try it.next()) |cpu| {
|
|
cpu.put(globalThis, jsc.ZigString.static("model"), jsc.ZigString.static("unknown").withEncoding().toJS(globalThis));
|
|
}
|
|
}
|
|
|
|
// Read /sys/devices/system/cpu/cpu{}/cpufreq/scaling_cur_freq to get current frequency (optional)
|
|
for (0..num_cpus) |cpu_index| {
|
|
const cpu = try values.getIndex(globalThis, @truncate(cpu_index));
|
|
|
|
var path_buf: [128]u8 = undefined;
|
|
const path = try std.fmt.bufPrint(&path_buf, "/sys/devices/system/cpu/cpu{}/cpufreq/scaling_cur_freq", .{cpu_index});
|
|
if (std.fs.openFileAbsolute(path, .{})) |file| {
|
|
defer file.close();
|
|
|
|
const read = try bun.sys.File.from(file).readToEndWithArrayList(&file_buf, true).unwrap();
|
|
defer file_buf.clearRetainingCapacity();
|
|
const contents = file_buf.items[0..read];
|
|
|
|
const digits = std.mem.trim(u8, contents, " \n");
|
|
const speed = (std.fmt.parseInt(u64, digits, 10) catch 0) / 1000;
|
|
|
|
cpu.put(globalThis, jsc.ZigString.static("speed"), jsc.JSValue.jsNumber(speed));
|
|
} else |_| {
|
|
// Initialize CPU speed to 0
|
|
cpu.put(globalThis, jsc.ZigString.static("speed"), jsc.JSValue.jsNumber(0));
|
|
}
|
|
}
|
|
|
|
return values;
|
|
}
|
|
|
|
extern fn bun_sysconf__SC_CLK_TCK() isize;
|
|
fn cpusImplDarwin(globalThis: *jsc.JSGlobalObject) !jsc.JSValue {
|
|
// Fetch the CPU info structure
|
|
var num_cpus: c.natural_t = 0;
|
|
var info: [*]bun.c.processor_cpu_load_info = undefined;
|
|
var info_size: std.c.mach_msg_type_number_t = 0;
|
|
if (bun.c.host_processor_info(
|
|
std.c.mach_host_self(),
|
|
bun.c.PROCESSOR_CPU_LOAD_INFO,
|
|
&num_cpus,
|
|
@as(*bun.c.processor_info_array_t, @ptrCast(&info)),
|
|
&info_size,
|
|
) != 0) {
|
|
return error.no_processor_info;
|
|
}
|
|
defer _ = std.c.vm_deallocate(std.c.mach_task_self(), @intFromPtr(info), info_size);
|
|
|
|
// Ensure we got the amount of data we expected to guard against buffer overruns
|
|
if (info_size != bun.c.PROCESSOR_CPU_LOAD_INFO_COUNT * num_cpus) {
|
|
return error.broken_process_info;
|
|
}
|
|
|
|
// Get CPU model name
|
|
var model_name_buf: [512]u8 = undefined;
|
|
var len: usize = model_name_buf.len;
|
|
// Try brand_string first and if it fails try hw.model
|
|
if (!(std.c.sysctlbyname("machdep.cpu.brand_string", &model_name_buf, &len, null, 0) == 0 or
|
|
std.c.sysctlbyname("hw.model", &model_name_buf, &len, null, 0) == 0))
|
|
{
|
|
return error.no_processor_info;
|
|
}
|
|
// NOTE: sysctlbyname doesn't update len if it was large enough, so we
|
|
// still have to find the null terminator. All cpus can share the same
|
|
// model name.
|
|
const model_name = jsc.ZigString.init(std.mem.sliceTo(&model_name_buf, 0)).withEncoding().toJS(globalThis);
|
|
|
|
// Get CPU speed
|
|
var speed: u64 = 0;
|
|
len = @sizeOf(@TypeOf(speed));
|
|
_ = std.c.sysctlbyname("hw.cpufrequency", &speed, &len, null, 0);
|
|
if (speed == 0) {
|
|
// Suggested by Node implementation:
|
|
// If sysctl hw.cputype == CPU_TYPE_ARM64, the correct value is unavailable
|
|
// from Apple, but we can hard-code it here to a plausible value.
|
|
speed = 2_400_000_000;
|
|
}
|
|
|
|
// Get the multiplier; this is the number of ms/tick
|
|
const ticks: i64 = bun_sysconf__SC_CLK_TCK();
|
|
const multiplier = 1000 / @as(u64, @intCast(ticks));
|
|
|
|
// Set up each CPU value in the return
|
|
const values = try jsc.JSValue.createEmptyArray(globalThis, @as(u32, @intCast(num_cpus)));
|
|
var cpu_index: u32 = 0;
|
|
while (cpu_index < num_cpus) : (cpu_index += 1) {
|
|
const times = CPUTimes{
|
|
.user = info[cpu_index].cpu_ticks[0] * multiplier,
|
|
.nice = info[cpu_index].cpu_ticks[3] * multiplier,
|
|
.sys = info[cpu_index].cpu_ticks[1] * multiplier,
|
|
.idle = info[cpu_index].cpu_ticks[2] * multiplier,
|
|
.irq = 0, // not available
|
|
};
|
|
|
|
const cpu = jsc.JSValue.createEmptyObject(globalThis, 3);
|
|
cpu.put(globalThis, jsc.ZigString.static("speed"), jsc.JSValue.jsNumber(speed / 1_000_000));
|
|
cpu.put(globalThis, jsc.ZigString.static("model"), model_name);
|
|
cpu.put(globalThis, jsc.ZigString.static("times"), times.toValue(globalThis));
|
|
|
|
try values.putIndex(globalThis, cpu_index, cpu);
|
|
}
|
|
return values;
|
|
}
|
|
|
|
pub fn cpusImplWindows(globalThis: *jsc.JSGlobalObject) !jsc.JSValue {
|
|
var cpu_infos: [*]libuv.uv_cpu_info_t = undefined;
|
|
var count: c_int = undefined;
|
|
const err = libuv.uv_cpu_info(&cpu_infos, &count);
|
|
if (err != 0) {
|
|
return error.NoProcessorInfo;
|
|
}
|
|
defer libuv.uv_free_cpu_info(cpu_infos, count);
|
|
|
|
const values = try jsc.JSValue.createEmptyArray(globalThis, @intCast(count));
|
|
|
|
for (cpu_infos[0..@intCast(count)], 0..@intCast(count)) |cpu_info, i| {
|
|
const times = CPUTimes{
|
|
.user = cpu_info.cpu_times.user,
|
|
.nice = cpu_info.cpu_times.nice,
|
|
.sys = cpu_info.cpu_times.sys,
|
|
.idle = cpu_info.cpu_times.idle,
|
|
.irq = cpu_info.cpu_times.irq,
|
|
};
|
|
|
|
const cpu = jsc.JSValue.createEmptyObject(globalThis, 3);
|
|
cpu.put(globalThis, jsc.ZigString.static("model"), jsc.ZigString.init(bun.span(cpu_info.model)).withEncoding().toJS(globalThis));
|
|
cpu.put(globalThis, jsc.ZigString.static("speed"), jsc.JSValue.jsNumber(cpu_info.speed));
|
|
cpu.put(globalThis, jsc.ZigString.static("times"), times.toValue(globalThis));
|
|
|
|
try values.putIndex(globalThis, @intCast(i), cpu);
|
|
}
|
|
|
|
return values;
|
|
}
|
|
|
|
pub fn freemem() u64 {
|
|
// OsBinding.cpp
|
|
return @extern(*const fn () callconv(.C) u64, .{
|
|
.name = "Bun__Os__getFreeMemory",
|
|
})();
|
|
}
|
|
|
|
extern fn get_process_priority(pid: i32) i32;
|
|
pub fn getPriority(global: *jsc.JSGlobalObject, pid: i32) bun.JSError!i32 {
|
|
const result = get_process_priority(pid);
|
|
if (result == std.math.maxInt(i32)) {
|
|
const err = jsc.SystemError{
|
|
.message = bun.String.static("no such process"),
|
|
.code = bun.String.static("ESRCH"),
|
|
.errno = comptime switch (bun.Environment.os) {
|
|
else => -@as(c_int, @intFromEnum(std.posix.E.SRCH)),
|
|
.windows => libuv.UV_ESRCH,
|
|
},
|
|
.syscall = bun.String.static("uv_os_getpriority"),
|
|
};
|
|
return global.throwValue(err.toErrorInstanceWithInfoObject(global));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
pub fn homedir(global: *jsc.JSGlobalObject) !bun.String {
|
|
// In Node.js, this is a wrapper around uv_os_homedir.
|
|
if (Environment.isWindows) {
|
|
var out: bun.PathBuffer = undefined;
|
|
var size: usize = out.len;
|
|
if (libuv.uv_os_homedir(&out, &size).toError(.uv_os_homedir)) |err| {
|
|
return global.throwValue(err.toJS(global));
|
|
}
|
|
return bun.String.cloneUTF8(out[0..size]);
|
|
} else {
|
|
|
|
// The posix implementation of uv_os_homedir first checks the HOME
|
|
// environment variable, then falls back to reading the passwd entry.
|
|
if (bun.getenvZ("HOME")) |home| {
|
|
if (home.len > 0)
|
|
return bun.String.init(home);
|
|
}
|
|
|
|
// From libuv:
|
|
// > Calling sysconf(_SC_GETPW_R_SIZE_MAX) would get the suggested size, but it
|
|
// > is frequently 1024 or 4096, so we can just use that directly. The pwent
|
|
// > will not usually be large.
|
|
// Instead of always using an allocation, first try a stack allocation
|
|
// of 4096, then fallback to heap.
|
|
var stack_string_bytes: [4096]u8 = undefined;
|
|
var string_bytes: []u8 = &stack_string_bytes;
|
|
defer if (string_bytes.ptr != &stack_string_bytes)
|
|
bun.default_allocator.free(string_bytes);
|
|
|
|
var pw: bun.c.passwd = undefined;
|
|
var result: ?*bun.c.passwd = null;
|
|
|
|
const ret = while (true) {
|
|
const ret = bun.c.getpwuid_r(
|
|
bun.c.geteuid(),
|
|
&pw,
|
|
string_bytes.ptr,
|
|
string_bytes.len,
|
|
&result,
|
|
);
|
|
|
|
if (ret == @intFromEnum(bun.sys.E.INTR))
|
|
continue;
|
|
|
|
// If the system call wants more memory, double it.
|
|
if (ret == @intFromEnum(bun.sys.E.RANGE)) {
|
|
const len = string_bytes.len;
|
|
bun.default_allocator.free(string_bytes);
|
|
string_bytes = "";
|
|
string_bytes = try bun.default_allocator.alloc(u8, len * 2);
|
|
continue;
|
|
}
|
|
|
|
break ret;
|
|
};
|
|
|
|
if (ret != 0) {
|
|
return global.throwValue(bun.sys.Error.fromCode(
|
|
@enumFromInt(ret),
|
|
.uv_os_homedir,
|
|
).toJS(global));
|
|
}
|
|
|
|
if (result == null) {
|
|
// in uv__getpwuid_r, null result throws UV_ENOENT.
|
|
return global.throwValue(bun.sys.Error.fromCode(
|
|
.NOENT,
|
|
.uv_os_homedir,
|
|
).toJS(global));
|
|
}
|
|
|
|
return if (pw.pw_dir) |dir|
|
|
bun.String.cloneUTF8(bun.span(dir))
|
|
else
|
|
bun.String.empty;
|
|
}
|
|
}
|
|
|
|
pub fn hostname(global: *jsc.JSGlobalObject) bun.JSError!jsc.JSValue {
|
|
if (Environment.isWindows) {
|
|
var name_buffer: [129:0]u16 = undefined;
|
|
if (bun.windows.GetHostNameW(&name_buffer, name_buffer.len) == 0) {
|
|
const str = bun.String.cloneUTF16(bun.sliceTo(&name_buffer, 0));
|
|
defer str.deref();
|
|
return str.toJS(global);
|
|
}
|
|
|
|
var result: std.os.windows.ws2_32.WSADATA = undefined;
|
|
if (std.os.windows.ws2_32.WSAStartup(0x202, &result) == 0) {
|
|
if (bun.windows.GetHostNameW(&name_buffer, name_buffer.len) == 0) {
|
|
var y = bun.String.cloneUTF16(bun.sliceTo(&name_buffer, 0));
|
|
defer y.deref();
|
|
return y.toJS(global);
|
|
}
|
|
}
|
|
|
|
return jsc.ZigString.init("unknown").withEncoding().toJS(global);
|
|
} else {
|
|
var name_buffer: [bun.HOST_NAME_MAX]u8 = undefined;
|
|
return jsc.ZigString.init(std.posix.gethostname(&name_buffer) catch "unknown").withEncoding().toJS(global);
|
|
}
|
|
}
|
|
|
|
pub fn loadavg(global: *jsc.JSGlobalObject) bun.JSError!jsc.JSValue {
|
|
const result = switch (bun.Environment.os) {
|
|
.mac => loadavg: {
|
|
var avg: c.struct_loadavg = undefined;
|
|
var size: usize = @sizeOf(@TypeOf(avg));
|
|
|
|
std.posix.sysctlbynameZ(
|
|
"vm.loadavg",
|
|
&avg,
|
|
&size,
|
|
null,
|
|
0,
|
|
) catch |err| switch (err) {
|
|
else => break :loadavg [3]f64{ 0, 0, 0 },
|
|
};
|
|
|
|
const scale: f64 = @floatFromInt(avg.fscale);
|
|
break :loadavg .{
|
|
if (scale == 0.0) 0 else @as(f64, @floatFromInt(avg.ldavg[0])) / scale,
|
|
if (scale == 0.0) 0 else @as(f64, @floatFromInt(avg.ldavg[1])) / scale,
|
|
if (scale == 0.0) 0 else @as(f64, @floatFromInt(avg.ldavg[2])) / scale,
|
|
};
|
|
},
|
|
.linux => loadavg: {
|
|
var info: c.struct_sysinfo = undefined;
|
|
if (c.sysinfo(&info) == @as(c_int, 0)) {
|
|
break :loadavg [3]f64{
|
|
std.math.ceil((@as(f64, @floatFromInt(info.loads[0])) / 65536.0) * 100.0) / 100.0,
|
|
std.math.ceil((@as(f64, @floatFromInt(info.loads[1])) / 65536.0) * 100.0) / 100.0,
|
|
std.math.ceil((@as(f64, @floatFromInt(info.loads[2])) / 65536.0) * 100.0) / 100.0,
|
|
};
|
|
}
|
|
break :loadavg [3]f64{ 0, 0, 0 };
|
|
},
|
|
.windows => .{ 0, 0, 0 },
|
|
.wasm => @compileError("TODO"),
|
|
};
|
|
|
|
return jsc.JSArray.create(global, &.{
|
|
jsc.JSValue.jsNumber(result[0]),
|
|
jsc.JSValue.jsNumber(result[1]),
|
|
jsc.JSValue.jsNumber(result[2]),
|
|
});
|
|
}
|
|
|
|
pub const networkInterfaces = switch (Environment.os) {
|
|
.linux, .mac => networkInterfacesPosix,
|
|
.windows => networkInterfacesWindows,
|
|
else => @compileError("Unsupported OS"),
|
|
};
|
|
|
|
fn networkInterfacesPosix(globalThis: *jsc.JSGlobalObject) bun.JSError!jsc.JSValue {
|
|
// getifaddrs sets a pointer to a linked list
|
|
var interface_start: ?*c.ifaddrs = null;
|
|
const rc = c.getifaddrs(&interface_start);
|
|
if (rc != 0) {
|
|
const err = jsc.SystemError{
|
|
.message = bun.String.static("A system error occurred: getifaddrs returned an error"),
|
|
.code = bun.String.static("ERR_SYSTEM_ERROR"),
|
|
.errno = @intFromEnum(std.posix.errno(rc)),
|
|
.syscall = bun.String.static("getifaddrs"),
|
|
};
|
|
|
|
return globalThis.throwValue(err.toErrorInstance(globalThis));
|
|
}
|
|
defer c.freeifaddrs(interface_start);
|
|
|
|
const helpers = struct {
|
|
// We'll skip interfaces that aren't actually available
|
|
pub fn skip(iface: *c.ifaddrs) bool {
|
|
// Skip interfaces that aren't actually available
|
|
if (iface.ifa_flags & c.IFF_RUNNING == 0) return true;
|
|
if (iface.ifa_flags & c.IFF_UP == 0) return true;
|
|
if (iface.ifa_addr == null) return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
// We won't actually return link-layer interfaces but we need them for
|
|
// extracting the MAC address
|
|
pub fn isLinkLayer(iface: *c.ifaddrs) bool {
|
|
if (iface.ifa_addr == null) return false;
|
|
return if (comptime Environment.isLinux)
|
|
return iface.ifa_addr.*.sa_family == std.posix.AF.PACKET
|
|
else if (comptime Environment.isMac)
|
|
return iface.ifa_addr.?.*.sa_family == std.posix.AF.LINK
|
|
else
|
|
@compileError("unreachable");
|
|
}
|
|
|
|
pub fn isLoopback(iface: *c.ifaddrs) bool {
|
|
return iface.ifa_flags & c.IFF_LOOPBACK == c.IFF_LOOPBACK;
|
|
}
|
|
};
|
|
|
|
// The list currently contains entries for link-layer interfaces
|
|
// and the IPv4, IPv6 interfaces. We only want to return the latter two
|
|
// but need the link-layer entries to determine MAC address.
|
|
// So, on our first pass through the linked list we'll count the number of
|
|
// INET interfaces only.
|
|
var num_inet_interfaces: usize = 0;
|
|
var it = interface_start;
|
|
while (it) |iface| : (it = iface.ifa_next) {
|
|
if (helpers.skip(iface) or helpers.isLinkLayer(iface)) continue;
|
|
num_inet_interfaces += 1;
|
|
}
|
|
|
|
var ret = jsc.JSValue.createEmptyObject(globalThis, 8);
|
|
|
|
// Second pass through, populate each interface object
|
|
it = interface_start;
|
|
while (it) |iface| : (it = iface.ifa_next) {
|
|
if (helpers.skip(iface) or helpers.isLinkLayer(iface)) continue;
|
|
|
|
const interface_name = std.mem.sliceTo(iface.ifa_name, 0);
|
|
const addr = std.net.Address.initPosix(@alignCast(@as(*std.posix.sockaddr, @ptrCast(iface.ifa_addr))));
|
|
const netmask = std.net.Address.initPosix(@alignCast(@as(*std.posix.sockaddr, @ptrCast(iface.ifa_netmask))));
|
|
|
|
var interface = jsc.JSValue.createEmptyObject(globalThis, 7);
|
|
|
|
// address <string> The assigned IPv4 or IPv6 address
|
|
// cidr <string> The assigned IPv4 or IPv6 address with the routing prefix in CIDR notation. If the netmask is invalid, this property is set to null.
|
|
{
|
|
// Compute the CIDR suffix; returns null if the netmask cannot
|
|
// be converted to a CIDR suffix
|
|
const maybe_suffix: ?u8 = switch (addr.any.family) {
|
|
std.posix.AF.INET => netmaskToCIDRSuffix(netmask.in.sa.addr),
|
|
std.posix.AF.INET6 => netmaskToCIDRSuffix(@as(u128, @bitCast(netmask.in6.sa.addr))),
|
|
else => null,
|
|
};
|
|
|
|
// Format the address and then, if valid, the CIDR suffix; both
|
|
// the address and cidr values can be slices into this same buffer
|
|
// e.g. addr_str = "192.168.88.254", cidr_str = "192.168.88.254/24"
|
|
var buf: [64]u8 = undefined;
|
|
const addr_str = bun.fmt.formatIp(addr, &buf) catch unreachable;
|
|
var cidr = jsc.JSValue.null;
|
|
if (maybe_suffix) |suffix| {
|
|
//NOTE addr_str might not start at buf[0] due to slicing in formatIp
|
|
const start = @intFromPtr(addr_str.ptr) - @intFromPtr(&buf[0]);
|
|
// Start writing the suffix immediately after the address
|
|
const suffix_str = std.fmt.bufPrint(buf[start + addr_str.len ..], "/{}", .{suffix}) catch unreachable;
|
|
// The full cidr value is the address + the suffix
|
|
const cidr_str = buf[start .. start + addr_str.len + suffix_str.len];
|
|
cidr = jsc.ZigString.init(cidr_str).withEncoding().toJS(globalThis);
|
|
}
|
|
|
|
interface.put(globalThis, jsc.ZigString.static("address"), jsc.ZigString.init(addr_str).withEncoding().toJS(globalThis));
|
|
interface.put(globalThis, jsc.ZigString.static("cidr"), cidr);
|
|
}
|
|
|
|
// netmask <string> The IPv4 or IPv6 network mask
|
|
{
|
|
var buf: [64]u8 = undefined;
|
|
const str = bun.fmt.formatIp(netmask, &buf) catch unreachable;
|
|
interface.put(globalThis, jsc.ZigString.static("netmask"), jsc.ZigString.init(str).withEncoding().toJS(globalThis));
|
|
}
|
|
|
|
// family <string> Either IPv4 or IPv6
|
|
interface.put(globalThis, jsc.ZigString.static("family"), (switch (addr.any.family) {
|
|
std.posix.AF.INET => jsc.ZigString.static("IPv4"),
|
|
std.posix.AF.INET6 => jsc.ZigString.static("IPv6"),
|
|
else => jsc.ZigString.static("unknown"),
|
|
}).toJS(globalThis));
|
|
|
|
// mac <string> The MAC address of the network interface
|
|
{
|
|
// We need to search for the link-layer interface whose name matches this one
|
|
var ll_it = interface_start;
|
|
const maybe_ll_addr = while (ll_it) |ll_iface| : (ll_it = ll_iface.ifa_next) {
|
|
if (helpers.skip(ll_iface) or !helpers.isLinkLayer(ll_iface)) continue;
|
|
|
|
const ll_name = bun.sliceTo(ll_iface.ifa_name, 0);
|
|
if (!strings.hasPrefix(ll_name, interface_name)) continue;
|
|
if (ll_name.len > interface_name.len and ll_name[interface_name.len] != ':') continue;
|
|
|
|
// This is the correct link-layer interface entry for the current interface,
|
|
// cast to a link-layer socket address
|
|
if (comptime Environment.isLinux) {
|
|
break @as(?*std.posix.sockaddr.ll, @ptrCast(@alignCast(ll_iface.ifa_addr)));
|
|
} else if (comptime Environment.isMac) {
|
|
break @as(?*c.sockaddr_dl, @ptrCast(@alignCast(ll_iface.ifa_addr)));
|
|
} else {
|
|
@compileError("unreachable");
|
|
}
|
|
} else null;
|
|
|
|
if (maybe_ll_addr) |ll_addr| {
|
|
// Encode its link-layer address. We need 2*6 bytes for the
|
|
// hex characters and 5 for the colon separators
|
|
var mac_buf: [17]u8 = undefined;
|
|
const addr_data = if (comptime Environment.isLinux) ll_addr.addr else if (comptime Environment.isMac) ll_addr.sdl_data[ll_addr.sdl_nlen..] else @compileError("unreachable");
|
|
if (addr_data.len < 6) {
|
|
const mac = "00:00:00:00:00:00";
|
|
interface.put(globalThis, jsc.ZigString.static("mac"), jsc.ZigString.init(mac).withEncoding().toJS(globalThis));
|
|
} else {
|
|
const mac = std.fmt.bufPrint(&mac_buf, "{x:0>2}:{x:0>2}:{x:0>2}:{x:0>2}:{x:0>2}:{x:0>2}", .{
|
|
addr_data[0], addr_data[1], addr_data[2],
|
|
addr_data[3], addr_data[4], addr_data[5],
|
|
}) catch unreachable;
|
|
interface.put(globalThis, jsc.ZigString.static("mac"), jsc.ZigString.init(mac).withEncoding().toJS(globalThis));
|
|
}
|
|
} else {
|
|
const mac = "00:00:00:00:00:00";
|
|
interface.put(globalThis, jsc.ZigString.static("mac"), jsc.ZigString.init(mac).withEncoding().toJS(globalThis));
|
|
}
|
|
}
|
|
|
|
// internal <boolean> true if the network interface is a loopback or similar interface that is not remotely accessible; otherwise false
|
|
interface.put(globalThis, jsc.ZigString.static("internal"), jsc.JSValue.jsBoolean(helpers.isLoopback(iface)));
|
|
|
|
// scopeid <number> The numeric IPv6 scope ID (only specified when family is IPv6)
|
|
if (addr.any.family == std.posix.AF.INET6) {
|
|
interface.put(globalThis, jsc.ZigString.static("scopeid"), jsc.JSValue.jsNumber(addr.in6.sa.scope_id));
|
|
}
|
|
|
|
// Does this entry already exist?
|
|
if (try ret.get(globalThis, interface_name)) |array| {
|
|
// Add this interface entry to the existing array
|
|
const next_index: u32 = @intCast(try array.getLength(globalThis));
|
|
try array.putIndex(globalThis, next_index, interface);
|
|
} else {
|
|
// Add it as an array with this interface as an element
|
|
const member_name = jsc.ZigString.init(interface_name);
|
|
var array = try jsc.JSValue.createEmptyArray(globalThis, 1);
|
|
try array.putIndex(globalThis, 0, interface);
|
|
ret.put(globalThis, &member_name, array);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
fn networkInterfacesWindows(globalThis: *jsc.JSGlobalObject) bun.JSError!jsc.JSValue {
|
|
var ifaces: [*]libuv.uv_interface_address_t = undefined;
|
|
var count: c_int = undefined;
|
|
const err = libuv.uv_interface_addresses(&ifaces, &count);
|
|
if (err != 0) {
|
|
const sys_err = jsc.SystemError{
|
|
.message = bun.String.static("uv_interface_addresses failed"),
|
|
.code = bun.String.static("ERR_SYSTEM_ERROR"),
|
|
//.info = info,
|
|
.errno = err,
|
|
.syscall = bun.String.static("uv_interface_addresses"),
|
|
};
|
|
return globalThis.throwValue(sys_err.toErrorInstance(globalThis));
|
|
}
|
|
defer libuv.uv_free_interface_addresses(ifaces, count);
|
|
|
|
var ret = jsc.JSValue.createEmptyObject(globalThis, 8);
|
|
|
|
// 65 comes from: https://stackoverflow.com/questions/39443413/why-is-inet6-addrstrlen-defined-as-46-in-c
|
|
var ip_buf: [65]u8 = undefined;
|
|
var mac_buf: [17]u8 = undefined;
|
|
|
|
for (ifaces[0..@intCast(count)]) |iface| {
|
|
var interface = jsc.JSValue.createEmptyObject(globalThis, 7);
|
|
|
|
// address <string> The assigned IPv4 or IPv6 address
|
|
// cidr <string> The assigned IPv4 or IPv6 address with the routing prefix in CIDR notation. If the netmask is invalid, this property is set to null.
|
|
var cidr = jsc.JSValue.null;
|
|
{
|
|
// Compute the CIDR suffix; returns null if the netmask cannot
|
|
// be converted to a CIDR suffix
|
|
const maybe_suffix: ?u8 = switch (iface.address.address4.family) {
|
|
std.posix.AF.INET => netmaskToCIDRSuffix(iface.netmask.netmask4.addr),
|
|
std.posix.AF.INET6 => netmaskToCIDRSuffix(@as(u128, @bitCast(iface.netmask.netmask6.addr))),
|
|
else => null,
|
|
};
|
|
|
|
// Format the address and then, if valid, the CIDR suffix; both
|
|
// the address and cidr values can be slices into this same buffer
|
|
// e.g. addr_str = "192.168.88.254", cidr_str = "192.168.88.254/24"
|
|
const addr_str = bun.fmt.formatIp(
|
|
// std.net.Address will do ptrCast depending on the family so this is ok
|
|
std.net.Address.initPosix(@ptrCast(&iface.address.address4)),
|
|
&ip_buf,
|
|
) catch unreachable;
|
|
if (maybe_suffix) |suffix| {
|
|
//NOTE addr_str might not start at buf[0] due to slicing in formatIp
|
|
const start = @intFromPtr(addr_str.ptr) - @intFromPtr(&ip_buf[0]);
|
|
// Start writing the suffix immediately after the address
|
|
const suffix_str = std.fmt.bufPrint(ip_buf[start + addr_str.len ..], "/{}", .{suffix}) catch unreachable;
|
|
// The full cidr value is the address + the suffix
|
|
const cidr_str = ip_buf[start .. start + addr_str.len + suffix_str.len];
|
|
cidr = jsc.ZigString.init(cidr_str).withEncoding().toJS(globalThis);
|
|
}
|
|
|
|
interface.put(globalThis, jsc.ZigString.static("address"), jsc.ZigString.init(addr_str).withEncoding().toJS(globalThis));
|
|
}
|
|
|
|
// netmask
|
|
{
|
|
const str = bun.fmt.formatIp(
|
|
// std.net.Address will do ptrCast depending on the family so this is ok
|
|
std.net.Address.initPosix(@ptrCast(&iface.netmask.netmask4)),
|
|
&ip_buf,
|
|
) catch unreachable;
|
|
interface.put(globalThis, jsc.ZigString.static("netmask"), jsc.ZigString.init(str).withEncoding().toJS(globalThis));
|
|
}
|
|
// family
|
|
interface.put(globalThis, jsc.ZigString.static("family"), (switch (iface.address.address4.family) {
|
|
std.posix.AF.INET => jsc.ZigString.static("IPv4"),
|
|
std.posix.AF.INET6 => jsc.ZigString.static("IPv6"),
|
|
else => jsc.ZigString.static("unknown"),
|
|
}).toJS(globalThis));
|
|
|
|
// mac
|
|
{
|
|
const phys = iface.phys_addr;
|
|
const mac = std.fmt.bufPrint(&mac_buf, "{x:0>2}:{x:0>2}:{x:0>2}:{x:0>2}:{x:0>2}:{x:0>2}", .{
|
|
phys[0], phys[1], phys[2], phys[3], phys[4], phys[5],
|
|
}) catch unreachable;
|
|
interface.put(globalThis, jsc.ZigString.static("mac"), jsc.ZigString.init(mac).withEncoding().toJS(globalThis));
|
|
}
|
|
|
|
// internal
|
|
{
|
|
interface.put(globalThis, jsc.ZigString.static("internal"), jsc.JSValue.jsBoolean(iface.is_internal != 0));
|
|
}
|
|
|
|
// cidr. this is here to keep ordering consistent with the node implementation
|
|
interface.put(globalThis, jsc.ZigString.static("cidr"), cidr);
|
|
|
|
// scopeid
|
|
if (iface.address.address4.family == std.posix.AF.INET6) {
|
|
interface.put(globalThis, jsc.ZigString.static("scopeid"), jsc.JSValue.jsNumber(iface.address.address6.scope_id));
|
|
}
|
|
|
|
// Does this entry already exist?
|
|
const interface_name = bun.span(iface.name);
|
|
if (try ret.get(globalThis, interface_name)) |array| {
|
|
// Add this interface entry to the existing array
|
|
const next_index: u32 = @intCast(try array.getLength(globalThis));
|
|
try array.putIndex(globalThis, next_index, interface);
|
|
} else {
|
|
// Add it as an array with this interface as an element
|
|
const member_name = jsc.ZigString.init(interface_name);
|
|
var array = try jsc.JSValue.createEmptyArray(globalThis, 1);
|
|
try array.putIndex(globalThis, 0, interface);
|
|
ret.put(globalThis, &member_name, array);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
pub fn release() bun.String {
|
|
var name_buffer: [bun.HOST_NAME_MAX]u8 = undefined;
|
|
|
|
const value = switch (Environment.os) {
|
|
.linux => slice: {
|
|
const uts = std.posix.uname();
|
|
const result = bun.sliceTo(&uts.release, 0);
|
|
bun.copy(u8, &name_buffer, result);
|
|
|
|
break :slice name_buffer[0..result.len];
|
|
},
|
|
.mac => slice: {
|
|
@memset(&name_buffer, 0);
|
|
|
|
var size: usize = name_buffer.len;
|
|
|
|
if (std.c.sysctlbyname(
|
|
"kern.osrelease",
|
|
&name_buffer,
|
|
&size,
|
|
null,
|
|
0,
|
|
) == -1) break :slice "unknown";
|
|
|
|
break :slice bun.sliceTo(&name_buffer, 0);
|
|
},
|
|
.windows => slice: {
|
|
var info: bun.windows.libuv.uv_utsname_s = undefined;
|
|
const err = bun.windows.libuv.uv_os_uname(&info);
|
|
if (err != 0) {
|
|
break :slice "unknown";
|
|
}
|
|
const value = bun.sliceTo(&info.release, 0);
|
|
@memcpy(name_buffer[0..value.len], value);
|
|
break :slice name_buffer[0..value.len];
|
|
},
|
|
else => @compileError("unsupported os"),
|
|
};
|
|
|
|
return bun.String.cloneUTF8(value);
|
|
}
|
|
|
|
pub extern fn set_process_priority(pid: i32, priority: i32) i32;
|
|
pub fn setProcessPriorityImpl(pid: i32, priority: i32) std.c.E {
|
|
if (pid < 0) return .SRCH;
|
|
|
|
const code: i32 = set_process_priority(pid, priority);
|
|
|
|
if (code == -2) return .SRCH;
|
|
if (code == 0) return .SUCCESS;
|
|
|
|
const errcode = bun.sys.getErrno(code);
|
|
return @enumFromInt(@intFromEnum(errcode));
|
|
}
|
|
|
|
pub fn setPriority1(global: *jsc.JSGlobalObject, pid: i32, priority: i32) !void {
|
|
const errcode = setProcessPriorityImpl(pid, priority);
|
|
switch (errcode) {
|
|
.SRCH => {
|
|
const err = jsc.SystemError{
|
|
.message = bun.String.static("no such process"),
|
|
.code = bun.String.static("ESRCH"),
|
|
.errno = comptime switch (bun.Environment.os) {
|
|
else => -@as(c_int, @intFromEnum(std.posix.E.SRCH)),
|
|
.windows => libuv.UV_ESRCH,
|
|
},
|
|
.syscall = bun.String.static("uv_os_getpriority"),
|
|
};
|
|
return global.throwValue(err.toErrorInstanceWithInfoObject(global));
|
|
},
|
|
.ACCES => {
|
|
const err = jsc.SystemError{
|
|
.message = bun.String.static("permission denied"),
|
|
.code = bun.String.static("EACCES"),
|
|
.errno = comptime switch (bun.Environment.os) {
|
|
else => -@as(c_int, @intFromEnum(std.posix.E.ACCES)),
|
|
.windows => libuv.UV_EACCES,
|
|
},
|
|
.syscall = bun.String.static("uv_os_getpriority"),
|
|
};
|
|
return global.throwValue(err.toErrorInstanceWithInfoObject(global));
|
|
},
|
|
.PERM => {
|
|
const err = jsc.SystemError{
|
|
.message = bun.String.static("operation not permitted"),
|
|
.code = bun.String.static("EPERM"),
|
|
.errno = comptime switch (bun.Environment.os) {
|
|
else => -@as(c_int, @intFromEnum(std.posix.E.SRCH)),
|
|
.windows => libuv.UV_ESRCH,
|
|
},
|
|
.syscall = bun.String.static("uv_os_getpriority"),
|
|
};
|
|
return global.throwValue(err.toErrorInstanceWithInfoObject(global));
|
|
},
|
|
else => {
|
|
// no other error codes can be emitted
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn setPriority2(global: *jsc.JSGlobalObject, priority: i32) !void {
|
|
return setPriority1(global, 0, priority);
|
|
}
|
|
|
|
pub fn totalmem() u64 {
|
|
switch (bun.Environment.os) {
|
|
.mac => {
|
|
var memory_: [32]c_ulonglong = undefined;
|
|
var size: usize = memory_.len;
|
|
|
|
std.posix.sysctlbynameZ(
|
|
"hw.memsize",
|
|
&memory_,
|
|
&size,
|
|
null,
|
|
0,
|
|
) catch |err| switch (err) {
|
|
else => return 0,
|
|
};
|
|
|
|
return memory_[0];
|
|
},
|
|
.linux => {
|
|
var info: c.struct_sysinfo = undefined;
|
|
if (c.sysinfo(&info) == @as(c_int, 0)) return @as(u64, @bitCast(info.totalram)) *% @as(c_ulong, @bitCast(@as(c_ulong, info.mem_unit)));
|
|
return 0;
|
|
},
|
|
.windows => {
|
|
return libuv.uv_get_total_memory();
|
|
},
|
|
else => @compileError("unsupported os"),
|
|
}
|
|
}
|
|
|
|
pub fn uptime(global: *jsc.JSGlobalObject) bun.JSError!f64 {
|
|
switch (Environment.os) {
|
|
.windows => {
|
|
var uptime_value: f64 = undefined;
|
|
const err = libuv.uv_uptime(&uptime_value);
|
|
if (err != 0) {
|
|
const sys_err = jsc.SystemError{
|
|
.message = bun.String.static("failed to get system uptime"),
|
|
.code = bun.String.static("ERR_SYSTEM_ERROR"),
|
|
.errno = err,
|
|
.syscall = bun.String.static("uv_uptime"),
|
|
};
|
|
return global.throwValue(sys_err.toErrorInstance(global));
|
|
}
|
|
return uptime_value;
|
|
},
|
|
.mac => {
|
|
var boot_time: std.posix.timeval = undefined;
|
|
var size: usize = @sizeOf(@TypeOf(boot_time));
|
|
|
|
std.posix.sysctlbynameZ(
|
|
"kern.boottime",
|
|
&boot_time,
|
|
&size,
|
|
null,
|
|
0,
|
|
) catch |err| switch (err) {
|
|
else => return 0,
|
|
};
|
|
|
|
return @floatFromInt(std.time.timestamp() - boot_time.sec);
|
|
},
|
|
.linux => {
|
|
var info: c.struct_sysinfo = undefined;
|
|
if (c.sysinfo(&info) == 0)
|
|
return @floatFromInt(info.uptime);
|
|
return 0;
|
|
},
|
|
else => @compileError("unsupported os"),
|
|
}
|
|
}
|
|
|
|
pub fn userInfo(globalThis: *jsc.JSGlobalObject, options: gen.UserInfoOptions) bun.JSError!jsc.JSValue {
|
|
_ = options; // TODO:
|
|
|
|
const result = jsc.JSValue.createEmptyObject(globalThis, 5);
|
|
|
|
const home = try homedir(globalThis);
|
|
defer home.deref();
|
|
|
|
result.put(globalThis, jsc.ZigString.static("homedir"), home.toJS(globalThis));
|
|
|
|
if (comptime Environment.isWindows) {
|
|
result.put(globalThis, jsc.ZigString.static("username"), jsc.ZigString.init(bun.getenvZ("USERNAME") orelse "unknown").withEncoding().toJS(globalThis));
|
|
result.put(globalThis, jsc.ZigString.static("uid"), jsc.JSValue.jsNumber(-1));
|
|
result.put(globalThis, jsc.ZigString.static("gid"), jsc.JSValue.jsNumber(-1));
|
|
result.put(globalThis, jsc.ZigString.static("shell"), jsc.JSValue.jsNull());
|
|
} else {
|
|
const username = bun.getenvZ("USER") orelse "unknown";
|
|
|
|
result.put(globalThis, jsc.ZigString.static("username"), jsc.ZigString.init(username).withEncoding().toJS(globalThis));
|
|
result.put(globalThis, jsc.ZigString.static("shell"), jsc.ZigString.init(bun.getenvZ("SHELL") orelse "unknown").withEncoding().toJS(globalThis));
|
|
result.put(globalThis, jsc.ZigString.static("uid"), jsc.JSValue.jsNumber(c.getuid()));
|
|
result.put(globalThis, jsc.ZigString.static("gid"), jsc.JSValue.jsNumber(c.getgid()));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
pub fn version() bun.JSError!bun.String {
|
|
var name_buffer: [bun.HOST_NAME_MAX]u8 = undefined;
|
|
|
|
const slice: []const u8 = switch (Environment.os) {
|
|
.mac => slice: {
|
|
@memset(&name_buffer, 0);
|
|
|
|
var size: usize = name_buffer.len;
|
|
|
|
if (std.c.sysctlbyname(
|
|
"kern.version",
|
|
&name_buffer,
|
|
&size,
|
|
null,
|
|
0,
|
|
) == -1) break :slice "unknown";
|
|
|
|
break :slice bun.sliceTo(&name_buffer, 0);
|
|
},
|
|
.linux => slice: {
|
|
const uts = std.posix.uname();
|
|
const result = bun.sliceTo(&uts.version, 0);
|
|
bun.copy(u8, &name_buffer, result);
|
|
|
|
break :slice name_buffer[0..result.len];
|
|
},
|
|
.windows => slice: {
|
|
var info: bun.windows.libuv.uv_utsname_s = undefined;
|
|
const err = bun.windows.libuv.uv_os_uname(&info);
|
|
if (err != 0) {
|
|
break :slice "unknown";
|
|
}
|
|
const slice = bun.sliceTo(&info.version, 0);
|
|
@memcpy(name_buffer[0..slice.len], slice);
|
|
break :slice name_buffer[0..slice.len];
|
|
},
|
|
else => @compileError("unsupported os"),
|
|
};
|
|
|
|
return bun.String.cloneUTF8(slice);
|
|
}
|
|
|
|
/// Given a netmask returns a CIDR suffix. Returns null if the mask is not valid.
|
|
/// `@TypeOf(mask)` must be one of u32 (IPv4) or u128 (IPv6)
|
|
fn netmaskToCIDRSuffix(mask: anytype) ?u8 {
|
|
const T = @TypeOf(mask);
|
|
comptime bun.assert(T == u32 or T == u128);
|
|
|
|
const mask_bits = @byteSwap(mask);
|
|
|
|
// Validity check: set bits should be left-contiguous
|
|
const first_zero = @clz(~mask_bits);
|
|
const last_one = @bitSizeOf(T) - @ctz(mask_bits);
|
|
if (first_zero < @bitSizeOf(T) and first_zero < last_one) return null;
|
|
return first_zero;
|
|
}
|
|
|
|
const string = []const u8;
|
|
|
|
const std = @import("std");
|
|
|
|
const bun = @import("bun");
|
|
const Environment = bun.Environment;
|
|
const c = bun.c;
|
|
const jsc = bun.jsc;
|
|
const strings = bun.strings;
|
|
const sys = bun.sys;
|
|
const gen = bun.gen.node_os;
|
|
const libuv = bun.windows.libuv;
|