A way to call execl, execle, execlp, execv, execvP or execvp from Node.js
Asked Answered
F

4

4

POSIX systems expose family of exec functions, that allow one to load something maybe different into current process, keeping open file descriptors, process identifier and so on.

This can be done for variety of reasons, and in my case this is bootstrapping — I want to change command line options of my own process, and then reload it over existing process, so there would be no child process.

Unfortunately, to much of my surprise, I could not find the way to call any of exec* functions in Node.js. So, what is the correct way to replace currently running Node.js process with other image?

Frowzy answered 15/12, 2015 at 13:22 Comment(11)
Consider this question: #4018654Wendall
Consider this nodejs.org/api/child_process.htmlZonnya
@Zonnya child_process does not overwrite existing process. It spawns a new one, and it will have new PID.Frowzy
@Wendall I don't see how is it related.Frowzy
What are you trying to do, that you need to overwrite the exact process again?Wendall
@clay, (a) bootstrapping. node started with no options, I need to add options like --expose_gc_as=V8GC or --harmony, (b) or I just want to open some files and leave their descriptors for the next application that I am going to overload, (c) or I am going through daemonization step and want to fork() (btw, there is no way to call fork() either), (d) or anything else people use exec for in other languages and platforms.Frowzy
Cool stuff. Never needed that type of functionality in Nodejs. Should you find an answer, be sure to post an answer back here.Wendall
@Frowzy have you found a solution?Lotson
@OleksiiRudenko, yes. I have used ffi module, and exposed execvp to the process. Please keep in mind fork won't work, because V8 has threads, and fork() syscall only copies main thread, so forked interpreter crashes immediately.Frowzy
I see. I didn't find a ready solution myself so I created this module github.com/OrKoN/native-exec to invoke execvp. It's without ffi, compiled as a native node addon instead.Lotson
@OleksiiRudenko please make this into an answer, I will accept it.Frowzy
L
5

I have created a module to invoke execvp function from NodeJS: https://github.com/OrKoN/native-exec

It works like this:

var exec = require('native-exec');

exec('ls', {
  newEnvKey: newEnvValue,
}, '-lsa'); // => the process is replaced with ls, which runs and exits

Since it's a native node addon it requires a C++ compiler installed. Works fine in Docker, on Mac OS and Linux. Probably, does not work on Windows. Tested with node 6, 7 and 8.

Lotson answered 30/6, 2017 at 11:28 Comment(1)
I guess it shouldn't work in Windows, unless started within LXSS.Frowzy
V
3

Here is an example using node-ffi that works with node v10. (alas, not v12)

#!/usr/bin/node

"use strict";

const ffi = require('ffi');
const ref = require('ref');
const ArrayType = require('ref-array');
const stringAry = ArrayType('string');

const readline = require("readline");
const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});

rl.question('Login: ', (username) => {
    username = username.replace(/[^a-z0-9_]/g, "");
    rl.close();
    execvp("/usr/bin/ssh", "-e", "none", username+'@localhost');
});



function execvp() {
    var current = ffi.Library(null, 
                              { execvp: ['int', ['string',
                                                 stringAry]],
                                dup2: ['int', ['int', 'int']]});
    current.dup2(process.stdin._handle.fd, 0);
    current.dup2(process.stdout._handle.fd, 1);
    current.dup2(process.stderr._handle.fd, 2);
    var ret = current.execvp(arguments[0], Array.prototype.slice.call(arguments).concat([ref.NULL]));    
}
Virile answered 13/8, 2019 at 20:3 Comment(0)
F
2

I ended up using ffi module, and exported execvp from libc.

Frowzy answered 29/6, 2017 at 19:58 Comment(2)
Could you please share the code that you used to do this? Thanks.Assemblage
@TrevTheDev, see my answer for an example.Tapeworm
T
0

Here is another example using ffi-napi, which works on Node 20:

https://gist.github.com/oxc/b91f02b55f4973910e5274a26694238d

It was inspired by the existing answers to this question by user1629060 (using ffi) and oleksii-rudenko (removing the "exit on close" flag from the streams).

I only needed execv, but implementing any of the other variants should work equivalently.

Note that args is the full array and needs to include arg0 as first element. It does NOT copy path as first element like in other examples here.

import ref from "ref-napi";
import ffi from "ffi-napi";
import ref_array_di from "ref-array-di";

const ArrayType = ref_array_di(ref);

const StringArray = ArrayType("string");

// from fcntl.h
const F_GETFD = 1; /* get close_on_exec */
const F_SETFD = 2; /* set/clear close_on_exec */
const FD_CLOEXEC = 1; /* actually anything with low bit set goes */

export function execv(path: string, args: string[]): number | never {
  const current = ffi.Library(null, {
    execv: ["int", ["string", StringArray]],
    fcntl: ["int", ["int", "int", "int"]],
  });

  function dontCloseOnExit(fd: number) {
    let flags = current.fcntl(fd, F_GETFD, 0);
    if (flags < 0) return flags;
    flags &= ~FD_CLOEXEC; //clear FD_CLOEXEC bit
    return current.fcntl(fd, F_SETFD, flags);
  }

  const argsArray = new StringArray(args.length + 1);
  for (let i = 0; i < args.length; i++) {
    argsArray[i] = args[i];
  }
  argsArray[args.length] = null;

  dontCloseOnExit(process.stdin.fd);
  dontCloseOnExit(process.stdout.fd);
  dontCloseOnExit(process.stderr.fd);

  return current.execv(path, argsArray);
}
Tapeworm answered 7/1 at 18:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.