├── 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 | --------------------------------------------------------------------------------