move random number gen
This commit is contained in:
115
scripts/crypto.c
115
scripts/crypto.c
@@ -5,70 +5,6 @@
|
||||
|
||||
#include "monocypher.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#if defined(_WIN32)
|
||||
// ------- Windows: use BCryptGenRandom -------
|
||||
#include <windows.h>
|
||||
#include <bcrypt.h>
|
||||
|
||||
int randombytes(void *buf, size_t n) {
|
||||
NTSTATUS status = BCryptGenRandom(NULL, (PUCHAR)buf, (ULONG)n, BCRYPT_USE_SYSTEM_PREFERRED_RNG);
|
||||
return (status == 0) ? 0 : -1;
|
||||
}
|
||||
|
||||
#elif defined(__linux__)
|
||||
// ------- Linux: try getrandom, fall back to /dev/urandom -------
|
||||
#include <unistd.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
|
||||
// If we have a new enough libc and kernel, getrandom is available.
|
||||
// Otherwise, we’ll do a /dev/urandom fallback.
|
||||
#include <sys/stat.h>
|
||||
|
||||
static int randombytes_fallback(void *buf, size_t n) {
|
||||
int fd = open("/dev/urandom", O_RDONLY);
|
||||
if (fd < 0) return -1;
|
||||
ssize_t r = read(fd, buf, n);
|
||||
close(fd);
|
||||
return (r == (ssize_t)n) ? 0 : -1;
|
||||
}
|
||||
|
||||
int randombytes(void *buf, size_t n) {
|
||||
#ifdef SYS_getrandom
|
||||
// Try getrandom(2) if available
|
||||
ssize_t ret = syscall(SYS_getrandom, buf, n, 0);
|
||||
if (ret < 0) {
|
||||
// If getrandom is not supported or fails, fall back
|
||||
if (errno == ENOSYS) {
|
||||
return randombytes_fallback(buf, n);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
return (ret == (ssize_t)n) ? 0 : -1;
|
||||
#else
|
||||
// getrandom not available, just fallback
|
||||
return randombytes_fallback(buf, n);
|
||||
#endif
|
||||
}
|
||||
|
||||
#else
|
||||
// ------- Other Unix: read from /dev/urandom -------
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int randombytes(void *buf, size_t n) {
|
||||
int fd = open("/dev/urandom", O_RDONLY);
|
||||
if (fd < 0) return -1;
|
||||
ssize_t r = read(fd, buf, n);
|
||||
close(fd);
|
||||
return (r == (ssize_t)n) ? 0 : -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline void to_hex(const uint8_t *in, size_t in_len, char *out)
|
||||
{
|
||||
static const char hexchars[] = "0123456789abcdef";
|
||||
@@ -134,7 +70,27 @@ JSValue js_crypto_keypair(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||||
uint8_t public[32];
|
||||
uint8_t private[32];
|
||||
|
||||
randombytes(private,32);
|
||||
JSValue global = JS_GetGlobalObject(js);
|
||||
JSValue os = JS_GetPropertyStr(js, global, "os");
|
||||
JSValue random_blob = JS_GetPropertyStr(js, os, "random_blob");
|
||||
JSValue size_val = JS_NewInt32(js, 32);
|
||||
JSValue blob = JS_Call(js, random_blob, os, 1, &size_val);
|
||||
size_t len;
|
||||
uint8_t *data = js_get_blob_data(js, &len, blob);
|
||||
if (!data || len != 32) {
|
||||
JS_FreeValue(js, blob);
|
||||
JS_FreeValue(js, size_val);
|
||||
JS_FreeValue(js, random_blob);
|
||||
JS_FreeValue(js, os);
|
||||
JS_FreeValue(js, global);
|
||||
return JS_ThrowInternalError(js, "failed to get random bytes");
|
||||
}
|
||||
memcpy(private, data, 32);
|
||||
JS_FreeValue(js, blob);
|
||||
JS_FreeValue(js, size_val);
|
||||
JS_FreeValue(js, random_blob);
|
||||
JS_FreeValue(js, os);
|
||||
JS_FreeValue(js, global);
|
||||
|
||||
private[0] &= 248;
|
||||
private[31] &= 127;
|
||||
@@ -181,33 +137,6 @@ JSValue js_crypto_shared(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||||
return crypto2js(js, shared);
|
||||
}
|
||||
|
||||
JSValue js_crypto_random(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||||
{
|
||||
// 1) Pull 64 bits of cryptographically secure randomness
|
||||
uint64_t r;
|
||||
if (randombytes(&r, sizeof(r)) != 0) {
|
||||
// If something fails (extremely rare), throw an error
|
||||
return JS_ThrowInternalError(js, "crypto.random: unable to get random bytes");
|
||||
}
|
||||
|
||||
// 2) Convert r to a double in the range [0,1).
|
||||
// We divide by (UINT64_MAX + 1.0) to ensure we never produce exactly 1.0.
|
||||
double val = (double)r / ((double)UINT64_MAX + 1.0);
|
||||
|
||||
// 3) Return that as a JavaScript number
|
||||
return JS_NewFloat64(js, val);
|
||||
}
|
||||
|
||||
JSValue js_crypto_random_fit(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||||
{
|
||||
int32_t r;
|
||||
if (randombytes(&r, sizeof(r)) != 0) {
|
||||
return JS_ThrowInternalError(js, "crypto.random: unable to get random bytes");
|
||||
}
|
||||
|
||||
return JS_NewInt32(js, r);
|
||||
}
|
||||
|
||||
JSValue js_crypto_hash(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||||
{
|
||||
if (argc < 1)
|
||||
@@ -245,8 +174,6 @@ JSValue js_crypto_hash(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||||
static const JSCFunctionListEntry js_crypto_funcs[] = {
|
||||
JS_CFUNC_DEF("keypair", 0, js_crypto_keypair),
|
||||
JS_CFUNC_DEF("shared", 1, js_crypto_shared),
|
||||
JS_CFUNC_DEF("random", 0, js_crypto_random),
|
||||
JS_CFUNC_DEF("random_fit", 0, js_crypto_random_fit),
|
||||
JS_CFUNC_DEF("hash", 2, js_crypto_hash),
|
||||
};
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ var SYSYM = '__SYSTEM__'
|
||||
// Get hidden modules from cell.hidden before stripping it
|
||||
var hidden = cell.hidden
|
||||
|
||||
var os = hidden.os;
|
||||
var use_dyn = hidden.use_dyn
|
||||
var dylib_ext = hidden.dylib_ext
|
||||
var load_internal = hidden.load_internal
|
||||
@@ -54,8 +55,6 @@ function get_import_dl(name) {
|
||||
return null
|
||||
}
|
||||
|
||||
// tries to load a C symbol from either a dynamic library or within the executable
|
||||
// an import 'debug/profile' can either be in a dynamic library debug.so or within the executable
|
||||
function get_c_symbol(name) {
|
||||
var dl = get_import_dl(name)
|
||||
var symname = `js_${name.replace('/', '_')}_use`
|
||||
@@ -96,7 +95,7 @@ globalThis.log = {}
|
||||
log.console = function(msg)
|
||||
{
|
||||
var caller = caller_data(1)
|
||||
console_mod.print(console_rec(caller.line, caller.file, msg))
|
||||
os.print(console_rec(caller.line, caller.file, msg))
|
||||
}
|
||||
|
||||
log.error = function(msg = new Error())
|
||||
@@ -106,7 +105,7 @@ log.error = function(msg = new Error())
|
||||
if (msg instanceof Error)
|
||||
msg = msg.name + ": " + msg.message + "\n" + msg.stack
|
||||
|
||||
console_mod.print(console_rec(caller.line,caller.file,msg))
|
||||
os.print(console_rec(caller.line,caller.file,msg))
|
||||
}
|
||||
|
||||
log.system = function(msg) {
|
||||
@@ -121,15 +120,6 @@ if (!fd.stat('.cell').isDirectory) {
|
||||
os.exit(1);
|
||||
}
|
||||
|
||||
function is_file(path) {
|
||||
try {
|
||||
var st = fd.stat(path)
|
||||
return !!(st && st.isFile)
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function write_file(path, blob) {
|
||||
var fd_handle = fd.open(path, 'w')
|
||||
fd.write(fd_handle, blob)
|
||||
@@ -216,8 +206,13 @@ function get_package_from_path(path) {
|
||||
// This will be set after shop.load_config() is called
|
||||
var config = null
|
||||
|
||||
// Unified path resolution function
|
||||
// Unified path resolution function
|
||||
function resolve_path(requested, pkg_context, ext) {
|
||||
|
||||
|
||||
if (requested.endsWith(ext)) ext = ''
|
||||
|
||||
var dependencies = (config && config.dependencies) ? config.dependencies : {}
|
||||
|
||||
// Helper to check file existence and return result object
|
||||
@@ -232,14 +227,14 @@ function resolve_path(requested, pkg_context, ext) {
|
||||
return null
|
||||
} catch (e) { return null }
|
||||
}
|
||||
if (is_file(path)) {
|
||||
if (fd.is_file(path)) {
|
||||
// log.console("check: found file: " + path)
|
||||
return { path: path, package_name: pkg, isCore: false }
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// Step 1: current package
|
||||
// Step 1: current package (Local)
|
||||
if (pkg_context) {
|
||||
var pkg_path = '.cell/modules/' + pkg_context + '/' + requested + ext
|
||||
var res = check(pkg_path, pkg_context, false)
|
||||
@@ -253,6 +248,7 @@ function resolve_path(requested, pkg_context, ext) {
|
||||
if (res) return res
|
||||
}
|
||||
} else {
|
||||
// Top-level local
|
||||
var project_path = requested + ext
|
||||
var res = check(project_path, null, false)
|
||||
if (res) return res
|
||||
@@ -275,6 +271,12 @@ function resolve_path(requested, pkg_context, ext) {
|
||||
var res = check(dep_path, pkg_alias, false)
|
||||
if (res) return res
|
||||
}
|
||||
|
||||
// Also check if it's just a module in .cell/modules even if not in dependencies (implicit)
|
||||
var implicit_path = '.cell/modules/' + requested + ext
|
||||
var res = check(implicit_path, pkg_alias, false)
|
||||
if (res) return res
|
||||
|
||||
} else {
|
||||
// Check replace directives for simple names
|
||||
if (config && config.replace && config.replace[requested]) {
|
||||
@@ -285,14 +287,23 @@ function resolve_path(requested, pkg_context, ext) {
|
||||
}
|
||||
// Check dependencies for simple names
|
||||
for (var alias in dependencies) {
|
||||
var dep_simple = '.cell/modules/' + alias + '/' + requested + ext
|
||||
var res = check(dep_simple, alias, false)
|
||||
if (res) return res
|
||||
if (alias == requested) {
|
||||
var dep_simple = '.cell/modules/' + alias + '/' + requested + ext
|
||||
var res = check(dep_simple, alias, false)
|
||||
if (res) return res
|
||||
}
|
||||
}
|
||||
|
||||
// Implicit check
|
||||
var implicit_path = '.cell/modules/' + requested + '/' + requested + ext
|
||||
var res = check(implicit_path, requested, false)
|
||||
if (res) return res
|
||||
}
|
||||
|
||||
// Step 3: core
|
||||
return check(requested + ext, null, true)
|
||||
var core_res = check(requested + ext, null, true)
|
||||
if (core_res) log.console(`resolve_path: found core ${requested}`)
|
||||
return core_res
|
||||
}
|
||||
|
||||
function get_compiled_path(resolved) {
|
||||
@@ -315,197 +326,215 @@ function get_compiled_path(resolved) {
|
||||
}
|
||||
}
|
||||
|
||||
globalThis.use = function use(file, ...args) {
|
||||
/* Package-aware module resolution:
|
||||
1. Check local package (cwd, ie '.')
|
||||
2. Check declared dependencies (from cell.toml [dependencies])
|
||||
3. Check core_qop (standard library)
|
||||
var open_dl = {}
|
||||
|
||||
There's also the possibility of native C code;
|
||||
there may be, in a package, a .so/.dll/.dylib
|
||||
that can be loaded. If that exists, as well as a .cm file, the
|
||||
.so/.dll/.dylib is loaded and the .cm file is ran with the
|
||||
loaded module as this.
|
||||
function get_c_symbol(requested, pkg_context) {
|
||||
// Construct symbol name: js_x_y_z_use
|
||||
var symname = `js_${requested.replace(/\//g, '_')}_use`
|
||||
|
||||
for embedded modules, it's the same, but in the cell runtime, so no .so/.dll/.dylib
|
||||
is loaded.
|
||||
*/
|
||||
var requested = file
|
||||
|
||||
// Check embedded modules first (these are always available)
|
||||
var embed_mod = use_embed(requested)
|
||||
if (!embed_mod) embed_mod = load_internal(requested)
|
||||
|
||||
// Check for dynamic library
|
||||
var dyn_mod = null
|
||||
var dyn_path = resolve_path(requested, current_package, dylib_ext)
|
||||
if (dyn_path) {
|
||||
try {
|
||||
if (!dyn_path.isCore) {
|
||||
dyn_mod = use_dyn(dyn_path.path)
|
||||
log.console("use: loaded dynamic library " + dyn_path.path)
|
||||
// 1. Check Local
|
||||
if (!pkg_context) {
|
||||
// Check local dylib: .cell/local/local.dylib
|
||||
var local_dl_path = '.cell/local/local' + dylib_ext
|
||||
if (fd.is_file(local_dl_path)) {
|
||||
var dl = open_dl['local']
|
||||
if (!dl) {
|
||||
try {
|
||||
dl = os.dylib_open(local_dl_path)
|
||||
open_dl['local'] = dl
|
||||
} catch(e) {}
|
||||
}
|
||||
if (dl) {
|
||||
try {
|
||||
var sym = os.dylib_symbol(dl, symname)
|
||||
if (sym) return sym
|
||||
} catch(e) {}
|
||||
}
|
||||
} catch (e) {
|
||||
log.console(`use: failed to load dynamic library ${dyn_path.path}: ${e}`)
|
||||
}
|
||||
}
|
||||
|
||||
// If we have a dynamic module, use it as the embedded module (context)
|
||||
if (dyn_mod) {
|
||||
embed_mod = dyn_mod
|
||||
// 2. Check Modules
|
||||
// Determine package name from requested path
|
||||
var pkg_name = null
|
||||
if (pkg_context) pkg_name = pkg_context
|
||||
else if (requested.includes('/')) pkg_name = requested.split('/')[0]
|
||||
else pkg_name = requested
|
||||
|
||||
if (pkg_name) {
|
||||
var mod_dl_path = `.cell/modules/${pkg_name}/${pkg_name}${dylib_ext}`
|
||||
if (fd.is_file(mod_dl_path)) {
|
||||
var dl = open_dl[pkg_name]
|
||||
if (!dl) {
|
||||
try {
|
||||
dl = os.dylib_open(mod_dl_path)
|
||||
open_dl[pkg_name] = dl
|
||||
} catch(e) {}
|
||||
}
|
||||
if (dl) {
|
||||
try {
|
||||
var sym = os.dylib_symbol(dl, symname)
|
||||
if (sym) return sym
|
||||
} catch(e) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve the module path with package awareness
|
||||
var resolved = resolve_path(requested, current_package, MOD_EXT)
|
||||
return null
|
||||
}
|
||||
|
||||
// Generate cache key based on resolution
|
||||
globalThis.use = function use(file, ...args) {
|
||||
var requested = file
|
||||
log.console(`use: ${file}`)
|
||||
|
||||
// 1. Check Local
|
||||
// Check for C symbol locally
|
||||
var c_mod = null
|
||||
if (!current_package) {
|
||||
c_mod = get_c_symbol(requested, null)
|
||||
}
|
||||
|
||||
var resolved = null
|
||||
|
||||
// Check for local file
|
||||
if (!current_package) {
|
||||
resolved = resolve_path(requested, null, MOD_EXT)
|
||||
// If we found a core module but we were looking for local, ignore it for now
|
||||
if (resolved && resolved.isCore) resolved = null
|
||||
}
|
||||
|
||||
// If we found something locally (C or Script), stop looking elsewhere
|
||||
if (c_mod || resolved) {
|
||||
// Proceed with local
|
||||
} else {
|
||||
// 2. Check Modules
|
||||
// Check for C symbol in modules
|
||||
// We need to guess the package name if not in a package context
|
||||
var pkg_guess = requested.split('/')[0]
|
||||
c_mod = get_c_symbol(requested, pkg_guess)
|
||||
|
||||
if (!c_mod) {
|
||||
// Check for module file
|
||||
resolved = resolve_path(requested, null, MOD_EXT) // resolve_path handles module lookup if we pass null context but path has /
|
||||
if (resolved && resolved.isCore) resolved = null
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Check Core
|
||||
if (!c_mod && !resolved) {
|
||||
var embed_mod = use_embed(requested)
|
||||
if (embed_mod) c_mod = embed_mod
|
||||
|
||||
var res = resolve_path(requested, null, MOD_EXT)
|
||||
if (res && res.isCore) resolved = res
|
||||
}
|
||||
|
||||
// If still nothing
|
||||
if (!c_mod && !resolved) {
|
||||
// Try load_internal as last resort for core C
|
||||
c_mod = load_internal(`js_${requested}_use`)
|
||||
}
|
||||
|
||||
if (!c_mod && !resolved)
|
||||
throw new Error(`Module ${file} could not be found (package context: ${current_package || 'none'})`)
|
||||
|
||||
// Generate cache key
|
||||
var cache_key = resolved
|
||||
? (resolved.isCore ? 'core:' + resolved.path : resolved.path)
|
||||
: (dyn_path ? dyn_path.path : requested)
|
||||
: (c_mod ? 'c:' + requested : requested)
|
||||
|
||||
if (use_cache[cache_key]) return use_cache[cache_key]
|
||||
|
||||
log.console(`cache miss: ${cache_key}`)
|
||||
// log.console(`cache miss: ${cache_key}`)
|
||||
|
||||
if (!resolved && !embed_mod)
|
||||
throw new Error(`Module ${file} could not be found (package context: ${current_package || 'none'})`)
|
||||
|
||||
// If only embedded/dynamic module exists, return it
|
||||
if (!resolved && embed_mod) {
|
||||
use_cache[cache_key] = embed_mod
|
||||
return embed_mod
|
||||
// If we have a C module, use it as context
|
||||
var context = {}
|
||||
if (c_mod) {
|
||||
context = c_mod
|
||||
log.console("use: using c_mod as context")
|
||||
}
|
||||
|
||||
var path = resolved.path
|
||||
var isCore = resolved.isCore
|
||||
var module_package = resolved.package_name
|
||||
// If we have a script, run it
|
||||
var ret = c_mod
|
||||
|
||||
if (resolved) {
|
||||
log.console("use: running script " + resolved.path)
|
||||
var path = resolved.path
|
||||
var isCore = resolved.isCore
|
||||
var module_package = resolved.package_name
|
||||
|
||||
// Check for circular dependencies
|
||||
if (path && loadingStack.includes(path)) {
|
||||
let cycleIndex = loadingStack.indexOf(path)
|
||||
let cyclePath = loadingStack.slice(cycleIndex).concat(path)
|
||||
throw new Error(`Circular dependency: ${cyclePath.join(" -> ")}`)
|
||||
}
|
||||
|
||||
inProgress[path] = true
|
||||
loadingStack.push(path)
|
||||
|
||||
var prev_package = current_package
|
||||
current_package = module_package
|
||||
|
||||
// If core module, load it
|
||||
if (isCore) {
|
||||
var ret = null
|
||||
try {
|
||||
// Try to load compiled version first
|
||||
var compiledPath = get_compiled_path(resolved)
|
||||
var useCompiled = false
|
||||
mkdir_p(compiledPath.substring(0, compiledPath.lastIndexOf('/')))
|
||||
|
||||
// Always compile from source - never use precompiled for core modules
|
||||
// if (is_file(compiledPath)) {
|
||||
var useCompiled = false
|
||||
var srcStat = fd.stat(path)
|
||||
var compiledStat = fd.stat(compiledPath)
|
||||
|
||||
// Always compile from source - never use precompiled for regular modules
|
||||
// if (srcStat && srcStat.isFile && compiledStat && compiledStat.isFile && compiledStat.mtime > srcStat.mtime) {
|
||||
// useCompiled = true
|
||||
// }
|
||||
|
||||
var fn
|
||||
var mod_name = path.substring(path.lastIndexOf('/') + 1, path.lastIndexOf('.'))
|
||||
|
||||
if (useCompiled) {
|
||||
var compiledBlob = fd.slurp(compiledPath)
|
||||
fn = js.compile_unblob(compiledBlob)
|
||||
fn = js.eval_compile(fn)
|
||||
log.console("use: using compiled core version " + compiledPath)
|
||||
log.console("use: using compiled version " + compiledPath)
|
||||
} else {
|
||||
var script = utf8.decode(core_qop.read(path))
|
||||
var mod_script = `(function setup_${requested.replace(/[^a-zA-Z0-9_]/g, '_')}_module(arg, $_){${script};})`
|
||||
fn = js.compile(path, mod_script)
|
||||
if (isCore) {
|
||||
var script = utf8.decode(core_qop.read(path))
|
||||
var mod_script = `(function setup_${requested.replace(/[^a-zA-Z0-9_]/g, '_')}_module(arg, $_){${script};})`
|
||||
fn = js.compile(path, mod_script)
|
||||
|
||||
// Save compiled version
|
||||
mkdir_p(compiledPath.substring(0, compiledPath.lastIndexOf('/')))
|
||||
var compiled = js.compile_blob(fn)
|
||||
write_file(compiledPath, compiled)
|
||||
// Save compiled version
|
||||
mkdir_p(compiledPath.substring(0, compiledPath.lastIndexOf('/')))
|
||||
var compiled = js.compile_blob(fn)
|
||||
write_file(compiledPath, compiled)
|
||||
|
||||
fn = js.eval_compile(fn)
|
||||
fn = js.eval_compile(fn)
|
||||
} else {
|
||||
var script = utf8.decode(fd.slurp(path))
|
||||
var mod_script = `(function setup_${mod_name}_module(arg, $_){${script};})`
|
||||
fn = js.compile(path, mod_script)
|
||||
|
||||
// Save compiled version to .cell directory
|
||||
var compiled = js.compile_blob(fn)
|
||||
write_file(compiledPath, compiled)
|
||||
|
||||
fn = js.eval_compile(fn)
|
||||
}
|
||||
}
|
||||
|
||||
var context = embed_mod ? embed_mod : {}
|
||||
ret = fn.call(context, args, $_)
|
||||
} catch (e) {
|
||||
// Script component doesn't exist or failed, fall back to embedded module
|
||||
// log.console("use: core module " + path + " failed to load script (using embedded if avail)")
|
||||
}
|
||||
log.console("use: script returned " + (typeof ret))
|
||||
|
||||
if (!ret && embed_mod) {
|
||||
ret = embed_mod
|
||||
} else if (!ret) {
|
||||
throw new Error(`Use must be used with a module, but ${path} doesn't return a value`)
|
||||
}
|
||||
|
||||
use_cache[cache_key] = ret
|
||||
return ret
|
||||
current_package = prev_package
|
||||
loadingStack.pop()
|
||||
delete inProgress[path]
|
||||
}
|
||||
|
||||
// Check for circular dependencies using the resolved path
|
||||
if (path && loadingStack.includes(path)) {
|
||||
let cycleIndex = loadingStack.indexOf(path)
|
||||
let cyclePath = loadingStack.slice(cycleIndex).concat(path)
|
||||
|
||||
throw new Error(
|
||||
`Circular dependency detected while loading "${file}".\n` +
|
||||
`Module chain: ${loadingStack.join(" -> ")}\n` +
|
||||
`Cycle specifically: ${cyclePath.join(" -> ")}`
|
||||
)
|
||||
if (!ret && c_mod) {
|
||||
log.console("use: script returned nothing, using c_mod")
|
||||
ret = c_mod
|
||||
}
|
||||
else if (!ret) throw new Error(`Use must be used with a module, but ${file} doesn't return a value`)
|
||||
|
||||
log.console("use: loading file " + path + " (package: " + (module_package || 'local') + ")")
|
||||
inProgress[path] = true
|
||||
loadingStack.push(path)
|
||||
|
||||
// Save and set package context for nested use() calls
|
||||
var prev_package = current_package
|
||||
current_package = module_package
|
||||
|
||||
// Determine the compiled file path in .cell directory
|
||||
var compiledPath = get_compiled_path(resolved)
|
||||
|
||||
mkdir_p(compiledPath.substring(0, compiledPath.lastIndexOf('/')))
|
||||
|
||||
// Check if compiled version exists and is newer than source
|
||||
var useCompiled = false
|
||||
var srcStat = fd.stat(path)
|
||||
var compiledStat = fd.stat(compiledPath)
|
||||
|
||||
// Always compile from source - never use precompiled for regular modules
|
||||
// if (srcStat && srcStat.isFile && compiledStat && compiledStat.isFile && compiledStat.mtime > srcStat.mtime) {
|
||||
// useCompiled = true
|
||||
// }
|
||||
|
||||
var fn
|
||||
var mod_name = path.substring(path.lastIndexOf('/') + 1, path.lastIndexOf('.'))
|
||||
|
||||
if (useCompiled) {
|
||||
var compiledBlob = fd.slurp(compiledPath)
|
||||
fn = js.compile_unblob(compiledBlob)
|
||||
fn = js.eval_compile(fn)
|
||||
log.console("use: using compiled version " + compiledPath)
|
||||
} else {
|
||||
var script = utf8.decode(fd.slurp(path))
|
||||
var mod_script = `(function setup_${mod_name}_module(arg, $_){${script};})`
|
||||
fn = js.compile(path, mod_script)
|
||||
|
||||
// Save compiled version to .cell directory
|
||||
var compiled = js.compile_blob(fn)
|
||||
write_file(compiledPath, compiled)
|
||||
|
||||
fn = js.eval_compile(fn)
|
||||
}
|
||||
|
||||
// Create context - if embedded module exists, use it as 'this'
|
||||
var context = embed_mod ? embed_mod : {}
|
||||
|
||||
// Call the script - pass embedded module as 'this' if it exists
|
||||
var ret = fn.call(context, args, $_)
|
||||
|
||||
// Restore previous package context
|
||||
current_package = prev_package
|
||||
|
||||
// If script doesn't return anything, check if we have embedded module
|
||||
if (!ret && embed_mod) {
|
||||
ret = embed_mod
|
||||
} else if (!ret) {
|
||||
throw new Error(`Use must be used with a module, but ${path} doesn't return a value`)
|
||||
}
|
||||
|
||||
loadingStack.pop()
|
||||
delete inProgress[path]
|
||||
|
||||
// Cache the result
|
||||
use_cache[cache_key] = ret
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
@@ -614,7 +643,7 @@ stone.p = function(object)
|
||||
|
||||
function guid(bits = 256)
|
||||
{
|
||||
var guid = new blob(bits, hidden.randi)
|
||||
var guid = new blob(bits, os.random)
|
||||
stone(guid)
|
||||
return text(guid,'h')
|
||||
}
|
||||
@@ -629,10 +658,13 @@ function create_actor(desc = {id:guid()}) {
|
||||
|
||||
var $_ = create_actor()
|
||||
|
||||
$_.random = hidden.rand
|
||||
$_.random = function() {
|
||||
var n = os.random()
|
||||
return n > 100000
|
||||
}
|
||||
$_.random[cell.DOC] = "returns a number between 0 and 1. There is a 50% chance that the result is less than 0.5."
|
||||
|
||||
$_.random_fit = hidden.randi
|
||||
$_.random_fit = os.random
|
||||
|
||||
$_.clock = function(fn) {
|
||||
actor_mod.clock(_ => {
|
||||
@@ -1113,7 +1145,7 @@ var useCompiled = false
|
||||
// Always compile from source - never use precompiled for main program
|
||||
// if (resolved_prog.isCore) {
|
||||
// // For core, we check if we have a compiled version, else we compile it
|
||||
// if (is_file(compiledPath)) {
|
||||
// if (fd.is_file(compiledPath)) {
|
||||
// useCompiled = true
|
||||
// }
|
||||
// } else {
|
||||
|
||||
16
scripts/fd.c
16
scripts/fd.c
@@ -411,6 +411,20 @@ JSC_SCALL(fd_readdir,
|
||||
#endif
|
||||
)
|
||||
|
||||
JSC_SCALL(fd_is_file,
|
||||
struct stat st;
|
||||
if (stat(str, &st) != 0)
|
||||
return JS_NewBool(js, false);
|
||||
return JS_NewBool(js, S_ISREG(st.st_mode));
|
||||
)
|
||||
|
||||
JSC_SCALL(fd_is_dir,
|
||||
struct stat st;
|
||||
if (stat(str, &st) != 0)
|
||||
return JS_NewBool(js, false);
|
||||
return JS_NewBool(js, S_ISDIR(st.st_mode));
|
||||
)
|
||||
|
||||
static const JSCFunctionListEntry js_fd_funcs[] = {
|
||||
MIST_FUNC_DEF(fd, open, 2),
|
||||
MIST_FUNC_DEF(fd, write, 2),
|
||||
@@ -428,6 +442,8 @@ static const JSCFunctionListEntry js_fd_funcs[] = {
|
||||
MIST_FUNC_DEF(fd, stat, 1),
|
||||
MIST_FUNC_DEF(fd, fstat, 1),
|
||||
MIST_FUNC_DEF(fd, readdir, 1),
|
||||
MIST_FUNC_DEF(fd, is_file, 1),
|
||||
MIST_FUNC_DEF(fd, is_dir, 1),
|
||||
};
|
||||
|
||||
JSValue js_fd_use(JSContext *js) {
|
||||
|
||||
114
scripts/os.c
114
scripts/os.c
@@ -13,6 +13,7 @@
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <bcrypt.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <sys/utsname.h>
|
||||
@@ -20,9 +21,14 @@
|
||||
#ifdef __linux__
|
||||
#include <sys/sysinfo.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include <fcntl.h>
|
||||
|
||||
static JSClassID js_dylib_class_id;
|
||||
|
||||
static void js_dylib_finalizer(JSRuntime *rt, JSValue val) {
|
||||
@@ -58,6 +64,8 @@ JSC_CCALL(os_power_state,
|
||||
case SDL_POWERSTATE_NO_BATTERY: statestr = "no battery"; break;
|
||||
case SDL_POWERSTATE_CHARGING: statestr = "charging"; break;
|
||||
case SDL_POWERSTATE_CHARGED: statestr = "charged"; break;
|
||||
case SDL_POWERSTATE_ERROR: statestr = "error"; break;
|
||||
case SDL_POWERSTATE_UNKNOWN: statestr = "unknown"; break;
|
||||
}
|
||||
ret = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js,ret,"state",JS_NewString(js,statestr));
|
||||
@@ -383,6 +391,109 @@ static JSValue js_os_dylib_symbol(JSContext *js, JSValue self, int argc, JSValue
|
||||
return symbol(js);
|
||||
}
|
||||
|
||||
JSC_CCALL(os_print,
|
||||
size_t len;
|
||||
const char *str = JS_ToCStringLen(js, &len, argv[0]);
|
||||
printf("%.*s", (int)len, str);
|
||||
JS_FreeCString(js, str);
|
||||
)
|
||||
|
||||
static JSValue js_os_load_internal(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||||
{
|
||||
void *handle;
|
||||
#ifdef _WIN32
|
||||
handle = GetModuleHandle(NULL);
|
||||
#else
|
||||
handle = dlopen(NULL, RTLD_LAZY);
|
||||
#endif
|
||||
if (argc < 1) {
|
||||
return JS_ThrowTypeError(js, "load_internal requires a symbol name");
|
||||
}
|
||||
|
||||
const char *symbol_name = JS_ToCString(js, argv[0]);
|
||||
if (!symbol_name) {
|
||||
return JS_ThrowTypeError(js, "symbol name must be a string");
|
||||
}
|
||||
|
||||
JSValue (*symbol)(JSContext *js);
|
||||
#if defined(_WIN32)
|
||||
symbol = GetProcAddress((HMODULE)handle, symbol_name);
|
||||
#else
|
||||
symbol = dlsym(handle, symbol_name);
|
||||
#endif
|
||||
|
||||
JS_FreeCString(js, symbol_name);
|
||||
|
||||
if (!symbol) {
|
||||
const char *error_msg = "Symbol not found";
|
||||
#ifndef _WIN32
|
||||
const char *dl_error = dlerror();
|
||||
if (dl_error) {
|
||||
error_msg = dl_error;
|
||||
}
|
||||
#endif
|
||||
return JS_ThrowReferenceError(js, "Failed to get symbol: %s", error_msg);
|
||||
}
|
||||
|
||||
// Return the symbol as a pointer value
|
||||
return symbol(js);
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
// ------- Windows: use BCryptGenRandom -------
|
||||
int randombytes(void *buf, size_t n) {
|
||||
NTSTATUS status = BCryptGenRandom(NULL, (PUCHAR)buf, (ULONG)n, BCRYPT_USE_SYSTEM_PREFERRED_RNG);
|
||||
return (status == 0) ? 0 : -1;
|
||||
}
|
||||
|
||||
#elif defined(__linux__)
|
||||
// ------- Linux: try getrandom, fall back to /dev/urandom -------
|
||||
static int randombytes_fallback(void *buf, size_t n) {
|
||||
int fd = open("/dev/urandom", O_RDONLY);
|
||||
if (fd < 0) return -1;
|
||||
ssize_t r = read(fd, buf, n);
|
||||
close(fd);
|
||||
return (r == (ssize_t)n) ? 0 : -1;
|
||||
}
|
||||
|
||||
int randombytes(void *buf, size_t n) {
|
||||
#ifdef SYS_getrandom
|
||||
// Try getrandom(2) if available
|
||||
ssize_t ret = syscall(SYS_getrandom, buf, n, 0);
|
||||
if (ret < 0) {
|
||||
// If getrandom is not supported or fails, fall back
|
||||
if (errno == ENOSYS) {
|
||||
return randombytes_fallback(buf, n);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
return (ret == (ssize_t)n) ? 0 : -1;
|
||||
#else
|
||||
// getrandom not available, just fallback
|
||||
return randombytes_fallback(buf, n);
|
||||
#endif
|
||||
}
|
||||
|
||||
#else
|
||||
// ------- Other Unix: read from /dev/urandom -------
|
||||
int randombytes(void *buf, size_t n) {
|
||||
int fd = open("/dev/urandom", O_RDONLY);
|
||||
if (fd < 0) return -1;
|
||||
ssize_t r = read(fd, buf, n);
|
||||
close(fd);
|
||||
return (r == (ssize_t)n) ? 0 : -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
JSC_CCALL(os_random,
|
||||
double random_double;
|
||||
uint8_t *buf = (uint8_t *)&random_double;
|
||||
if (randombytes(buf, sizeof(double)) != 0) {
|
||||
return JS_ThrowInternalError(js, "failed to generate random bytes");
|
||||
}
|
||||
return JS_NewFloat64(js, random_double);
|
||||
)
|
||||
|
||||
static const JSCFunctionListEntry js_os_funcs[] = {
|
||||
MIST_FUNC_DEF(os, platform, 0),
|
||||
MIST_FUNC_DEF(os, arch, 0),
|
||||
@@ -400,6 +511,9 @@ static const JSCFunctionListEntry js_os_funcs[] = {
|
||||
MIST_FUNC_DEF(os, exit, 0),
|
||||
MIST_FUNC_DEF(os, dylib_open, 1),
|
||||
MIST_FUNC_DEF(os, dylib_symbol, 2),
|
||||
MIST_FUNC_DEF(os, load_internal, 1),
|
||||
MIST_FUNC_DEF(os, print, 1),
|
||||
MIST_FUNC_DEF(os, random, 0),
|
||||
};
|
||||
|
||||
JSValue js_os_use(JSContext *js) {
|
||||
|
||||
151
source/jsffi.c
151
source/jsffi.c
@@ -157,33 +157,6 @@ static uint32_t xorshift32(){
|
||||
return rng_state = x;
|
||||
}
|
||||
|
||||
JSC_CCALL(os_guid,
|
||||
uint8_t data[16];
|
||||
for(int i = 0; i < 4; i++){
|
||||
uint32_t v = xorshift32();
|
||||
memcpy(&data[i*4], &v, 4);
|
||||
}
|
||||
|
||||
static const char hex[] = "0123456789abcdef";
|
||||
char buf[32];
|
||||
for(int i = 0; i < 16; i++){
|
||||
uint8_t b = data[i];
|
||||
buf[i*2 ] = hex[b >> 4];
|
||||
buf[i*2 + 1] = hex[b & 0x0f];
|
||||
}
|
||||
|
||||
return JS_NewStringLen(js, buf, 32);
|
||||
)
|
||||
|
||||
JSC_SCALL(console_print, printf("%s", str); )
|
||||
|
||||
static const JSCFunctionListEntry js_console_funcs[] = {
|
||||
MIST_FUNC_DEF(console,print,1),
|
||||
};
|
||||
|
||||
|
||||
JSC_SCALL(os_system, ret = number2js(js,system(str)); )
|
||||
|
||||
JSC_CCALL(os_rand,
|
||||
MTRand *mrand = &((cell_rt*)JS_GetContextOpaque(js))->mrand;
|
||||
return number2js(js, genRand(mrand));
|
||||
@@ -199,124 +172,6 @@ JSC_CCALL(os_srand,
|
||||
m_seedRand(mrand, js2number(js,argv[0]));
|
||||
)
|
||||
|
||||
static const JSCFunctionListEntry js_util_funcs[] = {
|
||||
MIST_FUNC_DEF(os, guid, 0),
|
||||
};
|
||||
|
||||
static void *get_main_module_handle() {
|
||||
#if defined(_WIN32)
|
||||
return GetModuleHandle(NULL);
|
||||
#else
|
||||
return dlopen(NULL, RTLD_LAZY);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void *get_symbol(void *handle, const char *name) {
|
||||
#if defined(_WIN32)
|
||||
return (void*)GetProcAddress((HMODULE)handle, name);
|
||||
#else
|
||||
return dlsym(handle, name);
|
||||
#endif
|
||||
}
|
||||
|
||||
JSC_CCALL(os_use_dyn,
|
||||
const char *path = JS_ToCString(js, argv[0]);
|
||||
if (!path) return JS_ThrowTypeError(js, "path must be a string");
|
||||
|
||||
// 1. Load the library
|
||||
#if defined(_WIN32)
|
||||
HMODULE handle = LoadLibraryA(path);
|
||||
#else
|
||||
void *handle = dlopen(path, RTLD_NOW | RTLD_LOCAL);
|
||||
#endif
|
||||
|
||||
if (!handle) {
|
||||
JS_FreeCString(js, path);
|
||||
#if defined(_WIN32)
|
||||
return JS_ThrowReferenceError(js, "Could not load library");
|
||||
#else
|
||||
return JS_ThrowReferenceError(js, "Could not load library: %s", dlerror());
|
||||
#endif
|
||||
}
|
||||
|
||||
// 2. Construct symbol name: js_<basename>_use
|
||||
// Extract basename from path
|
||||
const char *base = strrchr(path, '/');
|
||||
#if defined(_WIN32)
|
||||
const char *base2 = strrchr(path, '\\');
|
||||
if (base2 > base) base = base2;
|
||||
#endif
|
||||
|
||||
if (base) base++; // Skip the slash
|
||||
else base = path; // No slash, use whole path
|
||||
|
||||
char name_buf[256];
|
||||
const char *dot = strrchr(base, '.');
|
||||
size_t len = dot ? (size_t)(dot - base) : strlen(base);
|
||||
if (len > 100) len = 100; // Safety cap
|
||||
|
||||
char clean_base[128];
|
||||
memcpy(clean_base, base, len);
|
||||
clean_base[len] = '\0';
|
||||
|
||||
snprintf(name_buf, sizeof(name_buf), "js_%s_use", clean_base);
|
||||
|
||||
// 3. Get the symbol
|
||||
typedef JSValue (*init_fn)(JSContext*);
|
||||
init_fn fn = (init_fn)get_symbol(handle, name_buf);
|
||||
|
||||
JS_FreeCString(js, path);
|
||||
|
||||
if (!fn) {
|
||||
// Try without stripping extension? No, standard is usually without.
|
||||
// Maybe try "js_main_use"? No.
|
||||
// Let's stick to the plan.
|
||||
#if defined(_WIN32)
|
||||
FreeLibrary(handle);
|
||||
#else
|
||||
dlclose(handle);
|
||||
#endif
|
||||
return JS_ThrowReferenceError(js, "Could not find entry point %s in library", name_buf);
|
||||
}
|
||||
|
||||
// 4. Call the function
|
||||
return fn(js);
|
||||
)
|
||||
|
||||
JSC_SCALL(os_load_internal,
|
||||
void *handle = get_main_module_handle();
|
||||
if (!handle) {
|
||||
return JS_ThrowReferenceError(js, "Could not get main module handle");
|
||||
}
|
||||
|
||||
JSValue (*js_use)(JSContext*) = get_symbol(handle, str);
|
||||
|
||||
if (!js_use) {
|
||||
// Try without "js_" prefix or other variations if needed, but standard is js_<name>_use
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
ret = js_use(js);
|
||||
)
|
||||
|
||||
JSValue js_util_use(JSContext *js) {
|
||||
JSValue mod = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js,mod,js_util_funcs,countof(js_util_funcs));
|
||||
return mod;
|
||||
}
|
||||
|
||||
|
||||
JSValue js_console_use(JSContext *js) {
|
||||
JSValue mod = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js,mod,js_console_funcs,countof(js_console_funcs));
|
||||
return mod;
|
||||
}
|
||||
|
||||
JSC_CCALL(os_value_id,
|
||||
JS_SetPropertyStr(js, argv[0], "id", number2js(js, (uintptr_t)JS_VALUE_GET_PTR(argv[0])));
|
||||
return argv[0];
|
||||
)
|
||||
|
||||
void ffi_load(JSContext *js)
|
||||
{
|
||||
cell_rt *rt = JS_GetContextOpaque(js);
|
||||
@@ -336,12 +191,6 @@ void ffi_load(JSContext *js)
|
||||
|
||||
JSValue hidden_fn = JS_NewObject(js);
|
||||
|
||||
// Add functions that should only be accessible to engine.js
|
||||
JS_SetPropertyStr(js, hidden_fn, "load_internal", JS_NewCFunction(js, js_os_load_internal, "load_internal", 1));
|
||||
JS_SetPropertyStr(js, hidden_fn, "rand", JS_NewCFunction(js, js_os_rand, "rand", 0));
|
||||
JS_SetPropertyStr(js, hidden_fn, "randi", JS_NewCFunction(js, js_os_randi, "randi", 0));
|
||||
JS_SetPropertyStr(js, hidden_fn, "srand", JS_NewCFunction(js, js_os_srand, "srand", 1));
|
||||
JS_SetPropertyStr(js, hidden_fn, "use_dyn", JS_NewCFunction(js, js_os_use_dyn, "use_dyn", 1));
|
||||
|
||||
#if defined(_WIN32)
|
||||
JS_SetPropertyStr(js, hidden_fn, "dylib_ext", JS_NewString(js, ".dll"));
|
||||
|
||||
Reference in New Issue
Block a user