├── BUILD
├── LICENSE.txt
├── README.md
├── examples
├── dnsresolve.js
├── fcntl.js
├── socket.js
├── telnet.js
└── term.js
├── ffi.c
├── test.c
├── test.js
├── test2.js
└── util.mjs
/BUILD:
--------------------------------------------------------------------------------
1 | # BUILD
2 | #
3 | gcc -g -fPIC -DJS_SHARED_LIBRARY -c ffi.c
4 | gcc -g -shared -o ffi.so ffi.o -lffi -ldl
5 | #
6 | gcc -g -fPIC -c test.c
7 | gcc -g -shared -o test.so test.o
8 | #
9 | # ce: .mshell;
10 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2020 Fred Weigel
2 | Fred Weigel
3 |
4 | Permission is hereby granted, free of charge, to any person
5 | obtaining a copy of this software and associated documentation
6 | files (the "Software"), to deal in the Software without
7 | restriction, including without limitation the rights to use,
8 | copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the
10 | Software is furnished to do so, subject to the following
11 | conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23 | OTHER DEALINGS IN THE SOFTWARE.
24 |
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | qjs-ffi
2 | =======
3 |
4 | ## What is it? ##
5 | **qjs-ffi** is a simple interface to ffi from quickjs
6 | .
7 |
8 | libffi and libdl are required. This has only been run on Linux x86_64 (Fedora 31).
9 |
10 | See test.js for simple example and testing. libdl is needed to link to external shared objects (.so files). See the man pages for dlopen, dlerror, dlclose and dlsym.
11 |
12 | ## How to use it? ##
13 | Use dlopen() and dlsym() to get a function pointer to a desired function, use ffidefine() to create a ffi link to the function, then call() to execute the function:
14 |
15 | ```
16 | import { dlsym,
17 | define, call, toString, toArrayBuffer,
18 | RTLD_DEFAULT } from "./ffi.so";
19 |
20 | var fp;
21 | fp = dlsym(RTLD_DEFAULT, "strdup");
22 | define("strdup", fp, null, "char *", "char *");
23 | var p;
24 | p = call("strdup", "hello");
25 | ```
26 | define(name, function_pointer, abi, ret_type, types...)
27 |
28 | name is the name you want to refer to the function as, function_pointer is obtained from dlsym(), abi is abi (if null, use default), ret_type is the return type and types... are the types (prototype).
29 |
30 | n = call(name, params...) calls the function with the parameters.
31 |
32 | s = toString(p) converts a pointer p to a string.
33 |
34 | b = toArrayBuffer(p, n) converts pointer p, length n to ArrayBuffer.
35 |
36 | ## Installation ##
37 | Installing qjs-ffi easy.
38 |
39 | ```
40 | $ ./BUILD
41 | ```
42 |
43 | ## ABI ##
44 |
45 | ABI is the call type for a function pointer. The following values are allowed (define). Note that null is the same as "default". On Windows, "fastcall", "stdcall", "ms_cdecl" and "win64" may be useful.
46 |
47 | * null
48 | * "default"
49 | * "sysv"
50 | * "unix64"
51 | * "stdcall"
52 | * "thiscall"
53 | * "fastcall"
54 | * "ms_cdecl"
55 | * "win64"
56 |
57 | ## TYPES ##
58 | Types define parameter and return types. NOTE: structure passing by value is not yet supported. "void" is only useful as a return type. There are also C-like aliases, and a "string" semantic type. These types are used in define to declare the prototype for each function.
59 |
60 | These are the types from libffi:
61 |
62 | * "void"
63 | * "sint8"
64 | * "sint16"
65 | * "sint32"
66 | * "sint64"
67 | * "uint8"
68 | * "uint16"
69 | * "uint32"
70 | * "uint64"
71 | * "float"
72 | * "double"
73 | * "schar"
74 | * "uchar"
75 | * "sshort"
76 | * "ushort"
77 | * "sint"
78 | * "uint"
79 | * "slong"
80 | * "ulong"
81 | * "longdouble"
82 | * "pointer"
83 |
84 | "C-like" types:
85 |
86 | * "int" ("sint")
87 | * "long" ("slong")
88 | * "short" ("sshort")
89 | * "char" ("schar")
90 | * "size_t" ("uint")
91 | * "unsigned char" ("uchar")
92 | * "unsigned int" ("uint")
93 | * "unsigned long" ("ulong")
94 | * "void *" ("pointer")
95 | * "char *" ("pointer")
96 |
97 | Semantic types:
98 |
99 | * "string" ("pointer", for JavaScript string)
100 | * "buffer" ("pointer", for ArrayBuffer)
101 |
102 | Since call converts JavaScript strings into a pointer to the string data, "string" can be used as a parameter type to indicate that this is the intended behaviour (call with a C string constant).
103 |
104 | ## Default Function Pointer ##
105 | If a null is passed as the function pointer to define, define will convert the null into a pointer to the following C function. This will display "dummy function in ffi" on stderr when fficall is used.
106 |
107 | ```
108 | static int dummy_() {
109 | warn("dummy function in ffi");
110 | return 0;
111 | }
112 | ```
113 |
114 | ## Available imports ##
115 | ```
116 | import { debug, dlopen, dlerror, dlclose, dlsym,
117 | define, call, toString, toArrayBuffer,
118 | errno, JSContext,
119 | RTLD_LAZY, RTLD_NOW, RTLD_GLOBAL, RTLD_LOCAL,
120 | RTLD_NODELETE, RTLD_NOLOAD, RTLD_DEEPBIND,
121 | RTLD_DEFAULT, RTLD_NEXT } from "./ffi.so";
122 | ```
123 |
124 | ## dlopen, dlerror, dlclose, dlsym, errno ##
125 |
126 | These functions are described in the **man** pages. The **man** pages also describe the constant RTLD_* that are available.
127 |
128 | Note that errno() is a function.
129 |
130 | ## define, call, toString, toArrayBuffer ##
131 |
132 | define() defines a prototype for a FFI C function. Given a function pointer, it produces a callable function:
133 |
134 | ```
135 | f = define(name, fp, abi, return, parameters...)
136 | ```
137 | define() returns true or false -- true if the function has been defined, false otherwise. name is a string by which the function will be referenced. fp is a function pointer, usually derived from dlsym(). abi is the type of call (usually null meaning default abi), return is the return type, and parameters are the types of parameters.
138 |
139 | For example:
140 | ```
141 | var malloc;
142 | malloc = dlsym(RTLD_DEFAULT, "malloc");
143 | if (malloc == null)
144 | console.log(dlerror());
145 | else {
146 | if (define("malloc", malloc, null, "void *", "size_t");
147 | console.log("malloc defined");
148 | else
149 | console.log("define failed");
150 | }
151 | ```
152 | Up to 30 parameters can be defined.
153 |
154 | ```
155 | result = call(name, actual parameters...)
156 | ```
157 | call() calls an external function previously defined by define().
158 |
159 | For example:
160 | ```
161 | var p;
162 | p = call("malloc", 10);
163 | ```
164 | call() converts JavaScript strings (eg. "string") into a pointer to the C string. These strings **cannot** be altered by the FFI function. Use malloc to get memory that can be written. ArrayBuffer is converted to pointer as well, and the ArrayBuffer contents *can* be written.
165 |
166 | call() always returns a double. This presumes that **all integers and pointers fit into 52 bits**.
167 |
168 | If call() detects a problem before the actual invocation, it will return an exception. This happens if the function is not yet defined, or a parameter cannot be converted.
169 |
170 | true and false are converted to integer 1 and 0, null to pointer 0 (NULL), integers and float as defined by the types specified in the definition. Strings are copied, and a pointer to the copy is passed. These are the standard conversions. As well, libffi may do additional conversions as needed to execute the call. ArrayBuffer will be converted to a pointer, and the C function can change that memory.
171 |
172 | Some FFI functions will return or produce a pointer to a C string. toString() will convert that pointer into a JavaScript string:
173 | ```
174 | console.log(toString(p));
175 | ```
176 |
177 | If we have a pointer to memory, and a length, toArrayBuffer will create an ArrayBuffer with a copy of the storage.
178 |
179 | ## JSContext ##
180 |
181 | Returns the current JSContext *ctx. This allow functions within the
182 | QuickJS C API to be called from within a js module. This allows for
183 | limited "introspection".
184 |
185 | ## debug ##
186 |
187 | This is provided as a convenience feature, to allow debugging of ffi.so. Since the technique is useful, it is documented here.
188 |
189 | If ffi.so is compiled with debug (-g), debugging with gdb can be done:
190 | ```
191 | $ gdb qjs
192 | (gdb) set args test.js
193 | (gdb) b js_debug
194 | Function "js_debug" not defined.
195 | Make breakpoint pending on future shared library load? (y or [n]) y
196 | Breakpoint 1 (js_debug) pending.
197 | (gdb) run
198 |
199 | Breakpoint 1, js_debug (ctx=0x4d82d0, this_val=..., argc=0,
200 | argv=0x7fffffffca10) at ffi.c:494
201 | 494 return JS_NULL;
202 | (gdb)
203 | ```
204 | The breakpoint is triggered on the first call to debug() in the JavaScript.
205 |
206 | Breakpoints can then be set in other shared objects.
207 |
208 | ## Changes ##
209 |
210 | * Wed Jan 22 10:34:55 EST 2020
211 | * Note endian in Limitations
212 | * ArrayBuffer can be passed (converted to pointer like string)
213 | * Add ffitoarraybuffer(p, size)
214 | * Add type "buffer"
215 | * Add errno function
216 | * Only publish RTLD_ constants if available
217 | * Fri Jan 24 11:57:09 EST 2020
218 | * Add JSContext() to allow "introspective" functions
219 | * Rename ffidefine to define, fficall to call, ffitostring to toString and ffitoarraybuffer to toArrayBuffer
220 | * Add util.mjs and test2.js to illustrate how to use ffi a bit better.
221 |
222 |
223 | ## Limitations ##
224 |
225 | * Only **double** is returned
226 | * No structure pass by value
227 | * No C to JavaScript (without specific code for this, C function qsort() cannot be used with a JavaScript comparision function, for example).
228 | * Only little-endian (I don't have a big-endian test system)
229 |
230 | ## TODO ##
231 |
232 | ffi.so is useful, but some features would be worthwhile to add. I haven't needed these as yet (YAGNI)
233 |
234 | * JavaScript function to C function pointer
235 | * C structure access via pointer -- define setters/getters by type
236 | * Define and pass structures by value (new "types")
237 | * Expand return to return true 64 bit integers and pointers
238 | * Allow other endian
239 |
--------------------------------------------------------------------------------
/examples/dnsresolve.js:
--------------------------------------------------------------------------------
1 | import { strerror, err, out, exit, open, loadFile } from 'std';
2 | import { read, signal, ttySetRaw, write } from 'os';
3 | import { errno, toString, toArrayBuffer, toPointer, argSize, ptrSize } from 'ffi';
4 | import { Socket, socket, socklen_t, AF_INET, SOCK_STREAM, IPPROTO_UDP, ndelay, connect, sockaddr_in, select, fd_set, timeval, FD_SET, FD_CLR, FD_ISSET, FD_ZERO, errnos, send, recv } from './socket.js';
5 | import { termios, tcgetattr, tcsetattr, TCSANOW, IGNPAR, IMAXBEL, IUTF8, OPOST, ONLCR, CR0, TAB0, BS0, VT0, FF0, EXTB, CS8, CREAD, ISIG, ECHOE, ECHOK, ECHOCTL, ECHOKE, VINTR, cfgetospeed, cfsetospeed, B57600, B115200 } from './term.js';
6 |
7 | function not(n) {
8 | return ~n >>> 0;
9 | }
10 |
11 | const STDIN_FILENO = 0, STDOUT_FILENO = 1, STDERR_FILENO = 2;
12 |
13 | let log = err;
14 |
15 | function debug(fmt, ...args) {
16 | log.printf(fmt + '\n', ...args);
17 | log.flush();
18 | }
19 |
20 | function main(...args) {
21 | if(/^-o/.test(args[0])) {
22 | let arg = args[0].length == 2 ? (args.shift(), args.shift()) : args.shift().slice(2);
23 | log = open(arg, 'a+');
24 | } else {
25 | log = open('debug.log', 'w+');
26 | }
27 |
28 | debug('%s started (%s) [%s]', scriptArgs[0].replace(/.*\//g, ''), args, new Date().toISOString());
29 |
30 | const resolvConf = loadFile('/etc/resolv.conf');
31 | const servers = resolvConf .split(/\n/g) .filter(l => /^\s*nameserver/.test(l)) .map(l => l.replace(/^\s*nameserver\s*([^\s]+).*/g, '$1'));
32 |
33 | debug('servers: %s', servers.join('\n'));
34 |
35 | const addr = servers[0], port = 53;
36 |
37 | debug('addr: %s, port: %u', addr, port);
38 |
39 | for(let arg of args) {
40 | out.printf('%s -> %s\n', arg, lookup(arg));
41 | }
42 |
43 | function lookup(domain) {
44 | let local = new sockaddr_in(AF_INET, Math.floor(Math.random() * 65535 - 1024) + 1024, '0.0.0.0');
45 |
46 | let remote = new sockaddr_in();
47 |
48 | remote.sin_family = AF_INET;
49 | remote.sin_port = port;
50 | remote.sin_addr = addr;
51 |
52 | let sock = new Socket(IPPROTO_UDP);
53 | debug('socket() fd = %d', +sock);
54 |
55 | let ret = sock.bind(local);
56 | ReturnValue(ret, `sock.bind(${local})`);
57 |
58 | /*ret = sock.connect(addr, port);
59 |
60 | ReturnValue(ret, `sock.connect(${addr}, ${port})`);*/
61 |
62 | let inLen = 0, inBuf = new ArrayBuffer(128);
63 | let outLen = 0, outBuf = new ArrayBuffer(1024);
64 |
65 | const rfds = new fd_set();
66 | const wfds = new fd_set();
67 |
68 | outLen = Copy(new Uint8Array(outBuf), [
69 | 0xff,
70 | 0xff,
71 | 0x01,
72 | 0x00,
73 | 0x00,
74 | 0x01,
75 | 0x00,
76 | 0x00,
77 | 0x00,
78 | 0x00,
79 | 0x00,
80 | 0x00,
81 | ...ToDomain(domain),
82 | 0x00,
83 | 0x00,
84 | 0x01,
85 | 0x00,
86 | 0x01
87 | ]);
88 | new DataView(outBuf).setUint16(0, outLen - 2, false);
89 |
90 | do {
91 | FD_ZERO(rfds);
92 | FD_ZERO(wfds);
93 | FD_CLR(+sock, wfds);
94 |
95 | FD_SET(+sock, rfds);
96 |
97 | if(outLen) FD_SET(+sock, wfds);
98 | else if(inLen < inBuf.byteLength) FD_SET(+sock, rfds);
99 |
100 | const timeout = new timeval(5, 0);
101 | //console.log('select:', sock + 1);
102 |
103 | ret = select(sock + 1, rfds, wfds, null, timeout);
104 |
105 | if(FD_ISSET(+sock, wfds)) {
106 | if(outLen > 0) {
107 | //console.log('outBuf:', BufferToString(outBuf));
108 | if(sock.sendto(outBuf, outLen, 0, remote) > 0) {
109 | outLen = 0;
110 | }
111 | }
112 | }
113 |
114 | if(FD_ISSET(+sock, rfds)) {
115 | let length;
116 | debug('socket readable %s %u', remote, remote.byteLength);
117 |
118 | const data = new ArrayBuffer(1024);
119 | const slen = new socklen_t(remote.byteLength);
120 | length = sock.recvfrom(data, data.byteLength, 0, remote, slen);
121 |
122 | if(length > 0) {
123 | let u8 = new Uint8Array(data, 0, length);
124 | let header = new DataView(data, 0, 12);
125 | debug('Num answers: %u', header.getUint16(6, false));
126 | let addr = u8.slice(-4).join('.');
127 | debug('Received data from socket: %s', ArrayToBytes(u8));
128 |
129 | sock.close();
130 | return addr;
131 | }
132 | console.log(`Received ${length} bytes from socket`);
133 | }
134 | } while(!sock.destroyed);
135 | }
136 |
137 | debug('end');
138 | }
139 |
140 | function ReturnValue(ret, ...args) {
141 | const r = [-1, 0].indexOf(ret) != -1 ? ret + '' : '0x' + NumberToHex(ret, ptrSize * 2);
142 | debug('%s ret = %s%s%s', args, r, ...(ret == -1 ? [' errno =', errno(), ' error =', strerror(errno())] : ['', '']));
143 | }
144 |
145 | function NumberToHex(n, b = 2) {
146 | let s = (+n).toString(16);
147 | return '0'.repeat(Math.ceil(s.length / b) * b - s.length) + s;
148 | }
149 | /*
150 | function EscapeString(str) {
151 | let r = '';
152 | let codeAt = typeof str == 'string' ? i => str.charCodeAt(i) : i => str[i];
153 | for(let i = 0; i < str.length; i++) {
154 | const code = codeAt(i);
155 |
156 | if(code == 0x0a) r += '\\n';
157 | else if(code == 0x0d) r += '\\r';
158 | else if(code == 0x09) r += '\\t';
159 | else if(code <= 3) r += '\\0';
160 | else if(code < 32 || code >= 128)
161 | r += `\\${('00' + code.toString(8)).slice(-3)}`;
162 | else r += str[i];
163 | }
164 | return r;
165 | }
166 | */
167 | function BufferToArray(buf, offset, length) {
168 | let len, arr = new Uint8Array(buf, offset !== undefined ? offset : 0, length !== undefined ? length : buf.byteLength);
169 | // arr = [...arr];
170 | if((len = arr.indexOf(0)) != -1) arr = arr.slice(0, len);
171 | return arr;
172 | }
173 |
174 | function BufferToString(buf, offset, length) {
175 | return BufferToArray(buf, offset, length).reduce((s, code) => s + String.fromCharCode(code), '');
176 | }
177 |
178 | function BufferToBytes(buf, offset = 0, len) {
179 | const u8 = new Uint8Array(buf, typeof offset == 'number' ? offset : 0, typeof len == 'number' ? len : buf.byteLength );
180 | return ArrayToBytes(u8);
181 | }
182 |
183 | function ArrayToBytes(arr, delim = ', ', bytes = 1) {
184 | return ('[' +
185 | arr.reduce((s, code) =>
186 | (s != '' ? s + delim : '') + '0x' + ('000000000000000' + code.toString(16)).slice(-(bytes * 2)),
187 | ''
188 | ) +
189 | ']'
190 | );
191 | }
192 |
193 | function AvailableBytes(buf, numBytes) {
194 | return buf.byteLength - numBytes;
195 | }
196 |
197 | function Copy(dst, src, len) {
198 | if(len === undefined) len = src.length;
199 | if(dst.length < len) throw new RangeError(`dst.length (${dst.length}) < len (${len})`);
200 | for(let i = 0; i < len; i++) dst[i] = src[i];
201 | return len;
202 | }
203 |
204 | function ToDomain(str, alpha = false) {
205 | return str
206 | .split('.')
207 | .reduce(alpha
208 | ? (a, s) => a + String.fromCharCode(s.length) + s
209 | : (a, s) => a.concat([s.length, ...s.split('').map(ch => ch.charCodeAt(0))]),
210 | alpha ? '' : []
211 | );
212 | }
213 |
214 | function Append(buf, numBytes, ...chars) {
215 | let n = chars.reduce((a, c) => (typeof c == 'number' ? a + 1 : a + c.length), 0);
216 | if(AvailableBytes(buf, numBytes) < n) buf = CloneBuf(buf, numBytes + n);
217 | let a = new Uint8Array(buf, numBytes, n);
218 | let p = 0;
219 | for(let i = 0; i < chars.length; i++) {
220 | if(typeof chars[i] == 'number') {
221 | a[p++] = chars[i];
222 | } else if(typeof chars[i] == 'string') {
223 | const s = chars[i];
224 | const m = s.length;
225 | for(let j = 0; j < m; j++) a[p++] = s[j].charCodeAt(0);
226 | }
227 | }
228 | return [buf, numBytes + n];
229 | }
230 |
231 | function Dump(buf, numBytes) {
232 | return BufferToBytes(numBytes !== undefined ? buf.slice(0, numBytes) : buf);
233 | } /*
234 |
235 | function CloneBuf(buf, newLen) {
236 | let n = newLen !== undefined ? newLen : buf.byteLength;
237 | let p = toPointer(buf);
238 | return toArrayBuffer(p, n);
239 | }
240 |
241 | function Once(fn, thisArg) {
242 | let ran = false;
243 | let ret;
244 |
245 | return function(...args) {
246 | if(!ran) {
247 | ret = fn.call(thisArg, ...args);
248 | ran = true;
249 | }
250 | return ret;
251 | };
252 | }
253 | */
254 | function StringToBuffer(str) {
255 | return Uint8Array.from(str.split('').map(ch => ch.charCodeAt(0))).buffer;
256 | }
257 |
258 | main(...scriptArgs.slice(1));
259 |
--------------------------------------------------------------------------------
/examples/fcntl.js:
--------------------------------------------------------------------------------
1 | import { dlsym, define, call, RTLD_DEFAULT } from 'ffi.so';
2 |
3 | export const F_DUPFD = 0;
4 | export const F_GETFD = 1;
5 | export const F_SETFD = 2;
6 | export const F_GETFL = 3;
7 | export const F_SETFL = 4;
8 | export const F_GETLK = 5;
9 | export const F_SETLK = 6;
10 | export const F_SETLKW = 7;
11 | export const F_GETLK64 = 8;
12 | export const F_SETLK64 = 9;
13 | export const F_SETLKW64 = 10;
14 | export const F_GETOWN = 11;
15 | export const F_SETOWN = 12;
16 | export const F_SETSIG = 13;
17 | export const F_GETSIG = 14;
18 | export const O_RDONLY = 0x0;
19 | export const O_WRONLY = 0x1;
20 | export const O_RDWR = 0x2;
21 | export const O_ACCMODE = 0x3;
22 | export const O_CREAT = 0x40;
23 | export const O_EXCL = 0x80;
24 | export const O_NOCTTY = 0x100;
25 | export const O_TRUNC = 0x200;
26 | export const O_APPEND = 0x400;
27 | export const O_NDELAY = 0x800;
28 | export const O_NONBLOCK = 0x800;
29 | export const O_DSYNC = 0x1000;
30 | export const O_ASYNC = 0x2000;
31 | export const O_DIRECTORY = 0x10000;
32 | export const O_NOFOLLOW = 0x20000;
33 | export const O_CLOEXEC = 0x80000;
34 | export const O_RSYNC = 0x101000;
35 | export const O_SYNC = 0x101000;
36 | export const O_LARGEFILE = 0x8000;
37 | export const O_NOATIME = 0x00040000;
38 |
39 | const fp = dlsym(RTLD_DEFAULT, 'fcntl');
40 |
41 | define('fcntl', fp, null, 'int', 'int', 'int', 'int');
42 |
43 | export function fcntl(fd, cmd, arg) {
44 | return call('fcntl', fd, cmd, arg);
45 | }
46 |
47 | export default fcntl;
48 |
--------------------------------------------------------------------------------
/examples/socket.js:
--------------------------------------------------------------------------------
1 | import { Error, strerror } from 'std';
2 | import { read, write, close, setReadHandler, setWriteHandler } from 'os';
3 | import { O_NONBLOCK, F_GETFL, F_SETFL, fcntl } from './fcntl.js';
4 | import { debug, dlopen, define, dlerror, dlclose, dlsym, call, toString, toArrayBuffer, errno, JSContext, RTLD_LAZY, RTLD_NOW, RTLD_GLOBAL, RTLD_LOCAL, RTLD_NODELETE, RTLD_NOLOAD, RTLD_DEEPBIND, RTLD_DEFAULT, RTLD_NEXT, argSize, ptrSize } from 'ffi';
5 |
6 | function foreign(name, ret, ...args) {
7 | let fp = dlsym(RTLD_DEFAULT, name);
8 | define(name, fp, null, ret, ...args);
9 | return (...args) => call(name, ...args);
10 | }
11 | export let FD_SETSIZE = 1024;
12 |
13 | export const SOCK_STREAM = 1; /* stream (connection) socket */
14 | export const SOCK_DGRAM = 2; /* datagram (conn.less) socket */
15 | export const SOCK_RAW = 3; /* raw socket */
16 | export const SOCK_RDM = 4; /* reliably-delivered message */
17 | export const SOCK_SEQPACKET = 5; /* sequential packet socket */
18 | export const SOCK_DCCP = 6; /* Datagram Congestion Control Protocol socket */
19 | export const SOCK_PACKET = 10; /* linux specific way of */
20 |
21 | export const AF_UNIX = 1; /* Unix domain sockets */
22 | export const AF_LOCAL = 1; /* POSIX name for AF_UNIX */
23 | export const AF_INET = 2; /* Internet IP Protocol */
24 | export const AF_AX25 = 3; /* Amateur Radio AX.25 */
25 | export const AF_IPX = 4; /* Novell IPX */
26 | export const AF_APPLETALK = 5; /* AppleTalk DDP */
27 | export const AF_NETROM = 6; /* Amateur Radio NET/ROM */
28 | export const AF_BRIDGE = 7; /* Multiprotocol bridge */
29 | export const AF_ATMPVC = 8; /* ATM PVCs */
30 | export const AF_X25 = 9; /* Reserved for X.25 project */
31 | export const AF_INET6 = 10; /* IP version 6 */
32 |
33 | export const IPPROTO_ROUTING = 43; /* IPv6 routing header */
34 | export const IPPROTO_FRAGMENT = 44; /* IPv6 fragmentation header */
35 | export const IPPROTO_ICMPV6 = 58; /* ICMPv6 */
36 | export const IPPROTO_NONE = 59; /* IPv6 no next header */
37 | export const IPPROTO_DSTOPTS = 60; /* IPv6 destination options */
38 |
39 | export const IPPROTO_IP = 0;
40 | export const IPPROTO_ICMP = 1; /* Internet Control Message Protocol */
41 | export const IPPROTO_IGMP = 2; /* Internet Group Management Protocol */
42 | export const IPPROTO_IPIP = 4; /* IPIP tunnels (older KA9Q tunnels use 94) */
43 | export const IPPROTO_TCP = 6; /* Transmission Control Protocol */
44 | export const IPPROTO_EGP = 8; /* Exterior Gateway Protocol */
45 | export const IPPROTO_PUP = 12; /* PUP protocol */
46 | export const IPPROTO_UDP = 17; /* User Datagram Protocol */
47 | export const IPPROTO_IDP = 22; /* XNS IDP protocol */
48 | export const IPPROTO_RSVP = 46; /* RSVP protocol */
49 | export const IPPROTO_GRE = 47; /* Cisco GRE tunnels (rfc 1701,1702) */
50 | export const IPPROTO_IPV6 = 41; /* IPv6-in-IPv4 tunnelling */
51 | export const IPPROTO_PIM = 103; /* Protocol Independent Multicast */
52 | export const IPPROTO_ESP = 50; /* Encapsulation Security Payload protocol */
53 | export const IPPROTO_AH = 51; /* Authentication Header protocol */
54 | export const IPPROTO_COMP = 108; /* Compression Header protocol */
55 | export const IPPROTO_SCTP = 132; /* Stream Control Transmission Protocol. */
56 | export const IPPROTO_UDPLITE = 136; /* UDP-Lite protocol. */
57 | export const IPPROTO_RAW = 255; /* Raw IP packets */
58 |
59 | export const EPERM = 1;
60 | export const ENOENT = 2;
61 | export const EINTR = 4;
62 | export const EBADF = 9;
63 | export const EAGAIN = 11;
64 | export const ENOMEM = 12;
65 | export const EACCES = 13;
66 | export const EFAULT = 14;
67 | export const ENOTDIR = 20;
68 | export const EINVAL = 22;
69 | export const ENFILE = 23;
70 | export const EMFILE = 24;
71 | export const EROFS = 30;
72 | export const ENAMETOOLONG = 36;
73 | export const ELOOP = 40;
74 | export const ENOTSOCK = 88;
75 | export const EPROTOTYPE = 91;
76 | export const EPROTONOSUPPORT = 93;
77 | export const EOPNOTSUPP = 95;
78 | export const EAFNOSUPPORT = 97;
79 | export const EADDRINUSE = 98;
80 | export const EADDRNOTAVAIL = 99;
81 | export const ENETUNREACH = 101;
82 | export const ENOBUFS = 105;
83 | export const EISCONN = 106;
84 | export const ETIMEDOUT = 110;
85 | export const ECONNREFUSED = 111;
86 | export const EALREADY = 114;
87 | export const EINPROGRESS = 115;
88 |
89 | export const SO_DEBUG = 1;
90 | export const SO_REUSEADDR = 2;
91 | export const SO_TYPE = 3;
92 | export const SO_ERROR = 4;
93 | export const SO_DONTROUTE = 5;
94 | export const SO_BROADCAST = 6;
95 | export const SO_SNDBUF = 7;
96 | export const SO_RCVBUF = 8;
97 | export const SO_KEEPALIVE = 9;
98 | export const SO_OOBINLINE = 10;
99 | export const SO_NO_CHECK = 11;
100 | export const SO_PRIORITY = 12;
101 | export const SO_LINGER = 13;
102 | export const SO_BSDCOMPAT = 14;
103 | export const SO_REUSEPORT = 15;
104 | export const SO_PASSCRED = 16;
105 | export const SO_PEERCRED = 17;
106 | export const SO_RCVLOWAT = 18;
107 | export const SO_SNDLOWAT = 19;
108 | export const SO_RCVTIMEO = 20;
109 | export const SO_SNDTIMEO = 21;
110 | export const SO_ACCEPTCONN = 30;
111 | export const SO_SECURITY_AUTHENTICATION = 22;
112 | export const SO_SECURITY_ENCRYPTION_TRANSPORT = 23;
113 | export const SO_SECURITY_ENCRYPTION_NETWORK = 24;
114 | export const SO_BINDTODEVICE = 25;
115 | export const SO_ATTACH_FILTER = 26;
116 | export const SO_DETACH_FILTER = 27;
117 | export const SO_PEERNAME = 28;
118 | export const SO_TIMESTAMP = 29;
119 |
120 | export const SOL_SOCKET = 1;
121 |
122 | /* prettier-ignore */
123 | const syscall = { socket: foreign('socket', 'int', 'int', 'int', 'int'), select: foreign( 'select', 'int', 'int', 'buffer', 'buffer', 'buffer', 'buffer' ), connect: foreign('connect', 'int', 'int', 'void *', 'size_t'), bind: foreign('bind', 'int', 'int', 'void *', 'size_t'), listen: foreign('listen', 'int', 'int', 'int'), accept: foreign('accept', 'int', 'int', 'buffer', 'buffer'), getsockopt: foreign( 'getsockopt', 'int', 'int', 'int', 'int', 'void *', 'buffer' ), setsockopt: foreign( 'setsockopt', 'int', 'int', 'int', 'int', 'void *', 'size_t' ), recv: foreign('recv', 'int', 'int', 'buffer', 'size_t', 'int'), recvfrom: foreign( 'recvfrom', 'int', 'int', 'buffer', 'size_t', 'int', 'buffer', 'buffer' ), send: foreign('send', 'int', 'int', 'buffer', 'size_t', 'int'), sendto: foreign( 'sendto', 'int', 'int', 'buffer', 'size_t', 'int', 'buffer', 'size_t' ) };
124 |
125 | export const errnos = Object.fromEntries(Object.getOwnPropertyNames(Error).map(name => [Error[name], name]));
126 |
127 | export function socket(af = AF_INET, type = SOCK_STREAM, proto = IPPROTO_IP) {
128 | return syscall.socket(af, type, proto);
129 | }
130 |
131 | export function ndelay(fd, on = true) {
132 | let flags = fcntl(+fd, F_GETFL);
133 |
134 | if(on) flags |= O_NONBLOCK;
135 | else flags &= ~O_NONBLOCK;
136 |
137 | return fcntl(+fd, F_SETFL, flags);
138 | }
139 |
140 | export function connect(fd, addr, addrlen) {
141 | addrlen = typeof addrlen == 'number' ? addrlen : addr.byteLength;
142 |
143 | return syscall.connect(+fd, addr, addrlen);
144 | }
145 |
146 | export function bind(fd, addr, addrlen) {
147 | addrlen = typeof addrlen == 'number' ? addrlen : addr.byteLength;
148 |
149 | return syscall.bind(+fd, addr, addrlen);
150 | }
151 |
152 | export function accept(fd, addr, addrlen) {
153 | if(addr === undefined) addr = null;
154 | if(addrlen === undefined) addrlen = null;
155 |
156 | return syscall.accept(+fd, addr, addrlen);
157 | }
158 |
159 | export function listen(fd, backlog = 5) {
160 | return syscall.listen(+fd, backlog);
161 | }
162 |
163 | export function recv(fd, buf, offset, len, flags = 0) {
164 | if(typeof buf == 'object' && typeof buf.buffer == 'object') buf = buf.buffer;
165 | if(offset === undefined) offset = 0;
166 | if(len === undefined) len = buf.byteLength;
167 | return syscall.recv(+fd, buf, offset, len, flags);
168 | }
169 |
170 | export function send(fd, buf, offset, len, flags = 0) {
171 | if(typeof buf == 'string') buf = StringToArrayBuffer(buf);
172 | else if(typeof buf.buffer == 'object') buf = buf.buffer;
173 | if(offset === undefined) offset = 0;
174 | if(len === undefined) len = buf.byteLength;
175 | return syscall.send(+fd, buf, offset, len, flags);
176 | }
177 |
178 | export function select(nfds, readfds = null, writefds = null, exceptfds = null, timeout = null) {
179 | if(!(typeof nfds == 'number')) {
180 | let maxfd = Math.max(...[readfds, writefds, exceptfds].filter(s => s instanceof fd_set).map(s => s.maxfd));
181 | nfds = maxfd + 1;
182 | }
183 | return syscall.select(nfds, readfds, writefds, exceptfds, timeout);
184 | }
185 |
186 | export function getsockopt(sockfd, level, optname, optval, optlen) {
187 | return syscall.getsockopt(sockfd, level, optname, optval, optlen || optval.byteLength);
188 | }
189 |
190 | export function setsockopt(sockfd, level, optname, optval, optlen) {
191 | return syscall.setsockopt(sockfd, level, optname, optval, optlen || optval.byteLength);
192 | }
193 |
194 | export class timeval extends ArrayBuffer {
195 | static arrType = ptrSize == 8 ? BigUint64Array : Uint32Array;
196 | static numType = ptrSize == 8 ? BigInt : n => n;
197 |
198 | constructor(sec, usec) {
199 | super(ptrSize * 2);
200 |
201 | if(sec !== undefined || usec !== undefined) {
202 | let a = new timeval.arrType(this);
203 | a[0] = timeval.numType(sec);
204 | a[1] = timeval.numType(usec);
205 | }
206 | }
207 |
208 | set tv_sec(s) { new timeval.arrType(this)[0] = timeval.numType(s); }
209 | get tv_sec() { return new timeval.arrType(this)[0]; }
210 | set tv_usec(us) { new timeval.arrType(this)[1] = timeval.numType(us); }
211 | get tv_usec() { return new timeval.arrType(this)[1]; }
212 |
213 | toString() {
214 | const { tv_sec, tv_usec } = this;
215 | return `{ .tv_sec = ${tv_sec}, .tv_usec = ${tv_usec} }`;
216 | }
217 | }
218 |
219 | export class sockaddr_in extends ArrayBuffer {
220 | constructor(family, port, addr) {
221 | super(16);
222 |
223 | this.sin_family = family;
224 | this.sin_port = port;
225 | this.sin_addr = addr;
226 | }
227 |
228 | [Symbol.toPrimitive](hint) {
229 | return this.toString();
230 | }
231 |
232 | toString() {
233 | return `${this.sin_addr}:${this.sin_port}`;
234 | }
235 |
236 | get [Symbol.toStringTag]() {
237 | const { sin_family, sin_port, sin_addr } = this;
238 | return `{ .sin_family = ${sin_family}, .sin_port = ${sin_port}, .sin_addr = ${sin_addr} }`;
239 | }
240 | }
241 |
242 | Object.defineProperties(sockaddr_in.prototype, {
243 | sin_family: {
244 | set(af) { new Uint16Array(this)[0] = af; },
245 | get() { return new Uint16Array(this)[0]; },
246 | enumerable: true
247 | },
248 | sin_port: {
249 | set(port) { new DataView(this, 2).setUint16(0, port, false); },
250 | get() { return new DataView(this, 2).getUint16(0, false); },
251 | enumerable: true
252 | },
253 | sin_addr: {
254 | set(addr) {
255 | if (typeof addr == 'string') addr = addr.split(/[.:]/).map(n => +n);
256 | if (addr instanceof Array) {
257 | let a = new Uint8Array(this, 4);
258 | a[0] = addr[0];
259 | a[1] = addr[1];
260 | a[2] = addr[2];
261 | a[3] = addr[3];
262 | } else {
263 | new DataView(this, 4).setUint32(0, addr, false);
264 | }
265 | },
266 | get() {
267 | return new Uint8Array(this, 4, 4).join('.');
268 | },
269 | enumerable: true
270 | }
271 | });
272 |
273 | export class fd_set extends ArrayBuffer {
274 | constructor() {
275 | super(FD_SETSIZE / 8);
276 | }
277 |
278 | get size() { return this.byteLength * 8; }
279 | get maxfd() { const a = this.array; return a[a.length - 1]; }
280 |
281 | get array() {
282 | const a = new Uint8Array(this);
283 | const n = a.byteLength;
284 | const r = [];
285 | for(let i = 0; i < n; i++) for (let j = 0; j < 8; j++) if(a[i] & (1 << j)) r.push(i * 8 + j);
286 | return r;
287 | }
288 |
289 | toString() {
290 | return `[ ${this.array.join(', ')} ]`;
291 | }
292 | }
293 |
294 | export class socklen_t extends ArrayBuffer {
295 | constructor(v) {
296 | super(4);
297 | if(v != undefined) new Uint32Array(this)[0] = v | 0;
298 | }
299 |
300 | [Symbol.toPrimitive](hint) {
301 | return new Uint32Array(this)[0];
302 | }
303 |
304 | [Symbol.toStringTag] = `[object socklen_t]`;
305 | }
306 |
307 | export function FD_SET(fd, set) {
308 | new Uint8Array(set, fd >> 3, 1)[0] |= 1 << (fd & 0x7);
309 | }
310 |
311 | export function FD_CLR(fd, set) {
312 | new Uint8Array(set, fd >> 3, 1)[0] &= ~(1 << (fd & 0x7));
313 | }
314 |
315 | export function FD_ISSET(fd, set) {
316 | return !!(new Uint8Array(set, fd >> 3, 1)[0] & (1 << (fd & 0x7)));
317 | }
318 |
319 | export function FD_ZERO(fd, set) {
320 | const a = new Uint8Array(set);
321 | const n = a.length;
322 | for(let i = 0; i < n; i++) a[i] = 0;
323 | }
324 |
325 | export class Socket {
326 | constructor(proto = IPPROTO_IP) {
327 | this.type = [IPPROTO_UDP, SOCK_DGRAM].indexOf(proto) != -1 ? SOCK_DGRAM : SOCK_STREAM;
328 | this.fd = socket(this.family, this.type, proto);
329 | this.remote = new sockaddr_in(this.family);
330 | this.local = new sockaddr_in(this.family);
331 | this.pending = true;
332 | }
333 |
334 | set remoteFamily(family) { this.remote.sin_family = family; }
335 | get remoteFamily() { return this.remote.sin_family; }
336 | set remoteAddress(a) { this.remote.sin_addr = a; }
337 | get remoteAddress() { return this.remote.sin_addr; }
338 | set remotePort(n) { this.remote.sin_port = n; }
339 | get remotePort() { return this.remote.sin_port; }
340 | set localFamily(family) { this.local.sin_family = family; }
341 | get localFamily() { return this.local.sin_family; }
342 | set localAddress(a) { this.local.sin_addr = a; }
343 | get localAddress() { return this.local.sin_addr; }
344 | set localPort(n) { this.local.sin_port = n; }
345 | get localPort() { return this.local.sin_port; }
346 |
347 | connect(addr, port) {
348 | let ret;
349 | if(addr != undefined) this.remoteAddress = addr;
350 | if(port != undefined) this.remotePort = port;
351 | if((ret = connect(this.fd, this.remote, this.remote.byteLength)) == -1) {
352 | this.errno = syscall.errno;
353 | if(this.errno == EINPROGRESS) this.connecting = true;
354 | }
355 | return ret;
356 | }
357 |
358 | bind(addr, port) {
359 | let ret;
360 | if(addr != undefined) this.localAddress = addr;
361 | if(port != undefined) this.localPort = port;
362 | setsockopt(this.fd, SOL_SOCKET, SO_REUSEADDR, new socklen_t(1));
363 | if((ret = bind(this.fd, this.local, this.local.byteLength)) == -1) this.errno = syscall.errno;
364 | return ret;
365 | }
366 |
367 | listen(backlog = 5) {
368 | let ret;
369 | if((ret = listen(this.fd, backlog)) == -1) this.errno = syscall.errno;
370 | return ret;
371 | }
372 |
373 | accept(remote = new sockaddr_in(this.family)) {
374 | let len = new socklen_t(remote.byteLength);
375 | let ret = accept(this.fd, remote, len);
376 |
377 | if(ret == -1) this.errno = syscall.errno;
378 | else
379 | ret = Object.create(Socket.prototype, {
380 | fd: { v: ret, enumerable: true },
381 | local: { v: this.local, enumerable: true },
382 | remote: { v: remote, enumerable: true }
383 | });
384 | return ret;
385 | }
386 |
387 | read(...args) {
388 | let ret;
389 | const [buf, offset, len] = args;
390 | if(args.length == 0 || typeof buf != 'object') {
391 | let data = new ArrayBuffer(typeof buf == 'number' ? buf : 1024);
392 | if((ret = this.read(data)) > 0) return data.slice(0, ret);
393 | } else if((ret = read(this.fd, buf, offset, len)) <= 0) {
394 | if(ret < 0) this.errno = syscall.errno;
395 | else if(ret == 0) this.close();
396 | }
397 | return ret;
398 | }
399 |
400 | write(buf, offset, len) {
401 | let ret;
402 | if((ret = write(this.fd, buf, offset, len)) == -1) this.errno = syscall.errno;
403 | return ret;
404 | }
405 |
406 | recvfrom(buf, len, flags = 0, src_addr = null, addrlen = null) {
407 | return syscall.recvfrom(this.fd, buf, len, flags, src_addr, addrlen);
408 | }
409 |
410 | sendto(buf, len, flags = 0, dest_addr = null, addrlen) {
411 | return syscall.sendto(this.fd,
412 | buf,
413 | len,
414 | flags,
415 | dest_addr,
416 | addrlen === undefined ? dest_addr.byteLength : addrlen
417 | );
418 | }
419 |
420 | close() {
421 | close(this.fd);
422 | this.destroyed = true;
423 | }
424 |
425 | valueOf() {
426 | return this.fd;
427 | }
428 | }
429 |
430 | Object.assign(Socket.prototype, {
431 | family: AF_INET,
432 | connecting: false,
433 | destroyed: false,
434 | pending: false,
435 | remote: null,
436 | local: null
437 | });
438 |
--------------------------------------------------------------------------------
/examples/telnet.js:
--------------------------------------------------------------------------------
1 | import { strerror, err, out, exit, open } from 'std';
2 | import { read, signal, ttySetRaw, write } from 'os';
3 | import { errno, toString, toArrayBuffer, toPointer, argSize, ptrSize } from 'ffi';
4 | import { Socket, socket, AF_INET, SOCK_STREAM, ndelay, connect, sockaddr_in, select, fd_set, timeval, FD_SET, FD_CLR, FD_ISSET, FD_ZERO, errnos, send, recv } from './socket.js';
5 | import {
6 | termios,
7 | tcgetattr,
8 | tcsetattr,
9 | TCSANOW,
10 | IGNPAR,
11 | IMAXBEL,
12 | IUTF8,
13 | OPOST,
14 | ONLCR,
15 | CR0,
16 | TAB0,
17 | BS0,
18 | VT0,
19 | FF0,
20 | EXTB,
21 | CS8,
22 | CREAD,
23 | ISIG,
24 | ECHOE,
25 | ECHOK,
26 | ECHOCTL,
27 | ECHOKE,
28 | VINTR,
29 | cfgetospeed,
30 | cfsetospeed,
31 | B57600,
32 | B115200
33 | } from './term.js';
34 |
35 | function not(n) {
36 | return ~n >>> 0;
37 | }
38 |
39 | const STDIN_FILENO = 0,
40 | STDOUT_FILENO = 1,
41 | STDERR_FILENO = 2;
42 |
43 | let tattr = new termios();
44 | let log = err;
45 |
46 | function debug(fmt, ...args) {
47 | log.printf(fmt + '\n', ...args);
48 | log.flush();
49 | }
50 |
51 | function main(...args) {
52 | if (/^-o/.test(args[0])) {
53 | let arg = args[0].length == 2 ? (args.shift(), args.shift()) : args.shift().slice(2);
54 | log = open(arg, 'a+');
55 | }
56 |
57 | debug('%s started (%s) [%s]', scriptArgs[0].replace(/.*\//g, ''), args, new Date().toISOString());
58 |
59 | const SetupTerm = Once(() => {
60 | //ttySetRaw(STDIN_FILENO);
61 | ReturnValue(tcgetattr(STDIN_FILENO, tattr), 'tcgetattr');
62 | debug('tattr: %s', tattr);
63 | tattr.c_lflag &= not(ISIG | ECHOE | ECHOK | ECHOCTL | ECHOKE);
64 |
65 | ReturnValue(tcsetattr(STDIN_FILENO, TCSANOW, tattr), 'tcsetattr');
66 | ReturnValue(cfsetospeed(tattr, B115200), 'cfsetospeed');
67 | ReturnValue(cfgetospeed(tattr), 'cfgetospeed');
68 | });
69 |
70 | const listen = !!(args[0] == '-l' && args.shift());
71 |
72 | const [addr = '213.136.8.188', port = 23] = args;
73 |
74 | debug('addr: %s, port: %u', addr, port);
75 |
76 | let sock = new Socket();
77 | let conn;
78 | debug('socket() fd = %d', +sock);
79 |
80 | let ret;
81 |
82 | if (listen) {
83 | ret = sock.bind(addr, port);
84 | ReturnValue(ret, `sock.bind(${addr}, ${port})`);
85 | ret = sock.listen();
86 | ReturnValue(ret, `sock.listen())`);
87 | } else {
88 | ret = sock.connect(addr, port);
89 |
90 | ReturnValue(ret, `sock.connect(${addr}, ${port})`);
91 | }
92 |
93 | SetupTerm();
94 |
95 | let inLen = 0,
96 | inBuf = new ArrayBuffer(128);
97 | let outLen = 0,
98 | outBuf = new ArrayBuffer(1024);
99 |
100 | const rfds = new fd_set();
101 | const wfds = new fd_set();
102 | let handshake = 0;
103 |
104 | const SendTerm = Once(() => {
105 | return Send('\xff\xfa\x18\x00XTERM-256COLOR\xff\xf0\x1b[2J');
106 | });
107 | const Send = (a, n) => {
108 | const b = a instanceof ArrayBuffer ? a : StringToBuffer(a);
109 | if (n === undefined) n = b.byteLength;
110 | debug('Send -> %s', a instanceof ArrayBuffer ? Dump(b, n, 40) : EscapeString(a));
111 | return sock.write(b, 0, n);
112 | };
113 | debug('errnos: %s', Object.entries(errnos));
114 |
115 | do {
116 | FD_ZERO(rfds);
117 | FD_ZERO(wfds);
118 | FD_CLR(+sock, wfds);
119 |
120 | FD_SET(+sock, rfds);
121 |
122 | if (sock.connecting || outLen) FD_SET(+sock, wfds);
123 | else if (inLen < inBuf.byteLength) FD_SET(STDIN_FILENO, rfds);
124 |
125 | const timeout = new timeval(5, 0);
126 | //console.log('select:', sock + 1);
127 |
128 | ret = select(sock + 1, rfds, wfds, null, timeout);
129 |
130 | if (FD_ISSET(+sock, wfds)) {
131 | if (outLen > 0) {
132 | //console.log('outBuf:', BufferToString(outBuf));
133 | if (Send(outBuf, outLen) > 0) {
134 | outLen = 0;
135 | if (handshake == 0) {
136 | ttySetRaw(STDOUT_FILENO);
137 | signal(2, function () {
138 | debug('SIGINT');
139 | [outBuf, outLen] = Append(outBuf, outLen, tattr.c_cc[VINTR]);
140 | });
141 | signal(21, function () {
142 | debug('SIGTTIN');
143 | });
144 | signal(22, function () {
145 | debug('SIGTTOU');
146 | });
147 | signal(19, function () {
148 | debug('SIGSTOP');
149 | });
150 | signal(18, function () {
151 | debug('SIGCONT');
152 | });
153 | [outBuf, outLen] = Append(outBuf, outLen, '\x1b[2J');
154 | }
155 |
156 | if (handshake < 4) handshake++;
157 | }
158 | } else {
159 | conn = sock;
160 | }
161 | }
162 | if (FD_ISSET(+sock, rfds)) {
163 | if (listen) {
164 | conn = sock.accept();
165 |
166 | ReturnValue(conn, 'sock.accept()');
167 | } else {
168 | conn = sock;
169 | }
170 | }
171 |
172 | if (FD_ISSET(+conn, rfds)) {
173 | let length;
174 | //debug(`Socket readable handshake=${handshake} ${sock}`);
175 | if (handshake < 4) {
176 | length = outLen = sock.read(outBuf, 0, outBuf.byteLength);
177 | } else {
178 | const data = new ArrayBuffer(1024);
179 | length = sock.read(data, 0, data.byteLength);
180 |
181 | if (length > 0) {
182 | let start = 0;
183 | let chars = new Uint8Array(data, 0, length);
184 | if (length >= 2 && chars[0] == 0xff && chars[1] == 0xf2) {
185 | start += 2;
186 | length -= 2;
187 | }
188 | if (chars[0] == 0xff && chars[1] == 0xfe && chars[2] == 1) {
189 | start += 3;
190 | length -= 3;
191 | }
192 | let str = BufferToString(data, start, length);
193 | if (length >= 2 && str.indexOf('\xff\xfe') != -1) {
194 | SendTerm();
195 | }
196 | SetCursor(false);
197 | // debug('Received data from socket: "%s"', EscapeString(str));
198 | debug('Recv <- %s', Dump(data, length, 40));
199 | write(STDOUT_FILENO, data, start, length);
200 | } else {
201 | conn = undefined;
202 | }
203 | }
204 | //console.log(`Received ${length} bytes from socket`);
205 | }
206 | if (FD_ISSET(STDIN_FILENO, rfds)) {
207 | let offset, ret;
208 | again: offset = inLen;
209 | ret = read(STDIN_FILENO, inBuf, offset, inBuf.byteLength - offset);
210 |
211 | if (ret > 0) {
212 | inLen += ret;
213 | let chars = new Uint8Array(inBuf, offset, inLen - offset);
214 | SetCursor(true);
215 | for (let i = 0; i < chars.length; i++) {
216 | //err.printf("char '%c'\n", chars[i]);
217 | switch (chars[i]) {
218 | case 0x11: {
219 | out.printf([/*'\x1bc\x1b[?1000l',*/ '\x1b[?25h', '\r\n', 'Exited\n'].join(''));
220 | exit(1);
221 | break;
222 | }
223 | case 0xff: {
224 | if (chars[i + 1] == 0xf2 && chars[i + 2] == 0x03) {
225 | i += 2;
226 | [outBuf, outLen] = Append(outBuf, outLen, chars[i]);
227 | break;
228 | }
229 | }
230 | default: {
231 | [outBuf, outLen] = Append(outBuf, outLen, chars[i]);
232 | break;
233 | }
234 | }
235 | }
236 | }
237 | }
238 | } while (!sock.destroyed);
239 | debug('end');
240 | }
241 |
242 | function SetCursor(show) {
243 | const b = StringToBuffer(show ? '\x1b[34h\x1b[?25h' : '\x1b[?25l');
244 | write(STDOUT_FILENO, b, 0, b.byteLength);
245 | }
246 |
247 | function ReturnValue(ret, ...args) {
248 | const r = [-1, 0].indexOf(ret) != -1 ? ret + '' : '0x' + NumberToHex(ret, ptrSize * 2);
249 | debug('%s ret = %s%s%s', args, r, ...(ret == -1 ? [' errno =', errno(), ' error =', strerror(errno())] : ['', '']));
250 | }
251 |
252 | function NumberToHex(n, b = 2) {
253 | let s = (+n).toString(16);
254 | return '0'.repeat(Math.ceil(s.length / b) * b - s.length) + s;
255 | }
256 |
257 | function EscapeString(str) {
258 | let r = '';
259 | let codeAt = typeof str == 'string' ? (i) => str.charCodeAt(i) : (i) => str[i];
260 | for (let i = 0; i < str.length; i++) {
261 | const code = codeAt(i);
262 |
263 | if (code == 0x0a) r += '\\n';
264 | else if (code == 0x0d) r += '\\r';
265 | else if (code == 0x09) r += '\\t';
266 | else if (code <= 3) r += '\\0';
267 | else if (code < 32 || code >= 128) r += `\\${('00' + code.toString(8)).slice(-3)}`;
268 | else r += str[i];
269 | }
270 | return r;
271 | }
272 |
273 | function BufferToArray(buf, offset, length) {
274 | let len,
275 | arr = new Uint8Array(buf, offset !== undefined ? offset : 0, length !== undefined ? length : buf.byteLength);
276 | // arr = [...arr];
277 | if ((len = arr.indexOf(0)) != -1) arr = arr.slice(0, len);
278 | return arr;
279 | }
280 |
281 | function BufferToString(buf, offset, length) {
282 | return BufferToArray(buf, offset, length).reduce((s, code) => s + String.fromCharCode(code), '');
283 | }
284 |
285 | function BufferToBytes(buf, offset = 0, len, limit = Infinity) {
286 | len = typeof len == 'numer' ? len : buf.byteLength;
287 | offset = typeof offset == 'numer' ? offset : 0;
288 | return ArrayToBytes(new Uint8Array(buf, offset, len), undefined, undefined, limit);
289 | }
290 |
291 | function ArrayToBytes(arr, delim = ', ', bytes = 1, limit = Infinity) {
292 | let trail = '';
293 | delim = typeof delim == 'string' ? delim : ', ';
294 | bytes = typeof bytes == 'number' ? bytes : 1;
295 |
296 | if (limit !== Infinity) {
297 | let len = Math.min(arr.length, limit);
298 | arr = arr.slice(0, len);
299 | if (len < arr.length) trail = `... ${arr.length - len} more bytes ...`;
300 | }
301 | let str = arr.reduce((s, code) => (s != '' ? s + delim : '') + '0x' + ('000000000000000' + code.toString(16)).slice(-(bytes * 2)), '');
302 | return '[' + str + trail + ']';
303 | }
304 |
305 | function AvailableBytes(buf, numBytes) {
306 | return buf.byteLength - numBytes;
307 | }
308 |
309 | function Append(buf, numBytes, ...chars) {
310 | let n = chars.reduce((a, c) => (typeof c == 'number' ? a + 1 : a + c.length), 0);
311 | if (AvailableBytes(buf, numBytes) < n) buf = CloneBuf(buf, numBytes + n);
312 | let a = new Uint8Array(buf, numBytes, n);
313 | let p = 0;
314 | for (let i = 0; i < chars.length; i++) {
315 | if (typeof chars[i] == 'number') {
316 | a[p++] = chars[i];
317 | } else if (typeof chars[i] == 'string') {
318 | const s = chars[i];
319 | const m = s.length;
320 | for (let j = 0; j < m; j++) a[p++] = s[j].charCodeAt(0);
321 | }
322 | }
323 | return [buf, numBytes + n];
324 | }
325 |
326 | function Dump(buf, numBytes, limit = Infinity) {
327 | return BufferToBytes(numBytes !== undefined ? buf.slice(0, numBytes) : buf, 0, undefined, limit);
328 | }
329 |
330 | function CloneBuf(buf, newLen) {
331 | let n = newLen !== undefined ? newLen : buf.byteLength;
332 | let p = toPointer(buf);
333 | return toArrayBuffer(p, n);
334 | }
335 |
336 | function Once(fn, thisArg) {
337 | let ran = false;
338 | let ret;
339 |
340 | return function (...args) {
341 | if (!ran) {
342 | ret = fn.call(thisArg, ...args);
343 | ran = true;
344 | }
345 | return ret;
346 | };
347 | }
348 |
349 | function StringToBuffer(str) {
350 | return Uint8Array.from(str.split('').map((ch) => ch.charCodeAt(0))).buffer;
351 | }
352 |
353 | main(...scriptArgs.slice(1));
354 |
--------------------------------------------------------------------------------
/examples/term.js:
--------------------------------------------------------------------------------
1 | import { dlsym, define, call, RTLD_DEFAULT } from 'ffi.so';
2 |
3 | export const _IOC = (a, b, c, d) => (a << 30) | (b << 8) | c | (d << 16);
4 | export const _IOC_NONE = 0;
5 | export const _IOC_WRITE = 1;
6 | export const _IOC_READ = 2;
7 |
8 | export const _IO = (a, b) => _IOC(_IOC_NONE, a, b, 0);
9 | //export const _IOW = (a, b, c) => _IOC(_IOC_WRITE, a, b, sizeof(c));
10 | //export const _IOR = (a, b, c) => _IOC(_IOC_READ, a, b, sizeof(c));
11 | //export const _IOWR = (a, b, c) => _IOC(_IOC_READ | _IOC_WRITE, a, b, sizeof(c));
12 |
13 | /* c_iflag bits */
14 | export const IGNBRK = 0x01;
15 | export const BRKINT = 0x02;
16 | export const IGNPAR = 0x04;
17 | export const PARMRK = 0x08;
18 | export const INPCK = 0x10;
19 | export const ISTRIP = 0x20;
20 | export const INLCR = 0x40;
21 | export const IGNCR = 0x80;
22 | export const ICRNL = 0x0100;
23 | export const IUCLC = 0x0200;
24 | export const IXON = 0x0400;
25 | export const IXANY = 0x0800;
26 | export const IXOFF = 0x1000;
27 | export const IMAXBEL = 0x2000;
28 | export const IUTF8 = 0x4000;
29 |
30 | /* c_oflag bits */
31 | export const OPOST = 0x01;
32 | export const OLCUC = 0x02;
33 | export const ONLCR = 0x04;
34 | export const OCRNL = 0x08;
35 | export const ONOCR = 0x10;
36 | export const ONLRET = 0x20;
37 | export const OFILL = 0x40;
38 | export const OFDEL = 0x80;
39 | export const NLDLY = 0x0100;
40 | export const NL0 = 0x00;
41 | export const NL1 = 0x0100;
42 | export const CRDLY = 0x0600;
43 | export const CR0 = 0x00;
44 | export const CR1 = 0x0200;
45 | export const CR2 = 0x0400;
46 | export const CR3 = 0x0600;
47 | export const TABDLY = 0x1800;
48 | export const TAB0 = 0x00;
49 | export const TAB1 = 0x0800;
50 | export const TAB2 = 0x1000;
51 | export const TAB3 = 0x1800;
52 | export const XTABS = 0x1800;
53 | export const BSDLY = 0x2000;
54 | export const BS0 = 0x00;
55 | export const BS1 = 0x2000;
56 | export const VTDLY = 0x4000;
57 | export const VT0 = 0x00;
58 | export const VT1 = 0x4000;
59 | export const FFDLY = 0x8000;
60 | export const FF0 = 0x00;
61 | export const FF1 = 0x8000;
62 |
63 | /* c_cflag bit meaning */
64 | export const CBAUD = 0x100f;
65 | export const B0 = 0x00; /* hang up */
66 | export const B50 = 0x01;
67 | export const B75 = 0x02;
68 | export const B110 = 0x03;
69 | export const B134 = 0x04;
70 | export const B150 = 0x05;
71 | export const B200 = 0x06;
72 | export const B300 = 0x07;
73 | export const B600 = 0x08;
74 | export const B1200 = 0x09;
75 | export const B1800 = 0x0a;
76 | export const B2400 = 0x0b;
77 | export const B4800 = 0x0c;
78 | export const B9600 = 0x0d;
79 | export const B19200 = 0x0e;
80 | export const B38400 = 0x0f;
81 | export const EXTA = B19200;
82 | export const EXTB = B38400;
83 | export const CSIZE = 0x30;
84 | export const CS5 = 0x00;
85 | export const CS6 = 0x10;
86 | export const CS7 = 0x20;
87 | export const CS8 = 0x30;
88 | export const CSTOPB = 0x40;
89 | export const CREAD = 0x80;
90 | export const PARENB = 0x0100;
91 | export const PARODD = 0x0200;
92 | export const HUPCL = 0x0400;
93 | export const CLOCAL = 0x0800;
94 | export const CBAUDEX = 0x1000;
95 | export const B57600 = 0x1001;
96 | export const B115200 = 0x1002;
97 | export const B230400 = 0x1003;
98 | export const B460800 = 0x1004;
99 | export const B500000 = 0x1005;
100 | export const B576000 = 0x1006;
101 | export const B921600 = 0x1007;
102 | export const B1000000 = 0x1008;
103 | export const B1152000 = 0x1009;
104 | export const B1500000 = 0x100a;
105 | export const B2000000 = 0x100b;
106 | export const B2500000 = 0x100c;
107 | export const B3000000 = 0x100d;
108 | export const B3500000 = 0x100e;
109 | export const B4000000 = 0x100f;
110 | export const CIBAUD = 0x100f0000; /* input baud rate (not used) */
111 | export const CMSPAR = 0x40000000; /* mark or space (stick) parity */
112 | export const CRTSCTS = '0x80000000'; /* flow control */
113 |
114 | /* c_lflag bits */
115 | export const ISIG = 0x01;
116 | export const ICANON = 0x02;
117 | export const XCASE = 0x04;
118 | export const ECHO = 0x08;
119 | export const ECHOE = 0x10;
120 | export const ECHOK = 0x20;
121 | export const ECHONL = 0x40;
122 | export const NOFLSH = 0x80;
123 | export const ECHOCTL = 0x0200;
124 | export const ECHOPRT = 0x0400;
125 | export const ECHOKE = 0x0800;
126 |
127 | export const TOSTOP = 0x0100;
128 | export const FLUSHO = 0x1000;
129 | export const IEXTEN = 0x8000;
130 | export const PENDIN = 0x4000;
131 |
132 | /* tcflow() and TCXONC use these */
133 | export const TCOOFF = 0;
134 | export const TCOON = 1;
135 | export const TCIOFF = 2;
136 | export const TCION = 3;
137 |
138 | /* tcflush() and TCFLSH use these */
139 | export const TCIFLUSH = 0;
140 | export const TCOFLUSH = 1;
141 | export const TCIOFLUSH = 2;
142 |
143 | /* tcsetattr uses these */
144 | export const TCSANOW = 0;
145 | export const TCSADRAIN = 1;
146 | export const TCSAFLUSH = 2;
147 |
148 | export const TCGETS = 0x5401;
149 | export const TCSETS = 0x5402;
150 | export const TCSETSW = 0x5403;
151 | export const TCSETSF = 0x5404;
152 | export const TCGETA = 0x5405;
153 | export const TCSETA = 0x5406;
154 | export const TCSETAW = 0x5407;
155 | export const TCSETAF = 0x5408;
156 | export const TCSBRK = 0x5409;
157 | export const TCXONC = 0x540a;
158 | export const TCFLSH = 0x540b;
159 | export const TIOCEXCL = 0x540c;
160 | export const TIOCNXCL = 0x540d;
161 | export const TIOCSCTTY = 0x540e;
162 | export const TIOCGPGRP = 0x540f;
163 | export const TIOCSPGRP = 0x5410;
164 | export const TIOCOUTQ = 0x5411;
165 | export const TIOCSTI = 0x5412;
166 | export const TIOCGWINSZ = 0x5413;
167 | export const TIOCSWINSZ = 0x5414;
168 | export const TIOCMGET = 0x5415;
169 | export const TIOCMBIS = 0x5416;
170 | export const TIOCMBIC = 0x5417;
171 | export const TIOCMSET = 0x5418;
172 | export const TIOCGSOFTCAR = 0x5419;
173 | export const TIOCSSOFTCAR = 0x541a;
174 | export const FIONREAD = 0x541b;
175 | export const TIOCINQ = FIONREAD;
176 | export const TIOCLINUX = 0x541c;
177 | export const TIOCCONS = 0x541d;
178 | export const TIOCGSERIAL = 0x541e;
179 | export const TIOCSSERIAL = 0x541f;
180 | export const TIOCPKT = 0x5420;
181 | export const FIONBIO = 0x5421;
182 | export const TIOCNOTTY = 0x5422;
183 | export const TIOCSETD = 0x5423;
184 | export const TIOCGETD = 0x5424;
185 | export const TCSBRKP = 0x5425;
186 | export const TIOCSBRK = 0x5427;
187 | export const TIOCCBRK = 0x5428;
188 | export const TIOCGSID = 0x5429;
189 | export const TIOCGRS485 = 0x542e;
190 | export const TIOCSRS485 = 0x542f;
191 | export const TIOCGPTN = '0x80045430';
192 | export const TIOCSPTLCK = 0x40045431;
193 | export const TIOCGDEV = '0x80045432';
194 | export const TCGETX = 0x5432;
195 | export const TCSETX = 0x5433;
196 | export const TCSETXF = 0x5434;
197 | export const TCSETXW = 0x5435;
198 | export const TIOCSIG = 0x40045436;
199 | export const TIOCVHANGUP = 0x5437;
200 | export const TIOCGPKT = '0x80045438';
201 | export const TIOCGPTLCK = '0x80045439';
202 | export const TIOCGEXCL = '0x80045440';
203 | export const TIOCGPTPEER = 0x5441;
204 |
205 | export const FIONCLEX = 0x5450;
206 | export const FIOCLEX = 0x5451;
207 | export const FIOASYNC = 0x5452;
208 | export const TIOCSERCONFIG = 0x5453;
209 | export const TIOCSERGWILD = 0x5454;
210 | export const TIOCSERSWILD = 0x5455;
211 | export const TIOCGLCKTRMIOS = 0x5456;
212 | export const TIOCSLCKTRMIOS = 0x5457;
213 | export const TIOCSERGSTRUCT = 0x5458;
214 | export const TIOCSERGETLSR = 0x5459;
215 | export const TIOCSERGETMULTI = 0x545a;
216 | export const TIOCSERSETMULTI = 0x545b;
217 |
218 | export const TIOCMIWAIT = 0x545c;
219 | export const TIOCGICOUNT = 0x545d;
220 | export const FIOQSIZE = 0x5460;
221 |
222 | export const TIOCPKT_DATA = 0;
223 | export const TIOCPKT_FLUSHREAD = 1;
224 | export const TIOCPKT_FLUSHWRITE = 2;
225 | export const TIOCPKT_STOP = 4;
226 | export const TIOCPKT_START = 8;
227 | export const TIOCPKT_NOSTOP = 16;
228 | export const TIOCPKT_DOSTOP = 32;
229 | export const TIOCPKT_IOCTL = 64;
230 |
231 | export const TIOCSER_TEMT = 0x01;
232 |
233 | /*struct winsize {
234 | unsigned short ws_row;
235 | unsigned short ws_col;
236 | unsigned short ws_xpixel;
237 | unsigned short ws_ypixel;
238 | };*/
239 |
240 | export const TIOCM_LE = 0x0001;
241 | export const TIOCM_DTR = 0x0002;
242 | export const TIOCM_RTS = 0x0004;
243 | export const TIOCM_ST = 0x0008;
244 | export const TIOCM_SR = 0x0010;
245 | export const TIOCM_CTS = 0x0020;
246 | export const TIOCM_CAR = 0x0040;
247 | export const TIOCM_RNG = 0x0080;
248 | export const TIOCM_DSR = 0x0100;
249 | export const TIOCM_CD = TIOCM_CAR;
250 | export const TIOCM_RI = TIOCM_RNG;
251 | export const TIOCM_OUT1 = 0x2000;
252 | export const TIOCM_OUT2 = 0x4000;
253 | export const TIOCM_LOOP = 0x8000;
254 |
255 | export const N_TTY = 0;
256 | export const N_SLIP = 1;
257 | export const N_MOUSE = 2;
258 | export const N_PPP = 3;
259 | export const N_STRIP = 4;
260 | export const N_AX25 = 5;
261 | export const N_X25 = 6; /* X.25 async */
262 | export const N_6PACK = 7;
263 | export const N_MASC = 8; /* Reserved for Mobitex module */
264 | export const N_R3964 = 9; /* Reserved for Simatic R3964 module */
265 | export const N_PROFIBUS_FDL = 10; /* Reserved for Profibus */
266 | export const N_IRDA = 11; /* Linux IR - http://irda.sourceforge.net/ */
267 | export const N_SMSBLOCK = 12; /* SMS block mode - for talking to GSM data cards about SMS messages */
268 | export const N_HDLC = 13; /* synchronous HDLC */
269 | export const N_SYNC_PPP = 14; /* synchronous PPP */
270 | export const N_HCI = 15; /* Bluetooth HCI UART */
271 |
272 | export const FIOSETOWN = 0x8901;
273 | export const SIOCSPGRP = 0x8902;
274 | export const FIOGETOWN = 0x8903;
275 | export const SIOCGPGRP = 0x8904;
276 | export const SIOCATMARK = 0x8905;
277 | export const SIOCGSTAMP = 0x8906;
278 | export const SIOCGSTAMPNS = 0x8907;
279 |
280 | export const SIOCADDRT = 0x890b;
281 | export const SIOCDELRT = 0x890c;
282 | export const SIOCRTMSG = 0x890d;
283 |
284 | export const SIOCGIFNAME = 0x8910;
285 | export const SIOCSIFLINK = 0x8911;
286 | export const SIOCGIFCONF = 0x8912;
287 | export const SIOCGIFFLAGS = 0x8913;
288 | export const SIOCSIFFLAGS = 0x8914;
289 | export const SIOCGIFADDR = 0x8915;
290 | export const SIOCSIFADDR = 0x8916;
291 | export const SIOCGIFDSTADDR = 0x8917;
292 | export const SIOCSIFDSTADDR = 0x8918;
293 | export const SIOCGIFBRDADDR = 0x8919;
294 | export const SIOCSIFBRDADDR = 0x891a;
295 | export const SIOCGIFNETMASK = 0x891b;
296 | export const SIOCSIFNETMASK = 0x891c;
297 | export const SIOCGIFMETRIC = 0x891d;
298 | export const SIOCSIFMETRIC = 0x891e;
299 | export const SIOCGIFMEM = 0x891f;
300 | export const SIOCSIFMEM = 0x8920;
301 | export const SIOCGIFMTU = 0x8921;
302 | export const SIOCSIFMTU = 0x8922;
303 | export const SIOCSIFNAME = 0x8923;
304 | export const SIOCSIFHWADDR = 0x8924;
305 | export const SIOCGIFENCAP = 0x8925;
306 | export const SIOCSIFENCAP = 0x8926;
307 | export const SIOCGIFHWADDR = 0x8927;
308 | export const SIOCGIFSLAVE = 0x8929;
309 | export const SIOCSIFSLAVE = 0x8930;
310 | export const SIOCADDMULTI = 0x8931;
311 | export const SIOCDELMULTI = 0x8932;
312 | export const SIOCGIFINDEX = 0x8933;
313 | export const SIOGIFINDEX = SIOCGIFINDEX;
314 | export const SIOCSIFPFLAGS = 0x8934;
315 | export const SIOCGIFPFLAGS = 0x8935;
316 | export const SIOCDIFADDR = 0x8936;
317 | export const SIOCSIFHWBROADCAST = 0x8937;
318 | export const SIOCGIFCOUNT = 0x8938;
319 |
320 | export const SIOCGIFBR = 0x8940;
321 | export const SIOCSIFBR = 0x8941;
322 |
323 | export const SIOCGIFTXQLEN = 0x8942;
324 | export const SIOCSIFTXQLEN = 0x8943;
325 |
326 | export const SIOCDARP = 0x8953;
327 | export const SIOCGARP = 0x8954;
328 | export const SIOCSARP = 0x8955;
329 |
330 | export const SIOCDRARP = 0x8960;
331 | export const SIOCGRARP = 0x8961;
332 | export const SIOCSRARP = 0x8962;
333 |
334 | export const SIOCGIFMAP = 0x8970;
335 | export const SIOCSIFMAP = 0x8971;
336 |
337 | export const SIOCADDDLCI = 0x8980;
338 | export const SIOCDELDLCI = 0x8981;
339 |
340 | export const SIOCDEVPRIVATE = 0x89f0;
341 | export const SIOCPROTOPRIVATE = 0x89e0;
342 |
343 | export const NCC = 8;
344 | export const NCCS = 19;
345 |
346 | /*export const TIOCM_DTR = 0x0002;
347 | export const TIOCM_RTS = 0x0004;
348 | export const TIOCM_ST = 0x0008;
349 | export const TIOCM_SR = 0x0010;
350 | export const TIOCM_CTS = 0x0020;
351 | export const TIOCM_CAR = 0x0040;
352 | export const TIOCM_RNG = 0x0080;
353 | export const TIOCM_DSR = 0x0100;
354 | export const TIOCM_CD = TIOCM_CAR;
355 | export const TIOCM_RI = TIOCM_RNG;
356 | export const TIOCM_OUT1 = 0x2000;
357 | export const TIOCM_OUT2 = 0x4000;
358 | export const TIOCM_LOOP = 0x8000; */
359 |
360 | export const _POSIX_VDISABLE = '\0';
361 | export const VINTR = 0;
362 | export const VQUIT = 1;
363 | export const VERASE = 2;
364 | export const VKILL = 3;
365 | export const VEOF = 4;
366 | export const VTIME = 5;
367 | export const VMIN = 6;
368 | export const VSWTC = 7;
369 | export const VSTART = 8;
370 | export const VSTOP = 9;
371 | export const VSUSP = 10;
372 | export const VEOL = 11;
373 | export const VREPRINT = 12;
374 | export const VDISCARD = 13;
375 | export const VWERASE = 14;
376 | export const VLNEXT = 15;
377 | export const VEOL2 = 16;
378 |
379 | const cterm = [ 'tcdrain', 'tcgetsid', 'tcsendbreak', 'tcsetattr', 'tcflow', 'tcsetpgrp', 'tcflush', 'tcgetattr', 'tcgetpgrp', 'cfgetospeed', 'cfsetospeed', 'cfgetispeed', 'cfsetispeed' ].reduce((o, s) => { let fp = dlsym(RTLD_DEFAULT, s);
380 | if(fp == null) {
381 | console.log(dlerror());
382 | return o;
383 | }
384 | return { ...o, [s]: fp };
385 | }, {});
386 |
387 | function def(name, abi, ...params) {
388 | let defined = false;
389 | return (...args) => {
390 | if(!defined) {
391 | define(name, cterm[name], abi, ...params);
392 | defined = true;
393 | }
394 | return call(name, ...args);
395 | };
396 | }
397 |
398 | /* int tcgetattr(int fd, struct termios *termios_p); */
399 | export const tcgetattr = def('tcgetattr', null, 'int', 'int', 'buffer');
400 |
401 | /* int tcsetattr(int fd, int optional_actions, struct termios *termios_p); */
402 | export const tcsetattr = def('tcsetattr', null, 'int', 'int', 'int', 'buffer');
403 |
404 | /* int tcflush(int fd, int queue_selector); */
405 | export const tcflush = def('tcflush', null, 'int', 'int', 'int');
406 |
407 | /* int tcdrain(int fd); */
408 | export const tcdrain = def('tcdrain', null, 'int', 'int');
409 |
410 | /* int tcflow(int fd, int action); */
411 | export const tcflow = def('tcflow', null, 'int', 'int', 'int');
412 |
413 | /* int tcsendbreak(int fd, int duration); */
414 | export const tcsendbreak = def('tcsendbreak', null, 'int', 'int', 'int');
415 |
416 | /* pid_t tcgetsid(int fd); */
417 | export const tcgetsid = def('tcgetsid', null, 'int', 'int');
418 |
419 | /* pid_t tcgetpgrp(int fd); */
420 | export const tcgetpgrp = def('tcgetpgrp', null, 'int', 'int');
421 |
422 | /* int tcsetpgrp(int fd, pid_t pgrpid); */
423 | export const tcsetpgrp = def('tcsetpgrp', null, 'int', 'int', 'int');
424 |
425 | /* speed_t cfgetospeed(const struct termios *termios_p); */
426 | export const cfgetospeed = def('cfgetospeed', null, 'int', 'buffer');
427 |
428 | /* int cfsetospeed(struct termios *termios_p, speed_t speed); */
429 | export const cfsetospeed = def('cfsetospeed', null, 'int', 'buffer', 'int');
430 |
431 | /* speed_t cfgetispeed(const struct termios *termios_p); */
432 | export const cfgetispeed = def('cfgetispeed', null, 'int', 'buffer');
433 |
434 | /* int cfsetispeed(struct termios *termios_p, speed_t speed); */
435 | export const cfsetispeed = def('cfsetispeed', null, 'int', 'buffer', 'int');
436 |
437 | export class winsize extends ArrayBuffer {
438 | constructor(row, col, x, y) {
439 | super(4 * 2);
440 |
441 | this.ws_row = row | 0;
442 | this.ws_col = col | 0;
443 | this.ws_xpixel = x | 0;
444 | this.ws_ypixel = y | 0;
445 | }
446 | get ws_row() { return new Uint16Array(this, 0)[0]; }
447 | set ws_row(v) { new Uint16Array(this, 0)[0] = v >>> 0; }
448 | get ws_col() { return new Uint16Array(this, 2)[0]; }
449 | set ws_col(v) { new Uint16Array(this, 2)[0] = v >>> 0; }
450 | get ws_xpixel() { return new Uint16Array(this, 4)[0]; }
451 | set ws_xpixel(v) { new Uint16Array(this, 4)[0] = v >>> 0; }
452 | get ws_ypixel() { return new Uint16Array(this, 6)[0]; }
453 | set ws_ypixel(v) { new Uint16Array(this, 6)[0] = v >>> 0; }
454 | }
455 |
456 | export class termio extends ArrayBuffer {
457 | static fields = Uint16Array;
458 | constructor() {
459 | super(18);
460 | }
461 | get c_iflag() { return new termio.fields(this, 0)[0]; }
462 | set c_iflag(v) { new termio.fields(this, 0)[0] = v >>> 0; }
463 | get c_oflag() { return new termio.fields(this, 2)[0]; }
464 | set c_oflag(v) { new termio.fields(this, 2)[0] = v >>> 0; }
465 | get c_cflag() { return new termio.fields(this, 4)[0]; }
466 | set c_cflag(v) { new termio.fields(this, 4)[0] = v >>> 0; }
467 | get c_lflag() { return new termio.fields(this, 6)[0]; }
468 | set c_lflag(v) { new termio.fields(this, 6)[0] = v >>> 0; }
469 | get c_line() { return new Uint8Array(this, 8)[0]; }
470 | set c_line(v) { new Uint8Array(this, 8)[0] = typeof(v) == 'string' ? v.charCodeAt(0) : v; }
471 | get c_cc() { return new Uint8Array(this, 9).slice(0,NCC); }
472 | set c_cc(v) { const a = new Uint8Array(this, 9, NCC); const n = Math.min(v.length, a.length); for(let i = 0; i < n; i++) a[i] = typeof(v[i]) == 'string' ? v[i].charCodeAt(0) : v[i]; }
473 | }
474 |
475 | export class termios extends ArrayBuffer {
476 | static fields = Uint32Array;
477 | constructor() {
478 | super(60);
479 | }
480 | get c_iflag() { return new termios.fields(this, 0)[0]; }
481 | set c_iflag(v) { new termios.fields(this, 0)[0] = v >>> 0; }
482 | get c_oflag() { return new termios.fields(this, 4)[0]; }
483 | set c_oflag(v) { new termios.fields(this, 4)[0] = v >>> 0; }
484 | get c_cflag() { return new termios.fields(this, 8)[0]; }
485 | set c_cflag(v) { new termios.fields(this, 8)[0] = v >>> 0; }
486 | get c_lflag() { return new termios.fields(this, 12)[0]; }
487 | set c_lflag(v) { new termios.fields(this, 12)[0] = v >>> 0; }
488 | get c_line() { return new Uint8Array(this, 16)[0]; }
489 | set c_line(v) { new Uint8Array(this, 16)[0] = v >>> 0; }
490 | get c_cc() { return new Uint8Array(this, 17, NCCS)/*.map(c => String.fromCharCode(c))*/; }
491 | set c_cc(v) { const a = new Uint8Array(this, 17, NCCS); const n = Math.min(v.length, a.length); for(let i = 0; i < n; i++) a[i] = typeof(v[i]) == 'string' ? v[i].charCodeAt(0) : v[i]; }
492 |
493 | /* prettier-ignore */
494 | toString() {
495 | return `{ .c_iflag=${this.c_iflag}, .c_oflag=${this.c_oflag}, .c_cflag=${this.c_cflag}, .c_lflag=${this.c_lflag}, .c_line=${this.c_line}, .c_cc=${this.c_cc} }`;
496 | const i = termios.flags(this.c_iflag, { IGNBRK, BRKINT, IGNPAR, PARMRK, INPCK, ISTRIP, INLCR, IGNCR, ICRNL, IUCLC, IXON, IXANY, IXOFF, IMAXBEL, IUTF8 });
497 | const o = termios.flags(this.c_oflag, { OPOST, OLCUC, ONLCR, OCRNL, ONOCR, ONLRET, OFILL, OFDEL, NLDLY, NL0, NL1, CRDLY: [b => b & CRDLY, { CR0, CR1, CR2, CR3 }], TABDLY: [b => b & TABDLY, { TAB0, TAB1, TAB2, TAB3, XTABS }], BSDLY: [b => b & BSDLY, { BS0, BS1 }], VTDLY: [b => b & VTDLY, { VT0, VT1 }], FFDLY: [b => b & FFDLY, { FF0, FF1 }] });
498 | const c = termios.flags(this.c_cflag, { EXT: [b => b & CBAUD, { EXTA, EXTB }], CSIZE: [b => b & CSIZE, { CS5, CS6, CS7, CS8 }], CSTOPB, CREAD, PARENB, PARODD, HUPCL, CLOCAL, CMSPAR, CRTSCTS, BAUD: [b => b & CBAUD, termios.rates] });
499 | const l = termios.flags(this.c_lflag, { ISIG, ICANON, XCASE, ECHO, ECHOE, ECHOK, ECHONL, NOFLSH, ECHOCTL, ECHOPRT, ECHOKE, TOSTOP, FLUSHO, IEXTEN, PENDIN });
500 |
501 | return '{\n' + [
502 | 'iflag = ' + i,
503 | 'oflag = ' + o,
504 | 'cflag = ' + c,
505 | 'lflag = ' + l,
506 | 'line = ' + this.c_line,
507 | `cc = [\n${[...this.c_cc].map((ch,i) => ((['VINTR', 'VQUIT', 'VERASE', 'VKILL', 'VEOF', 'VTIME', 'VMIN', 'VSWTC', 'VSTART', 'VSTOP', 'VSUSP', 'VEOL', 'VREPRINT', 'VDISCARD', 'VWERASE', 'VLNEXT', 'VEOL2'][i]||`[${i}]`)+': ').padStart(12)+ (('00'+ch.toString(16).toUpperCase()).slice(-2))).join("\n")}\n ]`
508 | ].map(s => ` .c_${s}`).join(',\n') + '\n}';
509 | }
510 |
511 | static rates = {
512 | [B0]: 0,
513 | [B50]: 50,
514 | [B75]: 75,
515 | [B110]: 110,
516 | [B134]: 134,
517 | [B150]: 150,
518 | [B200]: 200,
519 | [B300]: 300,
520 | [B600]: 600,
521 | [B1200]: 1200,
522 | [B1800]: 1800,
523 | [B2400]: 2400,
524 | [B4800]: 4800,
525 | [B9600]: 9600,
526 | [B19200]: 19200,
527 | [B38400]: 38400,
528 | [B57600]: 57600,
529 | [B115200]: 115200,
530 | [B230400]: 230400,
531 | [B460800]: 460800,
532 | [B500000]: 500000,
533 | [B576000]: 576000,
534 | [B921600]: 921600,
535 | [B1000000]: 1000000,
536 | [B1152000]: 1152000,
537 | [B1500000]: 1500000,
538 | [B2000000]: 2000000,
539 | [B2500000]: 2500000,
540 | [B3000000]: 3000000,
541 | [B3500000]: 3500000,
542 | [B4000000]: 4000000
543 | };
544 |
545 | static flags(flags, obj) {
546 | const r = [];
547 | for(const name in obj) {
548 | const bit = obj[name];
549 | if(typeof bit[0] == 'function') {
550 | const [mask, o] = bit;
551 | const h = mask(flags);
552 | const e = Object.entries(o).find(([name, b]) => mask(b) == h);
553 | if(e) r.push(e[0]);
554 | } else {
555 | if(countBits(bit) > 1) throw new Error(`flags name=${name} bit=0x${bit.toString(16)}`);
556 | if(flags & bit) r.push(name);
557 | }
558 | }
559 | return r.join(' | ') || 0;
560 | }
561 | }
562 |
563 | function countBits(num) {
564 | return num
565 | .toString(2)
566 | .split('')
567 | .map(n => +n)
568 | .reduce((a, b) => a + b, 0);
569 | }
570 |
571 | function NumberToHex(n, b = 2) {
572 | let s = (+n).toString(16);
573 | return '0x' + '0'.repeat(Math.ceil(s.length / b) * b - s.length) + s;
574 | }
575 |
576 | /* prettier-ignore */
577 | export default { _IOC, _IOC_NONE, _IOC_WRITE, _IOC_READ, _IO,/* _IOW, _IOR, _IOWR,*/ IGNBRK, BRKINT, IGNPAR, PARMRK, INPCK, ISTRIP, INLCR, IGNCR, ICRNL, IUCLC, IXON, IXANY, IXOFF, IMAXBEL, IUTF8, OPOST, OLCUC, ONLCR, OCRNL, ONOCR, ONLRET, OFILL, OFDEL, NLDLY, NL0, NL1, CRDLY, CR0, CR1, CR2, CR3, TABDLY, TAB0, TAB1, TAB2, TAB3, XTABS, BSDLY, BS0, BS1, VTDLY, VT0, VT1, FFDLY, FF0, FF1, CBAUD, B0, B50, B75, B110, B134, B150, B200, B300, B600, B1200, B1800, B2400, B4800, B9600, B19200, B38400, EXTA, EXTB, CSIZE, CS5, CS6, CS7, CS8, CSTOPB, CREAD, PARENB, PARODD, HUPCL, CLOCAL, CBAUDEX, B57600, B115200, B230400, B460800, B500000, B576000, B921600, B1000000, B1152000, B1500000, B2000000, B2500000, B3000000, B3500000, B4000000, CIBAUD, CMSPAR, CRTSCTS, ISIG, ICANON, XCASE, ECHO, ECHOE, ECHOK, ECHONL, NOFLSH, ECHOCTL, ECHOPRT, ECHOKE, TOSTOP, FLUSHO, IEXTEN, PENDIN, TCOOFF, TCOON, TCIOFF, TCION, TCIFLUSH, TCOFLUSH, TCIOFLUSH, TCSANOW, TCSADRAIN, TCSAFLUSH, TCGETS, TCSETS, TCSETSW, TCSETSF, TCGETA, TCSETA, TCSETAW, TCSETAF, TCSBRK, TCXONC, TCFLSH, TIOCEXCL, TIOCNXCL, TIOCSCTTY, TIOCGPGRP, TIOCSPGRP, TIOCOUTQ, TIOCSTI, TIOCGWINSZ, TIOCSWINSZ, TIOCMGET, TIOCMBIS, TIOCMBIC, TIOCMSET, TIOCGSOFTCAR, TIOCSSOFTCAR, FIONREAD, TIOCINQ, TIOCLINUX, TIOCCONS, TIOCGSERIAL, TIOCSSERIAL, TIOCPKT, FIONBIO, TIOCNOTTY, TIOCSETD, TIOCGETD, TCSBRKP, TIOCSBRK, TIOCCBRK, TIOCGSID, TIOCGRS485, TIOCSRS485, TIOCGPTN, TIOCSPTLCK, TIOCGDEV, TCGETX, TCSETX, TCSETXF, TCSETXW, TIOCSIG, TIOCVHANGUP, TIOCGPKT, TIOCGPTLCK, TIOCGEXCL, TIOCGPTPEER, FIONCLEX, FIOCLEX, FIOASYNC, TIOCSERCONFIG, TIOCSERGWILD, TIOCSERSWILD, TIOCGLCKTRMIOS, TIOCSLCKTRMIOS, TIOCSERGSTRUCT, TIOCSERGETLSR, TIOCSERGETMULTI, TIOCSERSETMULTI, TIOCMIWAIT, TIOCGICOUNT, FIOQSIZE, TIOCPKT_DATA, TIOCPKT_FLUSHREAD, TIOCPKT_FLUSHWRITE, TIOCPKT_STOP, TIOCPKT_START, TIOCPKT_NOSTOP, TIOCPKT_DOSTOP, TIOCPKT_IOCTL, TIOCSER_TEMT, TIOCM_LE, TIOCM_DTR, TIOCM_RTS, TIOCM_ST, TIOCM_SR, TIOCM_CTS, TIOCM_CAR, TIOCM_RNG, TIOCM_DSR, TIOCM_CD, TIOCM_RI, TIOCM_OUT1, TIOCM_OUT2, TIOCM_LOOP, N_TTY, N_SLIP, N_MOUSE, N_PPP, N_STRIP, N_AX25, N_X25, N_6PACK, N_MASC, N_R3964, N_PROFIBUS_FDL, N_IRDA, N_SMSBLOCK, N_HDLC, N_SYNC_PPP, N_HCI, FIOSETOWN, SIOCSPGRP, FIOGETOWN, SIOCGPGRP, SIOCATMARK, SIOCGSTAMP, SIOCGSTAMPNS, SIOCADDRT, SIOCDELRT, SIOCRTMSG, SIOCGIFNAME, SIOCSIFLINK, SIOCGIFCONF, SIOCGIFFLAGS, SIOCSIFFLAGS, SIOCGIFADDR, SIOCSIFADDR, SIOCGIFDSTADDR, SIOCSIFDSTADDR, SIOCGIFBRDADDR, SIOCSIFBRDADDR, SIOCGIFNETMASK, SIOCSIFNETMASK, SIOCGIFMETRIC, SIOCSIFMETRIC, SIOCGIFMEM, SIOCSIFMEM, SIOCGIFMTU, SIOCSIFMTU, SIOCSIFNAME, SIOCSIFHWADDR, SIOCGIFENCAP, SIOCSIFENCAP, SIOCGIFHWADDR, SIOCGIFSLAVE, SIOCSIFSLAVE, SIOCADDMULTI, SIOCDELMULTI, SIOCGIFINDEX, SIOGIFINDEX, SIOCSIFPFLAGS, SIOCGIFPFLAGS, SIOCDIFADDR, SIOCSIFHWBROADCAST, SIOCGIFCOUNT, SIOCGIFBR, SIOCSIFBR, SIOCGIFTXQLEN, SIOCSIFTXQLEN, SIOCDARP, SIOCGARP, SIOCSARP, SIOCDRARP, SIOCGRARP, SIOCSRARP, SIOCGIFMAP, SIOCSIFMAP, SIOCADDDLCI, SIOCDELDLCI, SIOCDEVPRIVATE, SIOCPROTOPRIVATE, NCC, NCCS, TIOCM_RTS, TIOCM_ST, TIOCM_SR, TIOCM_CTS, TIOCM_CAR, TIOCM_RNG, TIOCM_DSR, TIOCM_CD, TIOCM_RI, TIOCM_OUT1, TIOCM_OUT2, TIOCM_LOOP, _POSIX_VDISABLE, VINTR, VQUIT, VERASE, VKILL, VEOF, VTIME, VMIN, VSWTC, VSTART, VSTOP, VSUSP, VEOL, VREPRINT, VDISCARD, VWERASE, VLNEXT, VEOL2, tcgetattr, tcsetattr, tcflush, tcdrain, tcflow, tcsendbreak, tcgetsid, tcgetpgrp, tcsetpgrp, winsize, termio, termio };
578 |
--------------------------------------------------------------------------------
/ffi.c:
--------------------------------------------------------------------------------
1 | /**********************************************************************
2 | * *
3 | * ffi.c *
4 | * *
5 | **********************************************************************/
6 |
7 |
8 | #define _GNU_SOURCE
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 | #include
16 | #include
17 |
18 |
19 | #define countof(x) (sizeof(x) / sizeof((x)[0]))
20 |
21 |
22 | struct function_s {
23 | struct function_s *next;
24 | char *name;
25 | void *fp;
26 | int nargs;
27 | ffi_cif cif;
28 | ffi_type **args;
29 | ffi_type *rtype;
30 | };
31 |
32 |
33 | #define TYPE_INTEGRAL 0
34 | #define TYPE_FLOAT 1
35 | #define TYPE_POINTER 2
36 |
37 |
38 | struct ffi_type_s {
39 | struct ffi_type_s *next;
40 | char *name;
41 | ffi_type *type;
42 | };
43 |
44 | union argument_s {
45 | long long ll;
46 | long double ld;
47 | float f;
48 | double d;
49 | void *p;
50 | };
51 | typedef union argument_s argument;
52 |
53 |
54 | struct typed_argument_s {
55 | argument arg;
56 | int type;
57 | };
58 | typedef struct typed_argument_s typed_argument;
59 |
60 |
61 | /* FFI types
62 | */
63 | static struct ffi_type_s *ffi_type_head = NULL;
64 |
65 |
66 | static void fatal(char *msg) {
67 | fprintf(stderr, "%s\n", msg);
68 | exit(2);
69 | }
70 |
71 |
72 | static void warn(char *msg) {
73 | fprintf(stderr, "%s\n", msg);
74 | }
75 |
76 |
77 | /* Find type by name
78 | */
79 | static ffi_type *find_ffi_type(const char *name) {
80 | struct ffi_type_s *p = NULL;
81 |
82 | if (name == NULL)
83 | return NULL;
84 | for (p = ffi_type_head; p; p = p->next)
85 | if (strcmp(p->name, name) == 0)
86 | return p->type;
87 | return NULL;
88 | }
89 |
90 |
91 | /* Add new ffi_type to named types
92 | */
93 | static void define_ffi_type(char *name, ffi_type *t) {
94 | struct ffi_type_s *p = NULL;
95 |
96 | /* ignore if already defined */
97 | if (find_ffi_type(name) != NULL)
98 | return;
99 |
100 | p = malloc(sizeof (struct ffi_type_s));
101 | if (p == NULL)
102 | fatal("define_ffi_type: no memory");
103 | p->name = strdup(name);
104 | p->next = ffi_type_head;
105 | ffi_type_head = p;
106 | p->type = t;
107 | }
108 |
109 |
110 | /* Return ABI
111 | */
112 | static int find_abi(const char *name) {
113 | if (strcmp(name, "default") == 0) return FFI_DEFAULT_ABI;
114 | #ifdef FFI_SYSV
115 | if (strcmp(name, "sysv") == 0) return FFI_SYSV;
116 | #endif
117 | #ifdef FFI_UNIX64
118 | if (strcmp(name, "unix64") == 0) return FFI_UNIX64;
119 | #endif
120 | #ifdef FFI_STDCALL
121 | if (strcmp(name, "stdcall") == 0) return FFI_STDCALL;
122 | #endif
123 | #ifdef FFI_THISCALL
124 | if (strcmp(name, "thiscall") == 0) return FFI_THISCALL;
125 | #endif
126 | #ifdef FFI_FASTCALL
127 | if (strcmp(name, "fastcall") == 0) return FFI_FASTCALL;
128 | #endif
129 | #ifdef FFI_MS_CDECL
130 | if (strcmp(name, "ms_cdecl") == 0) return FFI_MS_CDECL;
131 | #endif
132 | #ifdef FFI_WIN64
133 | if (strcmp(name, "win64") == 0) return FFI_WIN64;
134 | #endif
135 | return FFI_DEFAULT_ABI;
136 | }
137 |
138 |
139 | /* Define standard types.
140 | */
141 | static void define_types(void) {
142 |
143 | /* Standard ffi types
144 | */
145 | define_ffi_type("void", &ffi_type_void );
146 | define_ffi_type("sint8", &ffi_type_sint8 );
147 | define_ffi_type("sint16", &ffi_type_sint16 );
148 | define_ffi_type("sint32", &ffi_type_sint32 );
149 | define_ffi_type("sint64", &ffi_type_sint64 );
150 | define_ffi_type("uint8", &ffi_type_uint8 );
151 | define_ffi_type("uint16", &ffi_type_uint16 );
152 | define_ffi_type("uint32", &ffi_type_uint32 );
153 | define_ffi_type("uint64", &ffi_type_uint64 );
154 | define_ffi_type("float", &ffi_type_float );
155 | define_ffi_type("double", &ffi_type_double );
156 | define_ffi_type("schar", &ffi_type_schar );
157 | define_ffi_type("uchar", &ffi_type_uchar );
158 | define_ffi_type("sshort", &ffi_type_sshort );
159 | define_ffi_type("ushort", &ffi_type_ushort );
160 | define_ffi_type("sint", &ffi_type_sint );
161 | define_ffi_type("uint", &ffi_type_uint );
162 | define_ffi_type("slong", &ffi_type_slong );
163 | define_ffi_type("ulong", &ffi_type_ulong );
164 | define_ffi_type("longdouble", &ffi_type_longdouble);
165 | define_ffi_type("pointer", &ffi_type_pointer );
166 |
167 | /* Closer to C types. Could do these dynamically (YAGNI,
168 | * we support minimal number of platforms - 64 bit Linux
169 | * and 64 bit Windows, so any variation will be handled via
170 | * preprocessor)
171 | */
172 | define_ffi_type("int", &ffi_type_sint );
173 | define_ffi_type("long", &ffi_type_slong );
174 | define_ffi_type("short", &ffi_type_sshort );
175 | define_ffi_type("char", &ffi_type_schar );
176 | define_ffi_type("size_t", &ffi_type_uint );
177 | define_ffi_type("unsigned char", &ffi_type_uchar );
178 | define_ffi_type("unsigned int", &ffi_type_uint );
179 | define_ffi_type("unsigned long", &ffi_type_ulong );
180 | define_ffi_type("void *", &ffi_type_pointer);
181 | define_ffi_type("char *", &ffi_type_pointer);
182 |
183 | /* Extra types with more semantics. For now, the semantics are
184 | * automatic in fficall, but we can use a name in ffidefine to
185 | * show the intention. string is for JavaScript strings. buffer
186 | * is for JavaScript ArrayBuffers.
187 | */
188 | define_ffi_type("string", &ffi_type_pointer);
189 | define_ffi_type("buffer", &ffi_type_pointer);
190 | }
191 |
192 |
193 | /* Defined functions
194 | */
195 | static struct function_s *function_list = NULL;
196 |
197 |
198 | static int dummy_() {
199 | warn("dummy function in ffi");
200 | return 0;
201 | }
202 |
203 |
204 | /* Build function ffi
205 | */
206 | static bool define_function(const char *name,
207 | void *fp,
208 | const char *abi,
209 | const char *rtype,
210 | const char **args) {
211 |
212 | struct function_s *f = NULL;
213 | int i = 0;
214 | char *s = NULL;
215 |
216 | /* We need a name
217 | */
218 | if (name == NULL) {
219 | warn("define_function: no name");
220 | return false;
221 | }
222 |
223 | /* If function is already defined, just return
224 | */
225 | for (f = function_list; f; f = f->next)
226 | if (strcmp(f->name, name) == 0)
227 | return true;
228 |
229 | f = malloc(sizeof(struct function_s));
230 | if (f == NULL)
231 | fatal("define_function: no memory for function_s");
232 | f->name = NULL;
233 | f->args = NULL;
234 |
235 | f->name = strdup(name);
236 | if (f->name == NULL)
237 | fatal("define_function: no memory for name");
238 |
239 | /* If fp is not supplied, a dummy function will be used. This is
240 | * probably not something you want, but is useful when
241 | * prototyping.
242 | */
243 | f->fp = fp;
244 | if (f->fp == NULL)
245 | f->fp = dummy_;
246 |
247 | /* If abi is not supplied, use "default"
248 | */
249 | if (abi == NULL)
250 | abi = "default";
251 |
252 | /* Number of arguments (nargs).
253 | */
254 | for (f->nargs = 0; args && args[f->nargs]; ++f->nargs)
255 | ;
256 |
257 | /* Initialize argument types
258 | */
259 | f->args = NULL;
260 | if (f->nargs) {
261 | if (args == NULL) {
262 | warn("define_function: no args");
263 | goto error;
264 | }
265 | f->args = malloc(f->nargs * sizeof (ffi_type *));
266 | if (f->args == NULL)
267 | fatal("define_function: cannot alloc args");
268 | }
269 |
270 | /* Return type. Default to "void"
271 | */
272 | if (rtype == NULL)
273 | rtype = "void";
274 |
275 | /* Read each argument type. Record each type in f->args[].
276 | */
277 | for (i = 0; i < f->nargs; ++i) {
278 | s = (char *)args[i];
279 | if ((f->args[i] =
280 | find_ffi_type(s)) == NULL) {
281 | warn("define_function: no such type");
282 | goto error;
283 | }
284 | }
285 |
286 | /* Record return type
287 | */
288 | f->rtype = find_ffi_type(rtype);
289 | if (f->rtype == NULL) {
290 | warn("define_function: no such return type");
291 | goto error;
292 | }
293 |
294 | /* Prepare cif. Add prepared function to function list and return
295 | * true.
296 | */
297 | if (ffi_prep_cif(&(f->cif),
298 | find_abi(abi),
299 | f->nargs,
300 | f->rtype,
301 | f->args) == FFI_OK) {
302 | f->next = function_list;
303 | function_list = f;
304 | return true;
305 | }
306 | warn("define_function: ffi_prep_cif failed");
307 |
308 | /* On failure, return memory.
309 | */
310 | error:
311 | free(f->args);
312 | free(f->name);
313 | free(f);
314 | return false;
315 | }
316 |
317 |
318 | /* We assume little-endian, which means that assigning into "long long"
319 | * is sufficient to produce data that can be pointed to for char to
320 | * long long (and unsigned). So, we map all types into 3 types here.
321 | * 0 for integral, 1 for float, 2 for pointer.
322 | *
323 | * For big-endian (or other endian) this will have to be suitably
324 | * adjusted. Only call_function() is affected.
325 | */
326 |
327 |
328 | /* Call function previously defined
329 | *
330 | * Note that varargs are not yet supported. A custom function needs
331 | * to be built. But, we don't yet support deleting a defined function,
332 | * so varargs are not usable. Also, pass by structure is not supported.
333 | *
334 | * These restrictions could be lifted, but YAGNI. FIXME - we can call
335 | * a vararg, passing the parameter list itself. This would allow most
336 | * vprintf() etc (passing va_list ap). Structures should be passed by
337 | * value by defining the size of the structure, and passing a pointer
338 | * to the value. Structure return should work the same. For return,
339 | * the caller should supply a suitable area of memory. This should be
340 | * passed in as the rp parameter. Structures need definition
341 | * (define_structure(name, size)).
342 | *
343 | * All return values are captured into long double -- this must be
344 | * cast into the actual return.
345 | */
346 | static bool call_function(const char *name,
347 | typed_argument *args,
348 | long double *rp) {
349 | struct function_s *f = NULL;
350 | char *s = NULL;
351 | int i = 0;
352 | void **ptrs = NULL;
353 | void **pointer_list = NULL;
354 | int fl = 0;
355 | int pl = 0;
356 | argument *arguments = NULL;
357 | argument rc;
358 | double p = 0.0;
359 | long double r = 0.0;
360 | bool rv = false;
361 |
362 | if (name == NULL) {
363 | warn("call_function: no name");
364 | goto error;
365 | }
366 | for (f = function_list; f; f = f->next)
367 | if (strcmp(f->name, name) == 0)
368 | break;
369 | if (f == NULL) {
370 | warn("call_function: no such function");
371 | goto error;
372 | }
373 |
374 | if (f->nargs) {
375 | ptrs = malloc(f->nargs * sizeof (void *));
376 | if (ptrs == NULL)
377 | fatal("call_function: no memory for ptrs");
378 | pointer_list = malloc(f->nargs * sizeof (void *));
379 | if (pointer_list == NULL)
380 | fatal("call_function: no memory for pointer_list");
381 | arguments = malloc(f->nargs * sizeof (argument));
382 | if (arguments == NULL)
383 | fatal("call_function: no memory for arguments");
384 | if (args == NULL) {
385 | warn("call_function: nargs >= 1 but no args");
386 | goto error;
387 | }
388 | }
389 |
390 | /* initialize arguments, pointer_list and ptrs
391 | */
392 | fl = 0;
393 | pl = 0;
394 | for (i = 0; i < f->nargs; ++i) {
395 | arguments[i].p = NULL;
396 | pointer_list[i] = NULL;
397 | ptrs[i] = &(arguments[i]);
398 | }
399 |
400 | for (i = 0; i < f->nargs; ++i) {
401 | switch (args[i].type) {
402 |
403 | case TYPE_INTEGRAL:
404 | if (f->args[i] == &ffi_type_float)
405 | arguments[i].f = (float)args[i].arg.ll;
406 |
407 | else if (f->args[i] == &ffi_type_double)
408 | arguments[i].d = (double)args[i].arg.ll;
409 |
410 | else if (f->args[i] == &ffi_type_longdouble)
411 | arguments[i].ld = (long double)args[i].arg.ll;
412 |
413 | else if (f->args[i] == &ffi_type_pointer) {
414 | pointer_list[pl++] =
415 | (void *)(long long)args[i].arg.ll;
416 | ptrs[i] = &(pointer_list[pl - 1]);
417 |
418 | } else
419 | /* This absorbs all ffi_type integral */
420 | arguments[i].ll = (long long)args[i].arg.ll;
421 | break;
422 |
423 | case TYPE_FLOAT:
424 | if (f->args[i] == &ffi_type_float)
425 | arguments[i].f = (float)args[i].arg.ld;
426 |
427 | else if (f->args[i] == &ffi_type_double)
428 | arguments[i].d = (double)args[i].arg.ld;
429 |
430 | else if (f->args[i] == &ffi_type_longdouble)
431 | arguments[i].ld = (long double)args[i].arg.ld;
432 |
433 | else if (f->args[i] == &ffi_type_pointer) {
434 | long long t = (long long)args[i].arg.ld;
435 | pointer_list[pl++] = (void *)t;
436 | ptrs[i] = &(pointer_list[pl - 1]);
437 |
438 | } else
439 | arguments[i].ll = (long long)args[i].arg.ld;
440 |
441 | break;
442 |
443 | case TYPE_POINTER:
444 | pointer_list[pl++] = args[i].arg.p;
445 | ptrs[i] = &(pointer_list[pl - 1]);
446 | break;
447 |
448 | default:
449 | warn("call_function: error, unknown typed parameter");
450 | goto error;
451 |
452 | }
453 | }
454 |
455 | /* call the function
456 | */
457 | ffi_call(&(f->cif), f->fp, &rc, ptrs);
458 |
459 | /* result - simple conversion to float
460 | *
461 | * Underlying code assumes that everything is compatible with
462 | * long double -- but classic JavaScript uses double. This gives
463 | * us 53 (or 52) bits for integer results -- pray that all
464 | * pointers fit in 52 bits. This is a reasonable assumption in
465 | * 2020.
466 | */
467 | if (f->rtype == &ffi_type_void)
468 | r = 0;
469 | else if (f->rtype == &ffi_type_float)
470 | r = rc.f;
471 | else if (f->rtype == &ffi_type_double)
472 | r = rc.d;
473 | else if (f->rtype == &ffi_type_longdouble)
474 | r = rc.ld;
475 | else if (f->rtype == &ffi_type_pointer)
476 | r = (double)(long long)rc.p;
477 | else
478 | r = rc.ll;
479 |
480 | if (rp)
481 | *rp = r;
482 | rv = true;
483 |
484 | error:
485 | if (ptrs)
486 | free(ptrs);
487 | if (pointer_list)
488 | free(pointer_list);
489 | if (arguments)
490 | free(arguments);
491 | return rv;
492 | }
493 |
494 |
495 | /* debug()
496 | */
497 | static JSValue js_debug(JSContext *ctx, JSValueConst this_val,
498 | int argc, JSValueConst *argv) {
499 | return JS_NULL;
500 | }
501 |
502 |
503 | /* errno()
504 | */
505 | static JSValue js_errno(JSContext *ctx, JSValueConst this_val,
506 | int argc, JSValueConst *argv) {
507 | int e;
508 | e = errno;
509 | return JS_NewInt32(ctx, e);
510 | }
511 |
512 |
513 | /* h = dlopen(name, flags)
514 | */
515 | static JSValue js_dlopen(JSContext *ctx, JSValueConst this_val,
516 | int argc, JSValueConst *argv) {
517 | const char *s;
518 | void *res;
519 | int n;
520 | if (JS_IsNull(argv[0]))
521 | s = NULL;
522 | else {
523 | s = JS_ToCString(ctx, argv[0]);
524 | if (!s)
525 | return JS_EXCEPTION;
526 | }
527 | if (JS_ToUint32(ctx, &n, argv[1]))
528 | return JS_EXCEPTION;
529 | res = dlopen(s, n);
530 | if (s)
531 | JS_FreeCString(ctx, s);
532 | if (res == NULL)
533 | return JS_NULL;
534 | return JS_NewInt64(ctx, (long long)res);
535 | }
536 |
537 |
538 | /* s = dlerror()
539 | */
540 | static JSValue js_dlerror(JSContext *ctx, JSValueConst this_val,
541 | int argc, JSValueConst *argv) {
542 | char *res;
543 | res = dlerror();
544 | if (res == NULL)
545 | return JS_NULL;
546 | return JS_NewString(ctx, res);
547 | }
548 |
549 |
550 | /* n = dlclose(h)
551 | */
552 | static JSValue js_dlclose(JSContext *ctx, JSValueConst this_val,
553 | int argc, JSValueConst *argv) {
554 | int res;
555 | int64_t n;
556 | if (JS_ToInt64(ctx, &n, argv[0]))
557 | return JS_EXCEPTION;
558 | res = dlclose((void *)n);
559 | return JS_NewInt32(ctx, res);
560 | }
561 |
562 |
563 | /* p = dlsym(h, name)
564 | */
565 | static JSValue js_dlsym(JSContext *ctx, JSValueConst this_val,
566 | int argc, JSValueConst *argv) {
567 | void *res;
568 | int64_t n;
569 | const char *s;
570 | if (JS_ToInt64(ctx, &n, argv[0]))
571 | return JS_EXCEPTION;
572 | s = JS_ToCString(ctx, argv[1]);
573 | if (!s)
574 | return JS_EXCEPTION;
575 | res = dlsym((void *)n, s);
576 | if (s)
577 | JS_FreeCString(ctx, s);
578 | if (res == NULL)
579 | return JS_NULL;
580 | return JS_NewInt64(ctx, (long long)res);
581 | }
582 |
583 | #define MAX_PARAMETERS 30
584 |
585 | /* define(name, fp, abi, ret, p1,...pn)
586 | */
587 | static JSValue js_define(JSContext *ctx, JSValueConst this_val,
588 | int argc, JSValueConst *argv) {
589 | const char *name = NULL;
590 | int64_t fp = 0;
591 | const char *abi = NULL;
592 | const char *rtype = NULL;
593 | const char *params[MAX_PARAMETERS + 1];
594 | int i, nparams = 0;
595 | JSValue r = JS_FALSE;
596 | name = JS_ToCString(ctx, argv[0]);
597 | if (!name)
598 | goto error;
599 | if (JS_ToInt64(ctx, &fp, argv[1]))
600 | goto error;
601 | if (JS_IsNull(argv[2]))
602 | abi = NULL;
603 | else {
604 | abi = JS_ToCString(ctx, argv[2]);
605 | if (!abi)
606 | goto error;
607 | }
608 | rtype = JS_ToCString(ctx, argv[3]);
609 | if (!rtype)
610 | goto error;
611 | nparams = 0;
612 | for (i = 4; (i < argc) && (nparams < MAX_PARAMETERS); ++i)
613 | params[nparams++] = JS_ToCString(ctx, argv[i]);
614 | params[nparams] = NULL;
615 |
616 | if (define_function(name,
617 | (void *)fp,
618 | abi,
619 | rtype,
620 | params))
621 | r = JS_TRUE;
622 | else
623 | r = JS_FALSE;
624 |
625 | error:
626 | if (name)
627 | JS_FreeCString(ctx, name);
628 | if (rtype)
629 | JS_FreeCString(ctx, rtype);
630 | if (abi)
631 | JS_FreeCString(ctx, abi);
632 | for (i = 0; i < nparams; ++i)
633 | JS_FreeCString(ctx, params[i]);
634 |
635 | return r;
636 | }
637 |
638 | /* For 2020-01-19 support */
639 | static inline JS_BOOL JS_IsInteger(JSValueConst v) {
640 | int tag = JS_VALUE_GET_TAG(v);
641 | return tag == JS_TAG_INT || tag == JS_TAG_BIG_INT;
642 | }
643 |
644 | /* r = call(name, p1,...pn)
645 | */
646 | static JSValue js_call(JSContext *ctx, JSValueConst this_val,
647 | int argc, JSValueConst *argv) {
648 | const char *name = NULL;
649 | JSValue r = JS_EXCEPTION;
650 | typed_argument args[MAX_PARAMETERS];
651 | int i;
652 | long double res = 0;
653 | const char *strings[MAX_PARAMETERS];
654 | int fl = 0;
655 | name = JS_ToCString(ctx, argv[0]);
656 | if (!name)
657 | goto error;
658 |
659 | for (i = 0; i < MAX_PARAMETERS; ++i) {
660 | args[i].arg.ll = 0;
661 | args[i].type = TYPE_INTEGRAL;
662 | }
663 | for (i = 1; (i < argc) && (i <= MAX_PARAMETERS); ++i) {
664 | if (JS_IsNull(argv[i])) {
665 | ;
666 | } else if (JS_IsBool(argv[i])) {
667 | args[i - 1].arg.ll = JS_ToBool(ctx, argv[i]);
668 | if (args[i-1].arg.ll < 0)
669 | goto error;
670 | } else if (JS_IsInteger(argv[i])) {
671 | int64_t v;
672 | if (JS_ToInt64(ctx, &v, argv[i]))
673 | goto error;
674 | args[i - 1].arg.ll = v;
675 | } else if (JS_IsNumber(argv[i])) {
676 | double d;
677 | if (JS_ToFloat64(ctx, &d, argv[i]))
678 | goto error;
679 | args[i - 1].arg.ld = (long double)d;
680 | args[i - 1].type = TYPE_FLOAT;
681 | } else if (JS_IsString(argv[i])) {
682 | const char *s;
683 | s = JS_ToCString(ctx, argv[i]);
684 | if (!s)
685 | goto error;
686 | strings[fl++] = s;
687 | args[i - 1].arg.ll = (long long)s;
688 | } else {
689 | uint8_t *buf;
690 | size_t size;
691 | buf = JS_GetArrayBuffer(ctx, &size, argv[i]);
692 | if (!buf)
693 | goto error;
694 | args[i - 1].arg.ll = (long long)buf;
695 | }
696 | }
697 | if (call_function(name,
698 | args,
699 | &res)) {
700 | r = JS_NewFloat64(ctx, res);
701 | }
702 |
703 | error:
704 | if (name)
705 | JS_FreeCString(ctx, name);
706 | while (fl)
707 | JS_FreeCString(ctx, strings[--fl]);
708 | return r;
709 | }
710 |
711 |
712 | /* s = toString(p)
713 | */
714 | static JSValue js_tostring(JSContext *ctx, JSValueConst this_val,
715 | int argc, JSValueConst *argv) {
716 | const char *s;
717 | int64_t p;
718 | if (JS_ToInt64(ctx, &p, argv[0]))
719 | return JS_EXCEPTION;
720 | s = (const char *)p;
721 | return JS_NewString(ctx, s);
722 | }
723 |
724 |
725 | /* b = toArrayBuffer(p, size)
726 | */
727 | static JSValue js_toarraybuffer(JSContext *ctx, JSValueConst this_val,
728 | int argc, JSValueConst *argv) {
729 | const uint8_t *buf;
730 | int64_t p, s;
731 | size_t size;
732 | if (JS_ToInt64(ctx, &p, argv[0]))
733 | return JS_EXCEPTION;
734 | if (JS_ToInt64(ctx, &s, argv[1]))
735 | return JS_EXCEPTION;
736 | if (s < 0)
737 | return JS_EXCEPTION;
738 | size = s;
739 |
740 | buf = (const uint8_t *)p;
741 | return JS_NewArrayBufferCopy(ctx, buf, size);
742 | }
743 |
744 | /* s = toPointer(ArrayBuffer[, offset])
745 | *
746 | * returns string 0xAABBCCDD which is the base of the ArrayBuffer
747 | * plus an optional offset. A negative offset can be used,
748 | * indicating an offset from the end of the buffer.
749 | */
750 | static JSValue js_topointer(JSContext *ctx, JSValueConst this_val,
751 | int argc, JSValueConst *argv) {
752 | uint8_t *ptr;
753 | size_t size;
754 | char buf[64];
755 | ptr = JS_GetArrayBuffer(ctx, &size, argv[0]);
756 | if(argc > 1) {
757 | int64_t off;
758 | if(JS_ToInt64(ctx, &off, argv[1]))
759 | return JS_EXCEPTION;
760 | if(off < 0)
761 | off += size;
762 | if(off > size || off < 0)
763 | return JS_EXCEPTION;
764 | ptr += off;
765 | }
766 | snprintf(buf, sizeof(buf), "%p", ptr);
767 | return JS_NewString(ctx, buf);
768 | }
769 |
770 | /* p = JSContext()
771 | */
772 | static JSValue js_context(JSContext *ctx, JSValueConst this_val,
773 | int argc, JSValueConst *argv) {
774 | return JS_NewInt64(ctx, (long long)ctx);
775 | }
776 |
777 |
778 | static const JSCFunctionListEntry js_funcs[] = {
779 | JS_CFUNC_DEF("debug", 0, js_debug),
780 | JS_CFUNC_DEF("dlopen", 2, js_dlopen),
781 | JS_CFUNC_DEF("dlerror", 0, js_dlerror),
782 | JS_CFUNC_DEF("dlclose", 1, js_dlclose),
783 | JS_CFUNC_DEF("dlsym", 2, js_dlsym),
784 | JS_CFUNC_DEF("define", 4, js_define),
785 | JS_CFUNC_DEF("call", 1, js_call),
786 | JS_CFUNC_DEF("toString", 1, js_tostring),
787 | JS_CFUNC_DEF("toArrayBuffer", 2, js_toarraybuffer),
788 | JS_CFUNC_DEF("toPointer", 1, js_topointer),
789 | JS_CFUNC_DEF("errno", 0, js_errno),
790 | JS_CFUNC_DEF("JSContext", 0, js_context),
791 | #ifdef RTLD_LAZY
792 | JS_PROP_INT32_DEF("RTLD_LAZY", RTLD_LAZY, JS_PROP_CONFIGURABLE),
793 | #endif
794 | #ifdef RTLD_NOW
795 | JS_PROP_INT32_DEF("RTLD_NOW", RTLD_NOW, JS_PROP_CONFIGURABLE),
796 | #endif
797 | #ifdef RTLD_GLOBAL
798 | JS_PROP_INT32_DEF("RTLD_GLOBAL", RTLD_GLOBAL, JS_PROP_CONFIGURABLE),
799 | #endif
800 | #ifdef RTLD_LOCAL
801 | JS_PROP_INT32_DEF("RTLD_LOCAL", RTLD_LOCAL, JS_PROP_CONFIGURABLE),
802 | #endif
803 | #ifdef RTLD_NODELETE
804 | JS_PROP_INT32_DEF("RTLD_NODELETE", RTLD_NODELETE,
805 | JS_PROP_CONFIGURABLE),
806 | #endif
807 | #ifdef RTLD_NOLOAD
808 | JS_PROP_INT32_DEF("RTLD_NOLOAD", RTLD_NOLOAD, JS_PROP_CONFIGURABLE),
809 | #endif
810 | #ifdef RTLD_DEEPBIND
811 | JS_PROP_INT32_DEF("RTLD_DEEPBIND", RTLD_DEEPBIND,
812 | JS_PROP_CONFIGURABLE),
813 | #endif
814 | #ifdef RTLD_DEFAULT
815 | JS_PROP_INT64_DEF("RTLD_DEFAULT", (long)RTLD_DEFAULT,
816 | JS_PROP_CONFIGURABLE),
817 | #endif
818 | #ifdef RTLD_NEXT
819 | JS_PROP_INT64_DEF("RTLD_NEXT", (long)RTLD_NEXT,
820 | JS_PROP_CONFIGURABLE),
821 | #endif
822 | JS_PROP_INT32_DEF("pointerSize", sizeof(void*), JS_PROP_CONFIGURABLE)
823 | };
824 |
825 |
826 | static int js_init(JSContext *ctx, JSModuleDef *m) {
827 | define_types();
828 | return JS_SetModuleExportList(ctx, m, js_funcs,
829 | countof(js_funcs));
830 | }
831 |
832 |
833 | #ifdef JS_SHARED_LIBRARY
834 | #define JS_INIT_MODULE js_init_module
835 | #else
836 | #define JS_INIT_MODULE js_init_module_ffi
837 | #endif
838 |
839 |
840 | JSModuleDef *JS_INIT_MODULE(JSContext *ctx, const char *module_name) {
841 | JSModuleDef *m;
842 | m = JS_NewCModule(ctx, module_name, js_init);
843 | if (!m)
844 | return NULL;
845 | JS_AddModuleExportList(ctx, m, js_funcs, countof(js_funcs));
846 |
847 | return m;
848 | }
849 |
850 |
851 | /* ce: .mc; */
852 |
--------------------------------------------------------------------------------
/test.c:
--------------------------------------------------------------------------------
1 | /**********************************************************************
2 | * *
3 | * test.c *
4 | * *
5 | **********************************************************************/
6 |
7 | /* test functions for qjs-ffi
8 | */
9 |
10 | #include
11 | #include
12 |
13 | int test1(unsigned char *p) {
14 | printf("%d %d %d\n", p[0], p[1], p[2]);
15 | p[0] = 3;
16 | p[1] = 2;
17 | p[2] = 1;
18 | return 5;
19 | }
20 |
21 | /* ce: .mc; */
22 |
--------------------------------------------------------------------------------
/test.js:
--------------------------------------------------------------------------------
1 | /* test.js
2 | *
3 | * Test harness for JavaScript ffi
4 | *
5 | * Tektonics:
6 | *
7 | * Build shared object:
8 | *
9 | * gcc -g -fPIC -DJS_SHARED_LIBRARY -c ffi.c
10 | * gcc -g -shared -o ffi.so ffi.o -lffi -ldl
11 | *
12 | * Debug shared object:
13 | *
14 | * gdb qjs
15 | * set args test.js
16 | * b js_debug
17 | * run
18 | *
19 | * This will stop in gdb on the first debug(); call.
20 | *
21 | */
22 |
23 | import * as std from "std";
24 | import * as os from "os";
25 | import { debug, dlopen, dlerror, dlclose, dlsym,
26 | define, call, toString, toArrayBuffer, toPointer,
27 | errno, JSContext,
28 | RTLD_LAZY, RTLD_NOW, RTLD_GLOBAL, RTLD_LOCAL,
29 | RTLD_NODELETE, RTLD_NOLOAD, RTLD_DEEPBIND,
30 | RTLD_DEFAULT, RTLD_NEXT } from "./ffi.so";
31 |
32 | var h;
33 | var r;
34 | console.log("Hello World");
35 | debug();
36 | console.log("RTLD_NOW = ", RTLD_NOW);
37 | /* Expect an error -- libc.so is (usually) a linker script */
38 | console.log("dlopen = ", r = dlopen("libc.so", RTLD_NOW));
39 | if (r == null)
40 | console.log("dlerror = ", dlerror());
41 | /* But, using libc.so.6 should work */
42 | console.log("dlopen = ", h = dlopen("libc.so.6", RTLD_NOW));
43 | if (h == null)
44 | console.log("dlerror = ", dlerror());
45 | console.log("dlsym = ", r = dlsym(h, "malloc"));
46 | if (r == null)
47 | console.log("dlerror = ", dlerror());
48 | console.log("dlclose = ", r = dlclose(h));
49 | if (r != 0)
50 | console.log("dlerror = ", dlerror());
51 | console.log("dlopen = ", h = dlopen(null, RTLD_NOW));
52 | if (h == null)
53 | console.log("dlerror = ", dlerror());
54 | console.log("dlsym = ", r = dlsym(h, "malloc"));
55 | if (r == null)
56 | console.log("dlerror = ", dlerror());
57 | console.log("dlclose = ", r = dlclose(h));
58 | if (r != 0)
59 | console.log("dlerror = ", dlerror());
60 | var malloc;
61 | console.log("dlsym = ", malloc = dlsym(RTLD_DEFAULT, "malloc"));
62 | if (malloc == null)
63 | console.log("dlerror = ", dlerror());
64 | var free;
65 | console.log("dlsym = ", free = dlsym(RTLD_DEFAULT, "free"));
66 | if (free == null)
67 | console.log("dlerror = ", dlerror());
68 |
69 | /* We have function pointers to malloc and free -- define the ffi
70 | * functions
71 | */
72 | define("malloc", malloc, null, "void *", "size_t");
73 | define("free", free, null, "void", "void *");
74 |
75 | /* p = malloc(10); display pointer, free(p)
76 | */
77 | var p;
78 | p = call("malloc", 10);
79 | console.log(p);
80 | call("free", p);
81 |
82 | /* n = strlen("hello"); which should result in 5
83 | */
84 | var strlen;
85 | strlen = dlsym(RTLD_DEFAULT, "strlen");
86 | if (strlen == null)
87 | console.log(dlerror());
88 | define("strlen", strlen, null, "int", "char *");
89 |
90 | var n;
91 | n = call("strlen", "hello");
92 | /* We expect 5 */
93 | console.log(n);
94 |
95 | /* p = strdup("dup this").
96 | */
97 | var strdup;
98 | strdup = dlsym(RTLD_DEFAULT, "strdup");
99 | if (strdup == null)
100 | console.log(dlerror());
101 | define("strdup", strdup, null, "char *", "char *");
102 |
103 | p = call("strdup", "dup this");
104 |
105 | /* Convert strdup() result into a string (should display
106 | * dup this 8
107 | */
108 | var s;
109 | s = toString(p);
110 | console.log(s, call("strlen", p));
111 |
112 |
113 | console.log();
114 | console.log('testing test.so functions');
115 | h = dlopen('./test.so', RTLD_NOW);
116 | if (h == null)
117 | console.log("can't load ./test.so: ", dlerror());
118 | var fp;
119 | fp = dlsym(h, "test1");
120 | if (fp == null)
121 | console.log("can't find symbol test1: ", dlerror());
122 | if (!define("test1", fp, null, "int", "void *"))
123 | console.log("can't define test1");
124 | /* test1 takes a buffer but a string will work -- changes to the string
125 | * are lost, because a writable buffer is passed, but discarded before
126 | * the return.
127 | */
128 | r = call("test1", "abc");
129 | console.log("should be 5: ", r);
130 | /* pass buffer to test1 -- test1 changes the buffer in place, and this
131 | * is reflected in quickjs
132 | */
133 | var b;
134 | b = new ArrayBuffer(8);
135 | var u;
136 | u = new Uint8Array(b);
137 | u[0] = 1;
138 | u[1] = 2;
139 | u[2] = 3;
140 | console.log("should print 1 2 3");
141 | r = call("test1", b);
142 | console.log("should print 3,2,1,0,0,0,0,0");
143 | console.log(u);
144 |
145 | /* p is a pointer to "dup this" -- 9 bytes of memory
146 | */
147 | b = toArrayBuffer(p, 9);
148 | u = new Uint8Array(b);
149 | console.log(u);
150 |
151 | fp = dlsym(RTLD_DEFAULT, "strcpy");
152 | if (fp == null)
153 | console.log(dlerror());
154 | define("strcpy", fp, null, "string", "string", "string");
155 |
156 | b = toArrayBuffer(p, 16);
157 | call("free", p);
158 |
159 | let q;
160 | p = toPointer(b);
161 | q = toPointer(b, 4);
162 | console.log(q, "should be " + (BigInt(p) + 4n).toString(16));
163 |
164 | call("strcpy", +q, "this pointer");
165 | console.log(toString(q), call("strlen", +q));
166 |
167 |
168 | fp = dlsym(RTLD_DEFAULT, "strtoul");
169 | if (fp == null)
170 | console.log(dlerror());
171 | define("strtoul", fp, null, "ulong", "string", "string", "int");
172 | n = call("strtoul", "1234", null, 0);
173 | console.log(n, "Should be 1234");
174 | call("strtoul", '1234567890123456789012345678901234567890', null, 0);
175 | console.log(errno(), "should be 34 (ERANGE)");
176 |
177 | p = JSContext();
178 | console.log("jscontext = ", p);
179 |
180 |
181 |
--------------------------------------------------------------------------------
/test2.js:
--------------------------------------------------------------------------------
1 | /* test2.js
2 | */
3 |
4 | import * as os from "os";
5 | import * as std from "std";
6 | import * as util from "./util.mjs";
7 |
8 | var pid;
9 | var status;
10 |
11 | pid = util.fork();
12 | if (pid == 0) {
13 | console.log("in child: ", pid, "my pid is", util.getpid());
14 | console.log("parent is:", util.getppid());
15 | console.log("_exit:", util._exit);
16 | /* _exit() does not call any atexit function, etc. bail out fast!
17 | * std.exit() can be used as well.
18 | */
19 | util._exit(5);
20 | } else {
21 | console.log("in parent, child pid: ", pid, "my pid is", util.getpid());
22 | os.sleep(1000);
23 | status = os.waitpid(pid, 0);
24 | console.log(status, status[1] >> 8, "should be 5");
25 | }
26 |
--------------------------------------------------------------------------------
/util.mjs:
--------------------------------------------------------------------------------
1 | /* util.mjs
2 | */
3 |
4 | import * as std from "std";
5 | import * as os from "os";
6 | import * as ffi from "./ffi.so";
7 |
8 |
9 | /* Define a function
10 | */
11 | export function define(so, name, rtype, ...args) {
12 | if ((so == null) || (so == undefined))
13 | so = ffi.RTLD_DEFAULT;
14 | var p = ffi.dlsym(so, name);
15 | if (p == null) {
16 | console.log(name, "not in so");
17 | std.exit(1);
18 | }
19 | if (!ffi.define(name, p, null, rtype, ...args)) {
20 | console.log("define failed");
21 | std.exit(1);
22 | }
23 | return function (...a) {
24 | return ffi.call(name, ...a);
25 | }
26 | }
27 |
28 | export var getpid = define(null, "getpid", "int");
29 | export var getppid = define(null, "getppid", "int");
30 | export var _exit = define(null, "_exit", "void", "int");
31 | export var fork = define(null, "fork", "int");
32 |
--------------------------------------------------------------------------------