koffi
Version:
Fast and simple C FFI (foreign function interface) for Node.js
501 lines (496 loc) • 17.6 kB
JavaScript
var __getOwnPropNames = Object.getOwnPropertyNames;
var __commonJS = (cb, mod3) => function __require() {
return mod3 || (0, cb[__getOwnPropNames(cb)[0]])((mod3 = { exports: {} }).exports, mod3), mod3.exports;
};
// ../../bin/Koffi/package/src/cnoke/src/tools.js
var require_tools = __commonJS({
"../../bin/Koffi/package/src/cnoke/src/tools.js"(exports2, module2) {
"use strict";
var crypto = require("crypto");
var fs2 = require("fs");
var http = require("https");
var path2 = require("path");
var zlib = require("zlib");
async function download_http(url, dest) {
console.log(">> Downloading " + url);
let [tmp_name, file] = open_temporary_stream(dest);
try {
await new Promise((resolve, reject) => {
let request = http.get(url, (response) => {
if (response.statusCode != 200) {
let err = new Error(`Download failed: ${response.statusMessage} [${response.statusCode}]`);
err.code = response.statusCode;
reject(err);
return;
}
response.pipe(file);
file.on("finish", () => file.close(() => {
try {
fs2.renameSync(file.path, dest);
} catch (err) {
if (!fs2.existsSync(dest))
reject(err);
}
resolve();
}));
});
request.on("error", reject);
file.on("error", reject);
});
} catch (err) {
file.close();
try {
fs2.unlinkSync(tmp_name);
} catch (err2) {
if (err2.code != "ENOENT")
throw err2;
}
throw err;
}
}
function open_temporary_stream(prefix) {
let buf = Buffer.allocUnsafe(4);
for (; ; ) {
try {
crypto.randomFillSync(buf);
let suffix = buf.toString("hex").padStart(8, "0");
let filename2 = `${prefix}.${suffix}`;
let file = fs2.createWriteStream(filename2, { flags: "wx", mode: 420 });
return [filename2, file];
} catch (err) {
if (err.code != "EEXIST")
throw err;
}
}
}
function extract_targz(filename2, dest_dir, strip = 0) {
let reader = fs2.createReadStream(filename2).pipe(zlib.createGunzip());
return new Promise((resolve, reject) => {
let header = null;
let extended = {};
reader.on("readable", () => {
try {
for (; ; ) {
if (header == null) {
let buf = reader.read(512);
if (buf == null)
break;
if (!buf[0])
continue;
header = {
filename: buf.toString("utf-8", 0, 100).replace(/\0/g, ""),
mode: parseInt(buf.toString("ascii", 100, 109), 8),
size: parseInt(buf.toString("ascii", 124, 137), 8),
type: String.fromCharCode(buf[156])
};
Object.assign(header, extended);
extended = {};
header.filename = header.filename.replace(/\\/g, "/");
if (!header.filename.length)
throw new Error(`Insecure empty filename inside TAR archive`);
if (path_is_absolute(header.filename[0]))
throw new Error(`Insecure filename starting with / inside TAR archive`);
if (path_has_dotdot(header.filename))
throw new Error(`Insecure filename containing '..' inside TAR archive`);
for (let i = 0; i < strip; i++)
header.filename = header.filename.substr(header.filename.indexOf("/") + 1);
}
let aligned = Math.floor((header.size + 511) / 512) * 512;
let data = header.size ? reader.read(aligned) : null;
if (data == null) {
if (header.size)
break;
data = Buffer.alloc(0);
}
data = data.subarray(0, header.size);
if (header.type == "0" || header.type == "7") {
let filename3 = dest_dir + "/" + header.filename;
let dirname = path2.dirname(filename3);
fs2.mkdirSync(dirname, { recursive: true, mode: 493 });
fs2.writeFileSync(filename3, data, { mode: header.mode });
} else if (header.type == "5") {
let filename3 = dest_dir + "/" + header.filename;
fs2.mkdirSync(filename3, { recursive: true, mode: header.mode });
} else if (header.type == "L") {
extended.filename = data.toString("utf-8").replace(/\0/g, "");
} else if (header.type == "x") {
let str = data.toString("utf-8");
try {
while (str.length) {
let matches = str.match(/^([0-9]+) ([a-zA-Z0-9\._]+)=(.*)\n/);
let skip = parseInt(matches[1], 10);
let key = matches[2];
let value = matches[3];
switch (key) {
case "path":
{
extended.filename = value;
}
break;
case "size":
{
extended.size = parseInt(value, 10);
}
break;
}
str = str.substr(skip).trimStart();
}
} catch (err) {
throw new Error("Malformed PAX entry");
}
}
header = null;
}
} catch (err) {
reject(err);
}
});
reader.on("error", reject);
reader.on("end", resolve);
});
}
function path_is_absolute(path3) {
if (process.platform == "win32" && path3.match(/^[a-zA-Z]:/))
path3 = path3.substr(2);
return is_path_separator(path3[0]);
}
function path_has_dotdot(path3) {
let start = 0;
for (; ; ) {
let offset = path3.indexOf("..", start);
if (offset < 0)
break;
start = offset + 2;
if (offset && !is_path_separator(path3[offset - 1]))
continue;
if (offset + 2 < path3.length && !is_path_separator(path3[offset + 2]))
continue;
return true;
}
return false;
}
function is_path_separator(c) {
if (c == "/")
return true;
if (process.platform == "win32" && c == "\\")
return true;
return false;
}
function determine_arch2() {
let arch = process.arch;
if (arch == "riscv32" || arch == "riscv64") {
let buf = read_file_header(process.execPath, 512);
let header = decode_elf_header(buf);
let float_abi = header.e_flags & 6;
switch (float_abi) {
case 0:
{
}
break;
case 2:
{
arch += "f";
}
break;
case 4:
{
arch += "d";
}
break;
case 6:
{
arch += "q";
}
break;
}
} else if (arch == "arm") {
let buf = read_file_header(process.execPath, 512);
let header = decode_elf_header(buf);
if (header.e_flags & 1024) {
arch += "hf";
} else if (header.e_flags & 512) {
arch += "sf";
} else {
throw new Error("Unknown ARM floating-point ABI");
}
}
return arch;
}
function read_file_header(filename2, read) {
let fd = null;
try {
let fd2 = fs2.openSync(filename2);
let buf = Buffer.allocUnsafe(read);
let len = fs2.readSync(fd2, buf);
return buf.subarray(0, len);
} finally {
if (fd != null)
fs2.closeSync(fd);
}
}
function decode_elf_header(buf) {
let header = {};
if (buf.length < 16)
throw new Error("Truncated header");
if (buf[0] != 127 || buf[1] != 69 || buf[2] != 76 || buf[3] != 70)
throw new Error("Invalid magic number");
if (buf[6] != 1)
throw new Error("Invalid ELF version");
if (buf[5] != 1)
throw new Error("Big-endian architectures are not supported");
let machine = buf.readUInt16LE(18);
switch (machine) {
case 3:
{
header.e_machine = "ia32";
}
break;
case 40:
{
header.e_machine = "arm";
}
break;
case 62:
{
header.e_machine = "amd64";
}
break;
case 183:
{
header.e_machine = "arm64";
}
break;
case 243:
{
switch (buf[4]) {
case 1:
{
header.e_machine = "riscv32";
}
break;
case 2:
{
header.e_machine = "riscv64";
}
break;
}
}
break;
default:
throw new Error("Unknown ELF machine type");
}
switch (buf[4]) {
case 1:
{
buf = buf.subarray(0, 68);
if (buf.length < 68)
throw new Error("Truncated ELF header");
header.ei_class = 32;
header.e_flags = buf.readUInt32LE(36);
}
break;
case 2:
{
buf = buf.subarray(0, 120);
if (buf.length < 120)
throw new Error("Truncated ELF header");
header.ei_class = 64;
header.e_flags = buf.readUInt32LE(48);
}
break;
default:
throw new Error("Invalid ELF class");
}
return header;
}
function unlink_recursive(path3) {
try {
if (fs2.rmSync != null) {
fs2.rmSync(path3, { recursive: true, maxRetries: process.platform == "win32" ? 3 : 0 });
} else {
fs2.rmdirSync(path3, { recursive: true, maxRetries: process.platform == "win32" ? 3 : 0 });
}
} catch (err) {
if (err.code !== "ENOENT")
throw err;
}
}
function get_napi_version2(napi, major) {
if (napi > 8)
return null;
const support = {
6: ["6.14.2", "6.14.2", "6.14.2"],
8: ["8.6.0", "8.10.0", "8.11.2"],
9: ["9.0.0", "9.3.0", "9.11.0"],
10: ["10.0.0", "10.0.0", "10.0.0", "10.16.0", "10.17.0", "10.20.0", "10.23.0"],
11: ["11.0.0", "11.0.0", "11.0.0", "11.8.0"],
12: ["12.0.0", "12.0.0", "12.0.0", "12.0.0", "12.11.0", "12.17.0", "12.19.0", "12.22.0"],
13: ["13.0.0", "13.0.0", "13.0.0", "13.0.0", "13.0.0"],
14: ["14.0.0", "14.0.0", "14.0.0", "14.0.0", "14.0.0", "14.0.0", "14.12.0", "14.17.0"],
15: ["15.0.0", "15.0.0", "15.0.0", "15.0.0", "15.0.0", "15.0.0", "15.0.0", "15.12.0"]
};
const max = Math.max(...Object.keys(support).map((k) => parseInt(k, 10)));
if (major > max)
return major + ".0.0";
if (support[major] == null)
return null;
let required = support[major][napi - 1] || null;
return required;
}
function cmp_version(ver1, ver2) {
ver1 = String(ver1).replace(/-.*$/, "").split(".").reduce((acc, v, idx) => acc + parseInt(v, 10) * Math.pow(10, 2 * (5 - idx)), 0);
ver2 = String(ver2).replace(/-.*$/, "").split(".").reduce((acc, v, idx) => acc + parseInt(v, 10) * Math.pow(10, 2 * (5 - idx)), 0);
let cmp = Math.min(Math.max(ver1 - ver2, -1), 1);
return cmp;
}
module2.exports = {
download_http,
extract_targz,
path_is_absolute,
path_has_dotdot,
determine_arch: determine_arch2,
unlink_recursive,
get_napi_version: get_napi_version2,
cmp_version
};
}
});
// ../../bin/Koffi/package/src/koffi/package.json
var require_package = __commonJS({
"../../bin/Koffi/package/src/koffi/package.json"(exports2, module2) {
module2.exports = {
name: "koffi",
version: "2.11.0",
stable: "2.11.0",
description: "Fast and simple C FFI (foreign function interface) for Node.js",
keywords: [
"foreign",
"function",
"interface",
"ffi",
"binding",
"c",
"napi"
],
repository: {
type: "git",
url: "https://github.com/Koromix/koffi"
},
homepage: "https://koffi.dev/",
author: {
name: "Niels Martign\xE8ne",
email: "niels.martignene@protonmail.com",
url: "https://koromix.dev/"
},
main: "./index.js",
types: "./index.d.ts",
scripts: {
test: "node tools/koffi.js test",
prepack: `echo 'Use "npm run package" instead' && false`,
prepublishOnly: `echo 'Use "npm run package" instead' && false`,
package: "node tools/koffi.js build"
},
license: "MIT",
devDependencies: {
esbuild: "^0.19.2"
},
cnoke: {
api: "../../vendor/node-api-headers",
output: "../../bin/Koffi/{{ platform }}_{{ arch }}",
node: 16,
napi: 8,
require: "./index.js"
}
};
}
});
// ../../bin/Koffi/package/src/koffi/src/init.js
var require_init = __commonJS({
"../../bin/Koffi/package/src/koffi/src/init.js"(exports, module) {
var fs = require("fs");
var path = require("path");
var util = require("util");
var { get_napi_version, determine_arch } = require_tools();
var pkg = require_package();
function detect() {
if (process.versions.napi == null || process.versions.napi < pkg.cnoke.napi) {
let major = parseInt(process.versions.node, 10);
let required = get_napi_version(pkg.cnoke.napi, major);
if (required != null) {
throw new Error(`This engine is based on Node ${process.versions.node}, but ${pkg.name} requires Node >= ${required} in the Node ${major}.x branch (N-API >= ${pkg.cnoke.napi})`);
} else {
throw new Error(`This engine is based on Node ${process.versions.node}, but ${pkg.name} does not support the Node ${major}.x branch (N-API < ${pkg.cnoke.napi})`);
}
}
let arch = determine_arch();
let triplet3 = `${process.platform}_${arch}`;
return triplet3;
}
function init(triplet, native) {
if (native == null) {
let roots = [path.join(__dirname, "..")];
let triplets = [triplet];
if (process.resourcesPath != null)
roots.push(process.resourcesPath);
if (triplet.startsWith("linux_")) {
let musl = triplet.replace(/^linux_/, "musl_");
triplets.push(musl);
}
let filenames = roots.flatMap((root) => triplets.flatMap((triplet3) => [
`${root}/build/koffi/${triplet3}/koffi.node`,
`${root}/koffi/${triplet3}/koffi.node`,
`${root}/node_modules/koffi/build/koffi/${triplet3}/koffi.node`,
`${root}/../../bin/Koffi/${triplet3}/koffi.node`
]));
let first_err = null;
for (let filename of filenames) {
if (!fs.existsSync(filename))
continue;
try {
native = eval("require")(filename);
} catch (err) {
if (first_err == null)
first_err = err;
continue;
}
break;
}
if (first_err != null)
throw first_err;
}
if (native == null)
throw new Error("Cannot find the native Koffi module; did you bundle it correctly?");
if (native.version != pkg.version)
throw new Error("Mismatched native Koffi modules");
let mod = wrap(native);
return mod;
}
function wrap(native2) {
let obj = {
...native2,
// Deprecated functions
handle: util.deprecate(native2.opaque, "The koffi.handle() function was deprecated in Koffi 2.1, use koffi.opaque() instead", "KOFFI001"),
callback: util.deprecate(native2.proto, "The koffi.callback() function was deprecated in Koffi 2.4, use koffi.proto() instead", "KOFFI002")
};
obj.load = (...args) => {
let lib = native2.load(...args);
lib.cdecl = util.deprecate((...args2) => lib.func("__cdecl", ...args2), "The koffi.cdecl() function was deprecated in Koffi 2.7, use koffi.func(...) instead", "KOFFI003");
lib.stdcall = util.deprecate((...args2) => lib.func("__stdcall", ...args2), 'The koffi.stdcall() function was deprecated in Koffi 2.7, use koffi.func("__stdcall", ...) instead', "KOFFI004");
lib.fastcall = util.deprecate((...args2) => lib.func("__fastcall", ...args2), 'The koffi.fastcall() function was deprecated in Koffi 2.7, use koffi.func("__fastcall", ...) instead', "KOFFI005");
lib.thiscall = util.deprecate((...args2) => lib.func("__thiscall", ...args2), 'The koffi.thiscall() function was deprecated in Koffi 2.7, use koffi.func("__thiscall", ...) instead', "KOFFI006");
return lib;
};
return obj;
}
module.exports = {
detect,
init
};
}
});
// ../../bin/Koffi/package/src/koffi/indirect.js
var { detect: detect2, init: init2 } = require_init();
var triplet2 = detect2();
var mod2 = init2(triplet2, null);
module.exports = mod2;
;