koffi 2.6.0 → 2.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,15 @@
4
4
 
5
5
  ### Koffi 2.6
6
6
 
7
+ #### Koffi 2.6.2 (2023-10-13)
8
+
9
+ - Add indirect loading script to help some bundlers
10
+
11
+ #### Koffi 2.6.1 (2023-09-18)
12
+
13
+ - Support string direction qualifiers in classic function definitions
14
+ - Fix possible off-by-one array access when parsing type name
15
+
7
16
  #### Koffi 2.6.0 (2023-09-13)
8
17
 
9
18
  **New features:**
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/doc/contribute.md CHANGED
@@ -138,8 +138,8 @@ First, you must update the code in three steps:
138
138
  Once this is done, you can publish a new release with the following commands:
139
139
 
140
140
  ```sh
141
- node qemu.js test # If not done before
142
- node qemu.js dist
141
+ node tools/qemu.js test # If not done before
142
+ node tools/qemu.js dist
143
143
 
144
144
  cd build/dist
145
145
  npm publish
package/doc/packaging.md CHANGED
@@ -36,6 +36,14 @@ resources/
36
36
  MyApp.exe
37
37
  ```
38
38
 
39
+ ## Indirect loader
40
+
41
+ *New in Koffi 2.6.2*
42
+
43
+ Some bundlers (such as vite) don't like when require is used with native modules.
44
+
45
+ In this case, you can use `js require('koffi/indirect')` but you will need to make sure that the native Koffi modules are packaged properly.
46
+
39
47
  ## Packaging examples
40
48
 
41
49
  ### Electron with electron-builder
package/doc/variables.md CHANGED
@@ -97,4 +97,4 @@ console.log(koffi.decode(my_int, 'int')) // Prints -1
97
97
  console.log(koffi.decode(my_string, 'const char *')) // Prints "Hello World!"
98
98
  ```
99
99
 
100
- When encoding strings (either directly or embedded in arrays or structs), the memory will be bound to the raw pointer value and managed by Koffi. You can assign to the same string again and again with any leak or risk of use-after-free.
100
+ When encoding strings (either directly or embedded in arrays or structs), the memory will be bound to the raw pointer value and managed by Koffi. You can assign to the same string again and again without any leak or risk of use-after-free.
@@ -378,8 +378,8 @@ var require_package = __commonJS({
378
378
  "build/dist/src/koffi/package.json"(exports2, module2) {
379
379
  module2.exports = {
380
380
  name: "koffi",
381
- version: "2.6.0",
382
- stable: "2.6.0",
381
+ version: "2.6.2",
382
+ stable: "2.6.2",
383
383
  description: "Fast and simple C FFI (foreign function interface) for Node.js",
384
384
  keywords: [
385
385
  "foreign",
@@ -400,8 +400,8 @@ var require_package = __commonJS({
400
400
  email: "niels.martignene@protonmail.com",
401
401
  url: "https://koromix.dev/"
402
402
  },
403
- main: "src/index.js",
404
- types: "src/index.d.ts",
403
+ main: "./index.js",
404
+ types: "./index.d.ts",
405
405
  scripts: {
406
406
  test: "node tools/qemu.js test",
407
407
  prepack: `echo 'Use "npm run prepare" instead' && false`,
@@ -430,7 +430,7 @@ var require_package = __commonJS({
430
430
  }
431
431
  });
432
432
 
433
- // build/dist/src/koffi/src/index.js
433
+ // build/dist/src/koffi/index.js
434
434
  var util = require("util");
435
435
  var fs = require("fs");
436
436
  var { get_napi_version, determine_arch } = require_tools();
@@ -451,77 +451,77 @@ try {
451
451
  switch (triplet) {
452
452
  case "darwin_arm64":
453
453
  {
454
- native = require("../build/koffi/darwin_arm64/koffi.node");
454
+ native = require("./build/koffi/darwin_arm64/koffi.node");
455
455
  }
456
456
  break;
457
457
  case "darwin_x64":
458
458
  {
459
- native = require("../build/koffi/darwin_x64/koffi.node");
459
+ native = require("./build/koffi/darwin_x64/koffi.node");
460
460
  }
461
461
  break;
462
462
  case "freebsd_arm64":
463
463
  {
464
- native = require("../build/koffi/freebsd_arm64/koffi.node");
464
+ native = require("./build/koffi/freebsd_arm64/koffi.node");
465
465
  }
466
466
  break;
467
467
  case "freebsd_ia32":
468
468
  {
469
- native = require("../build/koffi/freebsd_ia32/koffi.node");
469
+ native = require("./build/koffi/freebsd_ia32/koffi.node");
470
470
  }
471
471
  break;
472
472
  case "freebsd_x64":
473
473
  {
474
- native = require("../build/koffi/freebsd_x64/koffi.node");
474
+ native = require("./build/koffi/freebsd_x64/koffi.node");
475
475
  }
476
476
  break;
477
477
  case "linux_arm32hf":
478
478
  {
479
- native = require("../build/koffi/linux_arm32hf/koffi.node");
479
+ native = require("./build/koffi/linux_arm32hf/koffi.node");
480
480
  }
481
481
  break;
482
482
  case "linux_arm64":
483
483
  {
484
- native = require("../build/koffi/linux_arm64/koffi.node");
484
+ native = require("./build/koffi/linux_arm64/koffi.node");
485
485
  }
486
486
  break;
487
487
  case "linux_ia32":
488
488
  {
489
- native = require("../build/koffi/linux_ia32/koffi.node");
489
+ native = require("./build/koffi/linux_ia32/koffi.node");
490
490
  }
491
491
  break;
492
492
  case "linux_riscv64hf64":
493
493
  {
494
- native = require("../build/koffi/linux_riscv64hf64/koffi.node");
494
+ native = require("./build/koffi/linux_riscv64hf64/koffi.node");
495
495
  }
496
496
  break;
497
497
  case "linux_x64":
498
498
  {
499
- native = require("../build/koffi/linux_x64/koffi.node");
499
+ native = require("./build/koffi/linux_x64/koffi.node");
500
500
  }
501
501
  break;
502
502
  case "openbsd_ia32":
503
503
  {
504
- native = require("../build/koffi/openbsd_ia32/koffi.node");
504
+ native = require("./build/koffi/openbsd_ia32/koffi.node");
505
505
  }
506
506
  break;
507
507
  case "openbsd_x64":
508
508
  {
509
- native = require("../build/koffi/openbsd_x64/koffi.node");
509
+ native = require("./build/koffi/openbsd_x64/koffi.node");
510
510
  }
511
511
  break;
512
512
  case "win32_arm64":
513
513
  {
514
- native = require("../build/koffi/win32_arm64/koffi.node");
514
+ native = require("./build/koffi/win32_arm64/koffi.node");
515
515
  }
516
516
  break;
517
517
  case "win32_ia32":
518
518
  {
519
- native = require("../build/koffi/win32_ia32/koffi.node");
519
+ native = require("./build/koffi/win32_ia32/koffi.node");
520
520
  }
521
521
  break;
522
522
  case "win32_x64":
523
523
  {
524
- native = require("../build/koffi/win32_x64/koffi.node");
524
+ native = require("./build/koffi/win32_x64/koffi.node");
525
525
  }
526
526
  break;
527
527
  }
@@ -529,6 +529,7 @@ try {
529
529
  }
530
530
  if (native == null) {
531
531
  let names = [
532
+ `/build/koffi/${process.platform}_${arch}/koffi.node`,
532
533
  `/koffi/${process.platform}_${arch}/koffi.node`,
533
534
  `/node_modules/koffi/build/koffi/${process.platform}_${arch}/koffi.node`
534
535
  ];
package/indirect.js ADDED
@@ -0,0 +1,480 @@
1
+ "use strict";
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __commonJS = (cb, mod) => function __require() {
4
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
5
+ };
6
+
7
+ // build/dist/src/cnoke/src/tools.js
8
+ var require_tools = __commonJS({
9
+ "build/dist/src/cnoke/src/tools.js"(exports2, module2) {
10
+ "use strict";
11
+ var crypto = require("crypto");
12
+ var fs2 = require("fs");
13
+ var http = require("https");
14
+ var path = require("path");
15
+ var zlib = require("zlib");
16
+ async function download_http(url, dest) {
17
+ if (Array.isArray(url)) {
18
+ let urls = url;
19
+ for (let url2 of urls) {
20
+ try {
21
+ await download_http(url2, dest);
22
+ return;
23
+ } catch (err) {
24
+ if (err.code != 404)
25
+ throw err;
26
+ }
27
+ }
28
+ throw new Error("All URLs returned error 404");
29
+ }
30
+ console.log(">> Downloading " + url);
31
+ let [tmp_name, file] = open_temporary_stream(dest);
32
+ try {
33
+ await new Promise((resolve, reject) => {
34
+ let request = http.get(url, (response) => {
35
+ if (response.statusCode != 200) {
36
+ let err = new Error(`Download failed: ${response.statusMessage} [${response.statusCode}]`);
37
+ err.code = response.statusCode;
38
+ reject(err);
39
+ return;
40
+ }
41
+ response.pipe(file);
42
+ file.on("finish", () => file.close(() => {
43
+ try {
44
+ fs2.renameSync(file.path, dest);
45
+ } catch (err) {
46
+ if (err.code != "EBUSY")
47
+ reject(err);
48
+ }
49
+ resolve();
50
+ }));
51
+ });
52
+ request.on("error", reject);
53
+ file.on("error", reject);
54
+ });
55
+ } catch (err) {
56
+ file.close();
57
+ try {
58
+ fs2.unlinkSync(tmp_name);
59
+ } catch (err2) {
60
+ if (err2.code != "ENOENT")
61
+ throw err2;
62
+ }
63
+ throw err;
64
+ }
65
+ }
66
+ function open_temporary_stream(prefix) {
67
+ let buf = Buffer.allocUnsafe(4);
68
+ for (; ; ) {
69
+ try {
70
+ crypto.randomFillSync(buf);
71
+ let suffix = buf.toString("hex").padStart(8, "0");
72
+ let filename = `${prefix}.${suffix}`;
73
+ let file = fs2.createWriteStream(filename, { flags: "wx", mode: 420 });
74
+ return [filename, file];
75
+ } catch (err) {
76
+ if (err.code != "EEXIST")
77
+ throw err;
78
+ }
79
+ }
80
+ }
81
+ function extract_targz(filename, dest_dir, strip = 0) {
82
+ let reader = fs2.createReadStream(filename).pipe(zlib.createGunzip());
83
+ return new Promise((resolve, reject) => {
84
+ let header = null;
85
+ let extended = {};
86
+ reader.on("readable", () => {
87
+ try {
88
+ for (; ; ) {
89
+ if (header == null) {
90
+ let buf = reader.read(512);
91
+ if (buf == null)
92
+ break;
93
+ if (!buf[0])
94
+ continue;
95
+ header = {
96
+ filename: buf.toString("utf-8", 0, 100).replace(/\0/g, ""),
97
+ mode: parseInt(buf.toString("ascii", 100, 109), 8),
98
+ size: parseInt(buf.toString("ascii", 124, 137), 8),
99
+ type: String.fromCharCode(buf[156])
100
+ };
101
+ Object.assign(header, extended);
102
+ extended = {};
103
+ header.filename = header.filename.replace(/\\/g, "/");
104
+ if (!header.filename.length)
105
+ throw new Error(`Insecure empty filename inside TAR archive`);
106
+ if (path_is_absolute(header.filename[0]))
107
+ throw new Error(`Insecure filename starting with / inside TAR archive`);
108
+ if (path_has_dotdot(header.filename))
109
+ throw new Error(`Insecure filename containing '..' inside TAR archive`);
110
+ for (let i = 0; i < strip; i++)
111
+ header.filename = header.filename.substr(header.filename.indexOf("/") + 1);
112
+ }
113
+ let aligned = Math.floor((header.size + 511) / 512) * 512;
114
+ let data = header.size ? reader.read(aligned) : null;
115
+ if (data == null) {
116
+ if (header.size)
117
+ break;
118
+ data = Buffer.alloc(0);
119
+ }
120
+ data = data.subarray(0, header.size);
121
+ if (header.type == "0" || header.type == "7") {
122
+ let filename2 = dest_dir + "/" + header.filename;
123
+ let dirname = path.dirname(filename2);
124
+ fs2.mkdirSync(dirname, { recursive: true, mode: 493 });
125
+ fs2.writeFileSync(filename2, data, { mode: header.mode });
126
+ } else if (header.type == "5") {
127
+ let filename2 = dest_dir + "/" + header.filename;
128
+ fs2.mkdirSync(filename2, { recursive: true, mode: header.mode });
129
+ } else if (header.type == "L") {
130
+ extended.filename = data.toString("utf-8").replace(/\0/g, "");
131
+ } else if (header.type == "x") {
132
+ let str = data.toString("utf-8");
133
+ try {
134
+ while (str.length) {
135
+ let matches = str.match(/^([0-9]+) ([a-zA-Z0-9\._]+)=(.*)\n/);
136
+ let skip = parseInt(matches[1], 10);
137
+ let key = matches[2];
138
+ let value = matches[3];
139
+ switch (key) {
140
+ case "path":
141
+ {
142
+ extended.filename = value;
143
+ }
144
+ break;
145
+ case "size":
146
+ {
147
+ extended.size = parseInt(value, 10);
148
+ }
149
+ break;
150
+ }
151
+ str = str.substr(skip).trimStart();
152
+ }
153
+ } catch (err) {
154
+ throw new Error("Malformed PAX entry");
155
+ }
156
+ }
157
+ header = null;
158
+ }
159
+ } catch (err) {
160
+ reject(err);
161
+ }
162
+ });
163
+ reader.on("error", reject);
164
+ reader.on("end", resolve);
165
+ });
166
+ }
167
+ function path_is_absolute(path2) {
168
+ if (process.platform == "win32" && path2.match(/^[a-zA-Z]:/))
169
+ path2 = path2.substr(2);
170
+ return is_path_separator(path2[0]);
171
+ }
172
+ function path_has_dotdot(path2) {
173
+ let start = 0;
174
+ for (; ; ) {
175
+ let offset = path2.indexOf("..", start);
176
+ if (offset < 0)
177
+ break;
178
+ start = offset + 2;
179
+ if (offset && !is_path_separator(path2[offset - 1]))
180
+ continue;
181
+ if (offset + 2 < path2.length && !is_path_separator(path2[offset + 2]))
182
+ continue;
183
+ return true;
184
+ }
185
+ return false;
186
+ }
187
+ function is_path_separator(c) {
188
+ if (c == "/")
189
+ return true;
190
+ if (process.platform == "win32" && c == "\\")
191
+ return true;
192
+ return false;
193
+ }
194
+ function determine_arch2() {
195
+ let arch2 = process.arch;
196
+ if (arch2 == "riscv32" || arch2 == "riscv64") {
197
+ let buf = read_file_header(process.execPath, 512);
198
+ let header = decode_elf_header(buf);
199
+ let float_abi = (header.e_flags & 6) >> 1;
200
+ switch (float_abi) {
201
+ case 0:
202
+ {
203
+ arch2 += "sf";
204
+ }
205
+ break;
206
+ case 1:
207
+ {
208
+ arch2 += "hf32";
209
+ }
210
+ break;
211
+ case 2:
212
+ {
213
+ arch2 += "hf64";
214
+ }
215
+ break;
216
+ case 3:
217
+ {
218
+ arch2 += "hf128";
219
+ }
220
+ break;
221
+ }
222
+ } else if (arch2 == "arm") {
223
+ arch2 = "arm32";
224
+ let buf = read_file_header(process.execPath, 512);
225
+ let header = decode_elf_header(buf);
226
+ if (header.e_flags & 1024) {
227
+ arch2 += "hf";
228
+ } else if (header.e_flags & 512) {
229
+ arch2 += "sf";
230
+ } else {
231
+ throw new Error("Unknown ARM floating-point ABI");
232
+ }
233
+ }
234
+ return arch2;
235
+ }
236
+ function read_file_header(filename, read) {
237
+ let fd = null;
238
+ try {
239
+ let fd2 = fs2.openSync(filename);
240
+ let buf = Buffer.allocUnsafe(read);
241
+ let len = fs2.readSync(fd2, buf);
242
+ return buf.subarray(0, len);
243
+ } finally {
244
+ if (fd != null)
245
+ fs2.closeSync(fd);
246
+ }
247
+ }
248
+ function decode_elf_header(buf) {
249
+ let header = {};
250
+ if (buf.length < 16)
251
+ throw new Error("Truncated header");
252
+ if (buf[0] != 127 || buf[1] != 69 || buf[2] != 76 || buf[3] != 70)
253
+ throw new Error("Invalid magic number");
254
+ if (buf[6] != 1)
255
+ throw new Error("Invalid ELF version");
256
+ if (buf[5] != 1)
257
+ throw new Error("Big-endian architectures are not supported");
258
+ let machine = buf.readUInt16LE(18);
259
+ switch (machine) {
260
+ case 3:
261
+ {
262
+ header.e_machine = "ia32";
263
+ }
264
+ break;
265
+ case 40:
266
+ {
267
+ header.e_machine = "arm";
268
+ }
269
+ break;
270
+ case 62:
271
+ {
272
+ header.e_machine = "amd64";
273
+ }
274
+ break;
275
+ case 183:
276
+ {
277
+ header.e_machine = "arm64";
278
+ }
279
+ break;
280
+ case 243:
281
+ {
282
+ switch (buf[4]) {
283
+ case 1:
284
+ {
285
+ header.e_machine = "riscv32";
286
+ }
287
+ break;
288
+ case 2:
289
+ {
290
+ header.e_machine = "riscv64";
291
+ }
292
+ break;
293
+ }
294
+ }
295
+ break;
296
+ default:
297
+ throw new Error("Unknown ELF machine type");
298
+ }
299
+ switch (buf[4]) {
300
+ case 1:
301
+ {
302
+ buf = buf.subarray(0, 68);
303
+ if (buf.length < 68)
304
+ throw new Error("Truncated ELF header");
305
+ header.ei_class = 32;
306
+ header.e_flags = buf.readUInt32LE(36);
307
+ }
308
+ break;
309
+ case 2:
310
+ {
311
+ buf = buf.subarray(0, 120);
312
+ if (buf.length < 120)
313
+ throw new Error("Truncated ELF header");
314
+ header.ei_class = 64;
315
+ header.e_flags = buf.readUInt32LE(48);
316
+ }
317
+ break;
318
+ default:
319
+ throw new Error("Invalid ELF class");
320
+ }
321
+ return header;
322
+ }
323
+ function unlink_recursive(path2) {
324
+ try {
325
+ if (fs2.rmSync != null) {
326
+ fs2.rmSync(path2, { recursive: true, maxRetries: process.platform == "win32" ? 3 : 0 });
327
+ } else {
328
+ fs2.rmdirSync(path2, { recursive: true, maxRetries: process.platform == "win32" ? 3 : 0 });
329
+ }
330
+ } catch (err) {
331
+ if (err.code !== "ENOENT")
332
+ throw err;
333
+ }
334
+ }
335
+ function get_napi_version2(napi, major) {
336
+ if (napi > 8)
337
+ return null;
338
+ const support = {
339
+ 6: ["6.14.2", "6.14.2", "6.14.2"],
340
+ 8: ["8.6.0", "8.10.0", "8.11.2"],
341
+ 9: ["9.0.0", "9.3.0", "9.11.0"],
342
+ 10: ["10.0.0", "10.0.0", "10.0.0", "10.16.0", "10.17.0", "10.20.0", "10.23.0"],
343
+ 11: ["11.0.0", "11.0.0", "11.0.0", "11.8.0"],
344
+ 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"],
345
+ 13: ["13.0.0", "13.0.0", "13.0.0", "13.0.0", "13.0.0"],
346
+ 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"],
347
+ 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"]
348
+ };
349
+ const max = Math.max(...Object.keys(support).map((k) => parseInt(k, 10)));
350
+ if (major > max)
351
+ return major + ".0.0";
352
+ if (support[major] == null)
353
+ return null;
354
+ let required = support[major][napi - 1] || null;
355
+ return required;
356
+ }
357
+ function cmp_version(ver1, ver2) {
358
+ ver1 = String(ver1).replace(/-.*$/, "").split(".").reduce((acc, v, idx) => acc + parseInt(v, 10) * Math.pow(10, 2 * (5 - idx)), 0);
359
+ ver2 = String(ver2).replace(/-.*$/, "").split(".").reduce((acc, v, idx) => acc + parseInt(v, 10) * Math.pow(10, 2 * (5 - idx)), 0);
360
+ let cmp = Math.min(Math.max(ver1 - ver2, -1), 1);
361
+ return cmp;
362
+ }
363
+ module2.exports = {
364
+ download_http,
365
+ extract_targz,
366
+ path_is_absolute,
367
+ path_has_dotdot,
368
+ determine_arch: determine_arch2,
369
+ unlink_recursive,
370
+ get_napi_version: get_napi_version2,
371
+ cmp_version
372
+ };
373
+ }
374
+ });
375
+
376
+ // build/dist/src/koffi/package.json
377
+ var require_package = __commonJS({
378
+ "build/dist/src/koffi/package.json"(exports2, module2) {
379
+ module2.exports = {
380
+ name: "koffi",
381
+ version: "2.6.2",
382
+ stable: "2.6.2",
383
+ description: "Fast and simple C FFI (foreign function interface) for Node.js",
384
+ keywords: [
385
+ "foreign",
386
+ "function",
387
+ "interface",
388
+ "ffi",
389
+ "binding",
390
+ "c",
391
+ "napi"
392
+ ],
393
+ repository: {
394
+ type: "git",
395
+ url: "https://github.com/Koromix/koffi"
396
+ },
397
+ homepage: "https://koffi.dev/",
398
+ author: {
399
+ name: "Niels Martign\xE8ne",
400
+ email: "niels.martignene@protonmail.com",
401
+ url: "https://koromix.dev/"
402
+ },
403
+ main: "./index.js",
404
+ types: "./index.d.ts",
405
+ scripts: {
406
+ test: "node tools/qemu.js test",
407
+ prepack: `echo 'Use "npm run prepare" instead' && false`,
408
+ prepublishOnly: `echo 'Use "npm run release" instead' && false`,
409
+ release: "node tools/qemu.js publish"
410
+ },
411
+ license: "MIT",
412
+ devDependencies: {
413
+ chalk: "^4.1.2",
414
+ esbuild: "^0.19.2",
415
+ "ffi-napi": "^4.0.3",
416
+ "ffi-rs": "^1.0.12",
417
+ minimatch: "^5.0.1",
418
+ "node-ssh": "^12.0.3",
419
+ raylib: "^0.9.2",
420
+ "ref-napi": "^3.0.3",
421
+ "ref-struct-di": "^1.1.1",
422
+ tar: "^6.1.11"
423
+ },
424
+ cnoke: {
425
+ output: "build/koffi/{{ platform }}_{{ arch }}",
426
+ napi: 8,
427
+ require: "./src/index.js"
428
+ }
429
+ };
430
+ }
431
+ });
432
+
433
+ // build/dist/src/koffi/indirect.js
434
+ var util = require("util");
435
+ var fs = require("fs");
436
+ var { get_napi_version, determine_arch } = require_tools();
437
+ var pkg = require_package();
438
+ if (process.versions.napi == null || process.versions.napi < pkg.cnoke.napi) {
439
+ let major = parseInt(process.versions.node, 10);
440
+ let required = get_napi_version(pkg.cnoke.napi, major);
441
+ if (required != null) {
442
+ throw new Error(`Project ${pkg.name} requires Node >= ${required} in the Node ${major}.x branch (N-API >= ${pkg.cnoke.napi})`);
443
+ } else {
444
+ throw new Error(`Project ${pkg.name} does not support the Node ${major}.x branch (N-API < ${pkg.cnoke.napi})`);
445
+ }
446
+ }
447
+ var arch = determine_arch();
448
+ var triplet = `${process.platform}_${arch}`;
449
+ var native = null;
450
+ {
451
+ let names = [
452
+ `/build/koffi/${process.platform}_${arch}/koffi.node`,
453
+ `/koffi/${process.platform}_${arch}/koffi.node`,
454
+ `/node_modules/koffi/build/koffi/${process.platform}_${arch}/koffi.node`
455
+ ];
456
+ for (let name of names) {
457
+ if (fs.existsSync(__dirname + name)) {
458
+ native = require(__dirname + name);
459
+ break;
460
+ }
461
+ }
462
+ if (native == null && process.resourcesPath != null) {
463
+ for (let name of names) {
464
+ if (fs.existsSync(process.resourcesPath + name)) {
465
+ native = require(process.resourcesPath + name);
466
+ break;
467
+ }
468
+ }
469
+ }
470
+ }
471
+ if (native == null)
472
+ throw new Error("Cannot find the native Koffi module; did you bundle it correctly?");
473
+ if (native.version != pkg.version)
474
+ throw new Error("Mismatched native Koffi modules");
475
+ module.exports = {
476
+ ...native,
477
+ // Deprecated functions
478
+ handle: util.deprecate(native.opaque, "The koffi.handle() function was deprecated in Koffi 2.1, use koffi.opaque() instead", "KOFFI001"),
479
+ callback: util.deprecate(native.proto, "The koffi.callback() function was deprecated in Koffi 2.4, use koffi.proto() instead", "KOFFI002")
480
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koffi",
3
- "version": "2.6.0",
4
- "stable": "2.6.0",
3
+ "version": "2.6.2",
4
+ "stable": "2.6.2",
5
5
  "description": "Fast and simple C FFI (foreign function interface) for Node.js",
6
6
  "keywords": [
7
7
  "foreign",
@@ -22,8 +22,8 @@
22
22
  "email": "niels.martignene@protonmail.com",
23
23
  "url": "https://koromix.dev/"
24
24
  },
25
- "main": "src/index.js",
26
- "types": "src/index.d.ts",
25
+ "main": "./index.js",
26
+ "types": "./index.d.ts",
27
27
  "scripts": {
28
28
  "install": "node src/cnoke/cnoke.js -p . -d src/koffi --prebuild"
29
29
  },
@@ -3265,7 +3265,7 @@ OpenResult OpenDescriptor(const char *filename, unsigned int flags, unsigned int
3265
3265
  case (int)OpenFlag::Append: {
3266
3266
  access = GENERIC_WRITE;
3267
3267
  share = FILE_SHARE_READ | FILE_SHARE_WRITE;
3268
- creation = CREATE_ALWAYS;
3268
+ creation = OPEN_ALWAYS;
3269
3269
  attributes = FILE_ATTRIBUTE_NORMAL;
3270
3270
  oflags = _O_WRONLY | _O_CREAT | _O_APPEND | _O_BINARY | _O_NOINHERIT;
3271
3271
  } break;
@@ -3293,7 +3293,15 @@ OpenResult OpenDescriptor(const char *filename, unsigned int flags, unsigned int
3293
3293
  }
3294
3294
  share |= FILE_SHARE_DELETE;
3295
3295
 
3296
- HANDLE h;
3296
+ HANDLE h = nullptr;
3297
+ int fd = -1;
3298
+ RG_DEFER_N(err_guard) {
3299
+ close(fd);
3300
+ if (h) {
3301
+ CloseHandle(h);
3302
+ }
3303
+ };
3304
+
3297
3305
  if (win32_utf8) {
3298
3306
  h = CreateFileA(filename, access, share, nullptr, creation, attributes, nullptr);
3299
3307
  } else {
@@ -3321,15 +3329,20 @@ OpenResult OpenDescriptor(const char *filename, unsigned int flags, unsigned int
3321
3329
  return ret;
3322
3330
  }
3323
3331
 
3324
- int fd = _open_osfhandle((intptr_t)h, oflags);
3332
+ fd = _open_osfhandle((intptr_t)h, oflags);
3325
3333
  if (fd < 0) {
3326
3334
  LogError("Cannot open '%1': %2", filename, strerror(errno));
3327
- CloseHandle(h);
3335
+ return OpenResult::OtherError;
3336
+ }
3328
3337
 
3338
+ if ((flags & (int)OpenFlag::Append) && _lseeki64(fd, 0, SEEK_END) < 0) {
3339
+ LogError("Cannot move file pointer: %1", strerror(errno));
3329
3340
  return OpenResult::OtherError;
3330
3341
  }
3331
3342
 
3343
+ err_guard.Disable();
3332
3344
  *out_fd = fd;
3345
+
3333
3346
  return OpenResult::Success;
3334
3347
  }
3335
3348
 
@@ -4871,15 +4884,20 @@ const char *GetUserConfigPath(const char *name, Allocator *alloc)
4871
4884
  const char *xdg = getenv("XDG_CONFIG_HOME");
4872
4885
  const char *home = getenv("HOME");
4873
4886
 
4887
+ const char *path = nullptr;
4888
+
4874
4889
  if (xdg) {
4875
- const char *path = Fmt(alloc, "%1%/%2", xdg, name).ptr;
4876
- return path;
4890
+ path = Fmt(alloc, "%1%/%2", xdg, name).ptr;
4877
4891
  } else if (home) {
4878
- const char *path = Fmt(alloc, "%1%/.config/%2", home, name).ptr;
4879
- return path;
4880
- } else {
4881
- return nullptr;
4892
+ path = Fmt(alloc, "%1%/.config/%2", home, name).ptr;
4893
+ } else if (!getuid()) {
4894
+ path = Fmt(alloc, "/root/.config/%1", name).ptr;
4882
4895
  }
4896
+
4897
+ if (path && !EnsureDirectoryExists(path))
4898
+ return nullptr;
4899
+
4900
+ return path;
4883
4901
  }
4884
4902
 
4885
4903
  const char *GetUserCachePath(const char *name, Allocator *alloc)
@@ -4889,15 +4907,20 @@ const char *GetUserCachePath(const char *name, Allocator *alloc)
4889
4907
  const char *xdg = getenv("XDG_CACHE_HOME");
4890
4908
  const char *home = getenv("HOME");
4891
4909
 
4910
+ const char *path = nullptr;
4911
+
4892
4912
  if (xdg) {
4893
- const char *path = Fmt(alloc, "%1%/%2", xdg, name).ptr;
4894
- return path;
4913
+ path = Fmt(alloc, "%1%/%2", xdg, name).ptr;
4895
4914
  } else if (home) {
4896
- const char *path = Fmt(alloc, "%1%/.cache/%2", home, name).ptr;
4897
- return path;
4898
- } else {
4899
- return nullptr;
4915
+ path = Fmt(alloc, "%1%/.cache/%2", home, name).ptr;
4916
+ } else if (!getuid()) {
4917
+ path = Fmt(alloc, "/root/.cache/%1", name).ptr;
4900
4918
  }
4919
+
4920
+ if (path && !EnsureDirectoryExists(path))
4921
+ return nullptr;
4922
+
4923
+ return path;
4901
4924
  }
4902
4925
 
4903
4926
  const char *GetSystemConfigPath(const char *name, Allocator *alloc)
@@ -5122,7 +5145,8 @@ static RG_THREAD_LOCAL Size rnd_offset;
5122
5145
 
5123
5146
  static void InitChaCha20(uint32_t state[16], uint32_t key[8], uint32_t iv[2])
5124
5147
  {
5125
- static const uint32_t *const magic = (const uint32_t *)"expand 32-byte k";
5148
+ alignas(uint32_t) static char str[] = "expand 32-byte k";
5149
+ const uint32_t *magic = (const uint32_t *)str;
5126
5150
 
5127
5151
  state[0] = LittleEndian(magic[0]);
5128
5152
  state[1] = LittleEndian(magic[1]);
@@ -35,7 +35,7 @@ bool PrototypeParser::Parse(const char *str, FunctionInfo *out_func)
35
35
 
36
36
  Tokenize(str);
37
37
 
38
- out_func->ret.type = ParseType();
38
+ out_func->ret.type = ParseType(nullptr);
39
39
  if (!CanReturnType(out_func->ret.type)) {
40
40
  MarkError("You are not allowed to directly return %1 values (maybe try %1 *)", out_func->ret.type->name);
41
41
  return false;
@@ -62,17 +62,7 @@ bool PrototypeParser::Parse(const char *str, FunctionInfo *out_func)
62
62
  break;
63
63
  }
64
64
 
65
- if (Match("_In_")) {
66
- param.directions = 1;
67
- } else if (Match("_Out_")) {
68
- param.directions = 2;
69
- } else if (Match("_Inout_")) {
70
- param.directions = 3;
71
- } else {
72
- param.directions = 1;
73
- }
74
-
75
- param.type = ParseType();
65
+ param.type = ParseType(&param.directions);
76
66
 
77
67
  if (!CanPassType(param.type, param.directions)) {
78
68
  MarkError("Type %1 cannot be used as a parameter", param.type->name);
@@ -145,7 +135,7 @@ void PrototypeParser::Tokenize(const char *str)
145
135
  }
146
136
  }
147
137
 
148
- const TypeInfo *PrototypeParser::ParseType()
138
+ const TypeInfo *PrototypeParser::ParseType(int *out_directions)
149
139
  {
150
140
  Size start = offset;
151
141
 
@@ -166,7 +156,7 @@ const TypeInfo *PrototypeParser::ParseType()
166
156
 
167
157
  while (offset >= start) {
168
158
  Span<const char> str = MakeSpan(tokens[start].ptr, tokens[offset].end() - tokens[start].ptr);
169
- const TypeInfo *type = ResolveType(env, str);
159
+ const TypeInfo *type = ResolveType(env, str, out_directions);
170
160
 
171
161
  if (type) {
172
162
  offset++;
@@ -49,7 +49,7 @@ public:
49
49
  private:
50
50
  void Tokenize(const char *str);
51
51
 
52
- const TypeInfo *ParseType();
52
+ const TypeInfo *ParseType(int *out_directions);
53
53
  const char *ParseIdentifier();
54
54
 
55
55
  bool Consume(const char *expect);
@@ -153,6 +153,22 @@ static inline bool IsIdentifierChar(char c)
153
153
  return IsAsciiAlphaOrDigit(c) || c == '_';
154
154
  }
155
155
 
156
+ static inline Span<const char> SplitIdentifier(Span<const char> str)
157
+ {
158
+ Size offset = 0;
159
+
160
+ if (str.len && IsIdentifierStart(str[0])) {
161
+ offset++;
162
+
163
+ while (offset < str.len && IsIdentifierChar(str[offset])) {
164
+ offset++;
165
+ }
166
+ }
167
+
168
+ Span<const char> token = str.Take(0, offset);
169
+ return token;
170
+ }
171
+
156
172
  const TypeInfo *ResolveType(Napi::Env env, Span<const char> str, int *out_directions)
157
173
  {
158
174
  InstanceData *instance = env.GetInstanceData<InstanceData>();
@@ -160,8 +176,31 @@ const TypeInfo *ResolveType(Napi::Env env, Span<const char> str, int *out_direct
160
176
  int indirect = 0;
161
177
  uint8_t disposables = 0;
162
178
 
179
+ // Consume parameter direction qualifier
180
+ if (out_directions) {
181
+ if (str.len && str[0] == '_') {
182
+ Span<const char> qualifier = SplitIdentifier(str);
183
+
184
+ if (qualifier == "_In_") {
185
+ *out_directions = 1;
186
+ str = str.Take(5, str.len - 5);
187
+ } else if (qualifier == "_Out_") {
188
+ *out_directions = 2;
189
+ str = str.Take(6, str.len - 6);
190
+ } else if (qualifier == "_Inout_") {
191
+ *out_directions = 3;
192
+ str = str.Take(8, str.len - 8);
193
+ } else {
194
+ *out_directions = 1;
195
+ }
196
+ } else {
197
+ *out_directions = 1;
198
+ }
199
+ }
200
+
163
201
  // Skip initial const qualifiers
164
- while (str.len >= 6 && StartsWith(str, "const") && IsAsciiWhite(str[5])) {
202
+ str = TrimStr(str);
203
+ while (SplitIdentifier(str) == "const") {
165
204
  str = str.Take(6, str.len - 6);
166
205
  str = TrimStr(str);
167
206
  }
@@ -173,13 +212,10 @@ const TypeInfo *ResolveType(Napi::Env env, Span<const char> str, int *out_direct
173
212
  for (;;) {
174
213
  remain = TrimStr(remain);
175
214
 
176
- if (!remain.len || !IsIdentifierStart(remain[0]))
215
+ Span<const char> token = SplitIdentifier(remain);
216
+ if (!token.len)
177
217
  break;
178
-
179
- do {
180
- remain.ptr++;
181
- remain.len--;
182
- } while (remain.len && IsIdentifierChar(remain[0]));
218
+ remain = remain.Take(token.len, remain.len - token.len);
183
219
  }
184
220
 
185
221
  Span<const char> name = TrimStr(MakeSpan(str.ptr, remain.ptr - str.ptr));
@@ -197,7 +233,7 @@ const TypeInfo *ResolveType(Napi::Env env, Span<const char> str, int *out_direct
197
233
  } else if (remain[0] == '!') {
198
234
  remain = remain.Take(1, remain.len - 1);
199
235
  disposables |= (1u << indirect);
200
- } else if (remain.len >= 6 && StartsWith(remain, "const") && !IsIdentifierStart(remain[6])) {
236
+ } else if (SplitIdentifier(remain) == "const") {
201
237
  remain = remain.Take(6, remain.len - 6);
202
238
  } else {
203
239
  break;
@@ -263,9 +299,6 @@ const TypeInfo *ResolveType(Napi::Env env, Span<const char> str, int *out_direct
263
299
  RG_ASSERT(type);
264
300
  }
265
301
 
266
- if (out_directions) {
267
- *out_directions = 1;
268
- }
269
302
  return type;
270
303
  }
271
304
 
File without changes