Files
bun.sh/src/bun.js/node/node_os.zig
2023-06-29 16:37:38 -07:00

757 lines
36 KiB
Zig

const std = @import("std");
const builtin = @import("builtin");
const bun = @import("root").bun;
const C = bun.C;
const string = bun.string;
const strings = bun.strings;
const JSC = @import("root").bun.JSC;
const Environment = bun.Environment;
const Global = bun.Global;
const is_bindgen: bool = std.meta.globalOption("bindgen", bool) orelse false;
const heap_allocator = bun.default_allocator;
const constants = @import("./os/constants.zig");
pub const Os = struct {
pub const name = "Bun__Os";
pub const code = @embedFile("../os.exports.js");
pub fn create(globalObject: *JSC.JSGlobalObject) callconv(.C) JSC.JSValue {
const module = JSC.JSValue.createEmptyObject(globalObject, 22);
module.put(globalObject, JSC.ZigString.static("arch"), JSC.NewFunction(globalObject, JSC.ZigString.static("arch"), 0, arch, true));
module.put(globalObject, JSC.ZigString.static("cpus"), JSC.NewFunction(globalObject, JSC.ZigString.static("cpus"), 0, cpus, true));
module.put(globalObject, JSC.ZigString.static("endianness"), JSC.NewFunction(globalObject, JSC.ZigString.static("endianness"), 0, endianness, true));
module.put(globalObject, JSC.ZigString.static("freemem"), JSC.NewFunction(globalObject, JSC.ZigString.static("freemem"), 0, freemem, true));
module.put(globalObject, JSC.ZigString.static("getPriority"), JSC.NewFunction(globalObject, JSC.ZigString.static("getPriority"), 1, getPriority, true));
module.put(globalObject, JSC.ZigString.static("homedir"), JSC.NewFunction(globalObject, JSC.ZigString.static("homedir"), 0, homedir, true));
module.put(globalObject, JSC.ZigString.static("hostname"), JSC.NewFunction(globalObject, JSC.ZigString.static("hostname"), 0, hostname, true));
module.put(globalObject, JSC.ZigString.static("loadavg"), JSC.NewFunction(globalObject, JSC.ZigString.static("loadavg"), 0, loadavg, true));
module.put(globalObject, JSC.ZigString.static("machine"), JSC.NewFunction(globalObject, JSC.ZigString.static("machine"), 0, machine, true));
module.put(globalObject, JSC.ZigString.static("networkInterfaces"), JSC.NewFunction(globalObject, JSC.ZigString.static("networkInterfaces"), 0, networkInterfaces, true));
module.put(globalObject, JSC.ZigString.static("platform"), JSC.NewFunction(globalObject, JSC.ZigString.static("platform"), 0, platform, true));
module.put(globalObject, JSC.ZigString.static("release"), JSC.NewFunction(globalObject, JSC.ZigString.static("release"), 0, release, true));
module.put(globalObject, JSC.ZigString.static("setPriority"), JSC.NewFunction(globalObject, JSC.ZigString.static("setPriority"), 2, setPriority, true));
module.put(globalObject, JSC.ZigString.static("totalmem"), JSC.NewFunction(globalObject, JSC.ZigString.static("totalmem"), 0, totalmem, true));
module.put(globalObject, JSC.ZigString.static("type"), JSC.NewFunction(globalObject, JSC.ZigString.static("type"), 0, Os.type, true));
module.put(globalObject, JSC.ZigString.static("uptime"), JSC.NewFunction(globalObject, JSC.ZigString.static("uptime"), 0, uptime, true));
module.put(globalObject, JSC.ZigString.static("userInfo"), JSC.NewFunction(globalObject, JSC.ZigString.static("userInfo"), 0, userInfo, true));
module.put(globalObject, JSC.ZigString.static("version"), JSC.NewFunction(globalObject, JSC.ZigString.static("version"), 0, version, true));
module.put(globalObject, JSC.ZigString.static("machine"), JSC.NewFunction(globalObject, JSC.ZigString.static("machine"), 0, machine, true));
module.put(globalObject, JSC.ZigString.static("devNull"), JSC.ZigString.init(devNull).withEncoding().toValue(globalObject));
module.put(globalObject, JSC.ZigString.static("EOL"), JSC.ZigString.init(EOL).withEncoding().toValue(globalObject));
module.put(globalObject, JSC.ZigString.static("constants"), constants.create(globalObject));
return module;
}
pub const EOL: []const u8 = if (Environment.isWindows) "\r\n" else "\n";
pub const devNull: []const u8 = if (Environment.isWindows) "\\\\.\nul" else "/dev/null";
pub fn arch(globalThis: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue {
JSC.markBinding(@src());
return JSC.ZigString.init(Global.arch_name).withEncoding().toValue(globalThis);
}
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(globalThis: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue {
JSC.markBinding(@src());
return if (comptime Environment.isLinux)
cpusImplLinux(globalThis) catch {
const err = JSC.SystemError{
.message = JSC.ZigString.init("Failed to get cpu information"),
.code = JSC.ZigString.init(@as(string, @tagName(JSC.Node.ErrorCode.ERR_SYSTEM_ERROR))),
};
globalThis.vm().throwError(globalThis, err.toErrorInstance(globalThis));
return JSC.JSValue.jsUndefined();
}
else if (comptime Environment.isMac)
cpusImplDarwin(globalThis) catch {
const err = JSC.SystemError{
.message = JSC.ZigString.init("Failed to get cpu information"),
.code = JSC.ZigString.init(@as(string, @tagName(JSC.Node.ErrorCode.ERR_SYSTEM_ERROR))),
};
globalThis.vm().throwError(globalThis, err.toErrorInstance(globalThis));
return JSC.JSValue.jsUndefined();
}
else
JSC.JSValue.createEmptyArray(globalThis, 0);
}
fn cpusImplLinux(globalThis: *JSC.JSGlobalObject) !JSC.JSValue {
// Create the return array
const values = JSC.JSValue.createEmptyArray(globalThis, 0);
var num_cpus: u32 = 0;
// Use a large line buffer because the /proc/stat file can have a very long list of interrupts
var line_buffer: [1024 * 8]u8 = undefined;
// Read /proc/stat to get number of CPUs and times
if (std.fs.openFileAbsolute("/proc/stat", .{})) |file| {
defer file.close();
var reader = file.reader();
// Skip the first line (aggregate of all CPUs)
try reader.skipUntilDelimiterOrEof('\n');
// Read each CPU line
while (try reader.readUntilDelimiterOrEof(&line_buffer, '\n')) |line| {
// CPU lines are formatted as `cpu0 user nice sys idle iowait irq softirq`
var toks = std.mem.tokenize(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));
values.putIndex(globalThis, num_cpus, cpu);
num_cpus += 1;
}
} else |_| {
return error.no_proc_stat;
}
// Read /proc/cpuinfo to get model information (optional)
if (std.fs.openFileAbsolute("/proc/cpuinfo", .{})) |file| {
defer file.close();
var reader = file.reader();
const key_processor = "processor\t: ";
const key_model_name = "model name\t: ";
var cpu_index: u32 = 0;
while (try reader.readUntilDelimiterOrEof(&line_buffer, '\n')) |line| {
if (std.mem.startsWith(u8, line, key_processor)) {
// 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;
} else if (std.mem.startsWith(u8, 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 = JSC.JSObject.getIndex(values, globalThis, cpu_index);
cpu.put(globalThis, JSC.ZigString.static("model"), JSC.ZigString.init(model_name).withEncoding().toValueGC(globalThis));
}
//TODO: special handling for ARM64 (no model name)?
}
} else |_| {
// Initialize model name to "unknown"
var it = values.arrayIterator(globalThis);
while (it.next()) |cpu| {
cpu.put(globalThis, JSC.ZigString.static("model"), JSC.ZigString.static("unknown").withEncoding().toValue(globalThis));
}
}
// Read /sys/devices/system/cpu/cpu{}/cpufreq/scaling_cur_freq to get current frequency (optional)
var cpu_index: u32 = 0;
while (cpu_index < num_cpus) : (cpu_index += 1) {
const cpu = JSC.JSObject.getIndex(values, globalThis, 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 bytes_read = try file.readAll(&line_buffer);
const digits = std.mem.trim(u8, line_buffer[0..bytes_read], " \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;
}
fn cpusImplDarwin(globalThis: *JSC.JSGlobalObject) !JSC.JSValue {
const local_bindings = @import("../../darwin_c.zig");
const c = std.c;
// Fetch the CPU info structure
var num_cpus: c.natural_t = 0;
var info: [*]local_bindings.processor_cpu_load_info = undefined;
var info_size: std.c.mach_msg_type_number_t = 0;
if (local_bindings.host_processor_info(std.c.mach_host_self(), local_bindings.PROCESSOR_CPU_LOAD_INFO, &num_cpus, @ptrCast(*local_bindings.processor_info_array_t, &info), &info_size) != .SUCCESS) {
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 != 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().toValueGC(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 unistd = @cImport({
@cInclude("unistd.h");
});
const ticks: i64 = unistd.sysconf(unistd._SC_CLK_TCK);
const multiplier = 1000 / @intCast(u64, ticks);
// Set up each CPU value in the return
const values = JSC.JSValue.createEmptyArray(globalThis, @intCast(u32, 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));
values.putIndex(globalThis, cpu_index, cpu);
}
return values;
}
pub fn endianness(globalThis: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue {
JSC.markBinding(@src());
switch (comptime builtin.target.cpu.arch.endian()) {
.Big => {
return JSC.ZigString.init("BE").withEncoding().toValue(globalThis);
},
.Little => {
return JSC.ZigString.init("LE").withEncoding().toValue(globalThis);
},
}
}
pub fn freemem(_: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue {
JSC.markBinding(@src());
return JSC.JSValue.jsNumberFromUint64(C.getFreeMemory());
}
pub fn getPriority(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue {
JSC.markBinding(@src());
var args_ = callframe.arguments(1);
const arguments: []const JSC.JSValue = args_.ptr[0..args_.len];
if (arguments.len > 0 and !arguments[0].isNumber()) {
const err = JSC.toTypeError(
JSC.Node.ErrorCode.ERR_INVALID_ARG_TYPE,
"getPriority() expects a number",
.{},
globalThis,
);
globalThis.vm().throwError(globalThis, err);
return JSC.JSValue.jsUndefined();
}
const pid = if (arguments.len > 0) arguments[0].asInt32() else 0;
const priority = C.getProcessPriority(pid);
if (priority == -1) {
//const info = JSC.JSValue.createEmptyObject(globalThis, 4);
//info.put(globalThis, JSC.ZigString.static("errno"), JSC.JSValue.jsNumberFromInt32(-3));
//info.put(globalThis, JSC.ZigString.static("code"), JSC.ZigString.init("ESRCH").withEncoding().toValueGC(globalThis));
//info.put(globalThis, JSC.ZigString.static("message"), JSC.ZigString.init("no such process").withEncoding().toValueGC(globalThis));
//info.put(globalThis, JSC.ZigString.static("syscall"), JSC.ZigString.init("uv_os_getpriority").withEncoding().toValueGC(globalThis));
const err = JSC.SystemError{
.message = JSC.ZigString.init("A system error occurred: uv_os_getpriority returned ESRCH (no such process)"),
.code = JSC.ZigString.init(@as(string, @tagName(JSC.Node.ErrorCode.ERR_SYSTEM_ERROR))),
//.info = info,
.errno = -3,
.syscall = JSC.ZigString.init("uv_os_getpriority"),
};
globalThis.vm().throwError(globalThis, err.toErrorInstance(globalThis));
return JSC.JSValue.jsUndefined();
}
return JSC.JSValue.jsNumberFromInt32(priority);
}
pub fn homedir(globalThis: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue {
JSC.markBinding(@src());
const dir: []const u8 = brk: {
if (comptime Environment.isWindows) {
if (bun.getenvZ("USERPROFILE")) |env|
break :brk bun.asByteSlice(env);
} else {
if (bun.getenvZ("HOME")) |env|
break :brk bun.asByteSlice(env);
}
break :brk "unknown";
};
return JSC.ZigString.init(dir).withEncoding().toValueGC(globalThis);
}
pub fn hostname(globalThis: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue {
JSC.markBinding(@src());
var name_buffer: [std.os.HOST_NAME_MAX]u8 = undefined;
return JSC.ZigString.init(std.os.gethostname(&name_buffer) catch "unknown").withEncoding().toValueGC(globalThis);
}
pub fn loadavg(globalThis: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue {
JSC.markBinding(@src());
const result = C.getSystemLoadavg();
return JSC.JSArray.from(globalThis, &.{
JSC.JSValue.jsDoubleNumber(result[0]),
JSC.JSValue.jsDoubleNumber(result[1]),
JSC.JSValue.jsDoubleNumber(result[2]),
});
}
pub fn networkInterfaces(globalThis: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue {
JSC.markBinding(@src());
// 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 = JSC.ZigString.init("A system error occurred: getifaddrs returned an error"),
.code = JSC.ZigString.init(@as(string, @tagName(JSC.Node.ErrorCode.ERR_SYSTEM_ERROR))),
.errno = @intFromEnum(std.os.errno(rc)),
.syscall = JSC.ZigString.init("getifaddrs"),
};
globalThis.vm().throwError(globalThis, err.toErrorInstance(globalThis));
return JSC.JSValue.jsUndefined();
}
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.os.AF.PACKET
else if (comptime Environment.isMac)
return iface.ifa_addr.?.*.family == std.os.AF.LINK
else
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(4, @ptrCast(*std.os.sockaddr, iface.ifa_addr)));
const netmask = std.net.Address.initPosix(@alignCast(4, @ptrCast(*std.os.sockaddr, 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.os.AF.INET => netmaskToCIDRSuffix(netmask.in.sa.addr),
std.os.AF.INET6 => netmaskToCIDRSuffix(@bitCast(u128, 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().toValueGC(globalThis);
}
interface.put(globalThis, JSC.ZigString.static("address"), JSC.ZigString.init(addr_str).withEncoding().toValueGC(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().toValueGC(globalThis));
}
// family <string> Either IPv4 or IPv6
interface.put(globalThis, JSC.ZigString.static("family"), (switch (addr.any.family) {
std.os.AF.INET => JSC.ZigString.static("IPv4"),
std.os.AF.INET6 => JSC.ZigString.static("IPv6"),
else => JSC.ZigString.static("unknown"),
}).toValueGC(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 @ptrCast(?*std.os.sockaddr.ll, @alignCast(4, ll_iface.ifa_addr));
} else if (comptime Environment.isMac) {
break @ptrCast(?*C.sockaddr_dl, @alignCast(2, ll_iface.ifa_addr));
} else 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;
var addr_data = if (comptime Environment.isLinux) ll_addr.addr else if (comptime Environment.isMac) ll_addr.sdl_data[ll_addr.sdl_nlen..] else unreachable;
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().toValueGC(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.os.AF.INET6) {
interface.put(globalThis, JSC.ZigString.static("scope_id"), JSC.JSValue.jsNumber(addr.in6.sa.scope_id));
}
// Does this entry already exist?
if (ret.get(globalThis, interface_name)) |array| {
// Add this interface entry to the existing array
const next_index = @intCast(u32, array.getLength(globalThis));
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 = JSC.JSValue.createEmptyArray(globalThis, 1);
array.putIndex(globalThis, 0, interface);
ret.put(globalThis, &member_name, array);
}
}
return ret;
}
pub fn platform(globalThis: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue {
JSC.markBinding(@src());
return JSC.ZigString.init(Global.os_name).withEncoding().toValueGC(globalThis);
}
pub fn release(globalThis: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue {
JSC.markBinding(@src());
var name_buffer: [std.os.HOST_NAME_MAX]u8 = undefined;
return JSC.ZigString.init(C.getRelease(&name_buffer)).withEncoding().toValueGC(globalThis);
}
pub fn setPriority(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue {
JSC.markBinding(@src());
var args_ = callframe.arguments(2);
var arguments: []const JSC.JSValue = args_.ptr[0..args_.len];
if (arguments.len == 0) {
const err = JSC.toTypeError(
JSC.Node.ErrorCode.ERR_INVALID_ARG_TYPE,
"The \"priority\" argument must be of type number. Received undefined",
.{},
globalThis,
);
globalThis.vm().throwError(globalThis, err);
return JSC.JSValue.jsUndefined();
}
const pid = if (arguments.len == 2) arguments[0].coerce(i32, globalThis) else 0;
const priority = if (arguments.len == 2) arguments[1].coerce(i32, globalThis) else arguments[0].coerce(i32, globalThis);
if (priority < -20 or priority > 19) {
const err = JSC.toTypeError(
JSC.Node.ErrorCode.ERR_OUT_OF_RANGE,
"The value of \"priority\" is out of range. It must be >= -20 && <= 19",
.{},
globalThis,
);
globalThis.vm().throwError(globalThis, err);
return JSC.JSValue.jsUndefined();
}
const errcode = C.setProcessPriority(pid, priority);
switch (errcode) {
.SRCH => {
const err = JSC.SystemError{
.message = JSC.ZigString.init("A system error occurred: uv_os_setpriority returned ESRCH (no such process)"),
.code = JSC.ZigString.init(@as(string, @tagName(JSC.Node.ErrorCode.ERR_SYSTEM_ERROR))),
//.info = info,
.errno = -3,
.syscall = JSC.ZigString.init("uv_os_setpriority"),
};
globalThis.vm().throwError(globalThis, err.toErrorInstance(globalThis));
return JSC.JSValue.jsUndefined();
},
.ACCES => {
const err = JSC.SystemError{
.message = JSC.ZigString.init("A system error occurred: uv_os_setpriority returned EACCESS (permission denied)"),
.code = JSC.ZigString.init(@as(string, @tagName(JSC.Node.ErrorCode.ERR_SYSTEM_ERROR))),
//.info = info,
.errno = -13,
.syscall = JSC.ZigString.init("uv_os_setpriority"),
};
globalThis.vm().throwError(globalThis, err.toErrorInstance(globalThis));
return JSC.JSValue.jsUndefined();
},
else => {},
}
return JSC.JSValue.jsUndefined();
}
pub fn totalmem(_: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue {
JSC.markBinding(@src());
return JSC.JSValue.jsNumberFromUint64(C.getTotalMemory());
}
pub fn @"type"(globalThis: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue {
JSC.markBinding(@src());
if (comptime Environment.isWindows)
return JSC.ZigString.static("Windows_NT").toValue(globalThis)
else if (comptime Environment.isMac)
return JSC.ZigString.static("Darwin").toValue(globalThis)
else if (comptime Environment.isLinux)
return JSC.ZigString.static("Linux").toValue(globalThis);
return JSC.ZigString.init(Global.os_name).withEncoding().toValueGC(globalThis);
}
pub fn uptime(_: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue {
JSC.markBinding(@src());
return JSC.JSValue.jsNumberFromUint64(C.getSystemUptime());
}
pub fn userInfo(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue {
const result = JSC.JSValue.createEmptyObject(globalThis, 5);
result.put(globalThis, JSC.ZigString.static("homedir"), homedir(globalThis, callframe));
if (comptime Environment.isWindows) {
result.put(globalThis, JSC.ZigString.static("username"), JSC.ZigString.init(bun.getenvZ("USERNAME") orelse "unknown").withEncoding().toValueGC(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().toValueGC(globalThis));
result.put(globalThis, JSC.ZigString.static("shell"), JSC.ZigString.init(bun.getenvZ("SHELL") orelse "unknown").withEncoding().toValueGC(globalThis));
if (comptime Environment.isLinux) {
result.put(globalThis, JSC.ZigString.static("uid"), JSC.JSValue.jsNumber(std.os.linux.getuid()));
result.put(globalThis, JSC.ZigString.static("gid"), JSC.JSValue.jsNumber(std.os.linux.getgid()));
} else {
result.put(globalThis, JSC.ZigString.static("uid"), JSC.JSValue.jsNumber(C.darwin.getuid()));
result.put(globalThis, JSC.ZigString.static("gid"), JSC.JSValue.jsNumber(C.darwin.getgid()));
}
}
return result;
}
pub fn version(globalThis: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue {
JSC.markBinding(@src());
var name_buffer: [std.os.HOST_NAME_MAX]u8 = undefined;
return JSC.ZigString.init(C.getVersion(&name_buffer)).withEncoding().toValueGC(globalThis);
}
inline fn getMachineName() []const u8 {
return switch (@import("builtin").target.cpu.arch) {
.arm => "arm",
.aarch64 => "arm64",
.mips => "mips",
.mips64 => "mips64",
.powerpc64 => "ppc64",
.powerpc64le => "ppc64le",
.s390x => "s390x",
.x86 => "i386",
.x86_64 => "x86_64",
else => "unknown",
};
}
pub fn machine(globalThis: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue {
JSC.markBinding(@src());
return JSC.ZigString.static(comptime getMachineName()).toValue(globalThis);
}
};
/// 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 std.debug.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;
}
test "netmaskToCIDRSuffix" {
const ipv4_tests = .{
.{ "255.255.255.255", 32 },
.{ "255.255.255.254", 31 },
.{ "255.255.255.252", 30 },
.{ "255.255.255.128", 25 },
.{ "255.255.255.0", 24 },
.{ "255.255.128.0", 17 },
.{ "255.255.0.0", 16 },
.{ "255.128.0.0", 9 },
.{ "255.0.0.0", 8 },
.{ "224.0.0.0", 3 },
.{ "192.0.0.0", 2 },
.{ "128.0.0.0", 1 },
.{ "0.0.0.0", 0 },
// invalid masks
.{ "255.0.0.255", null },
.{ "128.0.0.255", null },
.{ "128.0.0.1", null },
};
inline for (ipv4_tests) |t| {
const addr = try std.net.Address.parseIp4(t[0], 0);
try std.testing.expectEqual(@as(?u8, t[1]), netmaskToCIDRSuffix(addr.in.sa.addr));
}
const ipv6_tests = .{
.{ "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 128 },
.{ "ffff:ffff:ffff:ffff::", 64 },
.{ "::", 0 },
// invalid masks
.{ "ff00:1::", null },
.{ "0:1::", null },
};
inline for (ipv6_tests) |t| {
const addr = try std.net.Address.parseIp6(t[0], 0);
const bits = @bitCast(u128, addr.in6.sa.addr);
try std.testing.expectEqual(@as(?u8, t[1]), netmaskToCIDRSuffix(bits));
}
}