Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save guest271314/1b3f30ef0ed8d2e3888c1b86cceaf781 to your computer and use it in GitHub Desktop.
Save guest271314/1b3f30ef0ed8d2e3888c1b86cceaf781 to your computer and use it in GitHub Desktop.
How to read stardard input to a different process using QuickJS

Occasionally we might be experimenting inside a JavaScript or other engine, runtime, interpreter, or context where reading the standard input stream to the current process is either unintentionally or deliberately restricted.

The V8 JavaScript/WebAssembly engine has a developer shell called d8. d8 has a readline() function that in general expects input from a TTY. Further, the expectation is generally that the input will be evaluated as a script. See Using d8. v8 is about 37.6 MB.

What this means is that if d8 is launched by a different process reading stardard input to d8 is not straightforward where the input is not necessarily UTF-8.

Bellard's QuickJS (qjs) is aboout 1.2 MB. See quickjs and quickjs-ng.

dd and head

We can use the dd command, supported on several operating systems, and implemented by Busybox.

We can also use GNU Coreutils head to read stadard input streams.

For example, we get the PID of the current process, then read /proc/<PID>/fd/0, then send the read standard input stream back to the process.

#!/bin/bash
# Bash Native Messaging host
# Read STDIN for V8's d8 shell, return message to d8
# guest271314 2024

set -x
set -o posix

getNativeMessage() {
  # https://lists.gnu.org/archive/html/help-bash/2023-06/msg00036.html
  length=$(dd iflag=fullblock oflag=nocache conv=notrunc,fdatasync bs=4 count=1 if=/proc/$@/fd/0 | od -An -td4 -)
  message=$(dd iflag=fullblock oflag=nocache conv=notrunc,fdatasync bs=$((length)) count=1 if=/proc/$@/fd/0)
  # length=$(head -q -z --bytes=4 /proc/$@/fd/0 | od -An -td4 -)
  # message=$(head -q -z --bytes=$((length)) /proc/$@/fd/0)
  echo "$message" 
}

getNativeMessage "$1"

JavaScript

Let's do this using JavaScript. Using as little resources as possible. It wouldn't make sense to use node (108.7 MB) or deno (128.8 MB) or bun (92.5 MB). We'll use qjs (in this case quickjs-ng/quickjs) at 1.2 MB to read standard input to V8's d8 shell that is from a non-TTY parent application, then send the standard input stream back to d8 for processing.

We start qjs within d8 with

const stdin = os.system("./read_d8_stdin.js", [`/proc/${pid.replace(/\D+/g, "")}/fd/0`]);

then read the result in the stdin variable in d8

#!/usr/bin/env -S /home/user/bin/qjs -m --std
// Read stdin to V8's d8, send to d8
// const stdin = os.system("./read_d8_stdin.js", [`/proc/${pid.replace(/\D+/g, "")}/fd/0`]);
function read_d8_stdin([, path] = scriptArgs) {
  try {
    const size = new Uint32Array(1);
    const err = { errno: 0 };
    const pipe = std.open(
      path,
      "rb",
      err,
    );
    if (err.errno !== 0) {
      throw `${std.strerror(err.errno)}: ${path}`;
    }
    pipe.read(size.buffer, 0, 4);
    const output = new Uint8Array(size[0]);
    pipe.read(output.buffer, 0, output.length);
    std.out.write(output.buffer, 0, output.length);
    std.out.flush();
    std.exit(0);
  } catch (e) {
    const err = { errno: 0 };
    const file = std.open("qjsErr.txt", "w", err);
    if (err.errno !== 0) {
      file.puts(JSON.stringify(err));
      file.close();
      std.exit(1);
    }
    file.puts(JSON.stringify(e));
    file.close();
    std.out.puts(JSON.stringify(err));
    std.exit(1);
  }
}

read_d8_stdin();

Notice we also handle errors, and send the error message to the caller/parent process.

We have successfully read the standard input of a different process using QuickJS JavaScript engine/runtime for 1.2 MB.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment