Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pass handle down pipeline

Say I have

node foo.js | node bar.js

is there a way to pass a handle on foo's stdin to bar.js?

I have a rare case where I'd like to communicate backwards in the pipeline.

At the least I know that I could send node bar.js the pid of node foo.js. Given that pid, on *nix, I should be able to write to foo's stdin using:

/proc/<pid>/fd/0

but is there a way to do the same on MacOS?

like image 704
Alexander Mills Avatar asked Jan 30 '23 05:01

Alexander Mills


1 Answers

So there are different ways of doing it.

Approach 1 - IOCTL

This is inspired from

https://stackoverflow.com/a/36522620/2830850

So you create writevt.c file with below content

/*
 * Mostly ripped off of console-tools' writevt.c
 */

#include <stdio.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <unistd.h>

char *progname;

static int usage() {
    printf("Usage: %s ttydev text\n", progname);
    return 2;
}

int main(int argc, char **argv) {
    int fd, argi;
    char *term = NULL;
    char *text = NULL;

    progname = argv[0];

    argi = 1;

    if (argi < argc)
        term = argv[argi++];
    else {
        fprintf(stderr, "%s: no tty specified\n", progname);
        return usage();
    }

    if (argi < argc)
        text = argv[argi++];
    else {
        fprintf(stderr, "%s: no text specified\n", progname);
        return usage();
    }

    if (argi != argc) {
        fprintf(stderr, "%s: too many arguments\n", progname);
        return usage();
    }

    fd = open(term, O_RDONLY);
    if (fd < 0) {
        perror(term);
        fprintf(stderr, "%s: could not open tty\n", progname);
        return 1;
    }

    while (*text) {
        if (ioctl(fd, TIOCSTI, text)) {
            perror("ioctl");
            return 1;
        }
        text++;
    }

    return 0;
}

Compile it using below

gcc -o writevt writevt.c

Then add root permission to the same

sudo chown root:wheel writevt
sudo chmod 4755 writevt

Now I created a simple foo.js with below code

var stdin = process.openStdin();

stdin.addListener("data", function(d) {
    console.log(process.env.NAME + " entered: [" +
        d.toString().trim() + "]");
});

And in a terminal run first the tty command

$ tty
/dev/ttys019

And now run the code like below

NAME=A node foo.js  | NAME=B node foo.js

Now from another terminal run the below command

./writevt /dev/ttys019 "FROM external command^M"

^M here is CTRL+V + CTRL+ENTER on Mac

Content

As you can see from the gif the input reaches stdin of A and then A prints on stdout and which is received by B then. So if I modify the code like below

var stdin = process.openStdin();

stdin.addListener("data", function(d) {
    console.log(process.env.NAME + " entered: [" +
        d.toString().trim() + "]");
});

if (process.env.NAME === "B") {
    setInterval(function() {
        require('child_process').exec(`./writevt /dev/ttys019 "Hello from B?
"`)
    }, 1000)
}

Note 1: ^M was added using Vim inside the above code

Note 2: The TTY location has been hard coded in this but you can pass it through a environment variable by running

export TTY=`tty`

And then using process.env.TTY in the code. The updated results are

Working

Approach 2 - FIFO files

In this approach you make a fifo file first

$ mkfifo nodebridge

Now you change your code like below

var stdin = process.openStdin();
var fs = require("fs")
stdin.addListener("data", function(d) {
    console.log(process.env.NAME + " entered: [" +
        d.toString().trim() + "]");
});

if (process.env.NAME === "B") {
    setInterval( () => {
        require('child_process').exec('printf "Hello from B?\\n" > nodebridge')
    }, 1000);
}

And run the command like below

NAME=A node foo.js < nodebridge | NAME=B node foo.js

NodeBridge

like image 200
Tarun Lalwani Avatar answered Jan 31 '23 18:01

Tarun Lalwani