Skip to content

Commit

Permalink
3.1.0 Add functions to get/set the close-on-exec flag
Browse files Browse the repository at this point in the history
pid2 needs to set the close-on-exec flag on a certain file descriptor,
but node doesn't have an API to achieve this. The only other node module
that provides the `fcntl(2)` binding doesn't play well with esbuild, so
we need to recruit the help of our friend here.

This change adds two functions to get/set the close-on-exec flag on a
file descriptor.
  • Loading branch information
lhchavez committed Jul 9, 2024
1 parent 3195048 commit 7468b65
Show file tree
Hide file tree
Showing 7 changed files with 339 additions and 222 deletions.
10 changes: 10 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@ export interface Size {
cols: number
rows: number
}
/**
* Set the close-on-exec flag on a file descriptor. This is `fcntl(fd, F_SETFD, FD_CLOEXEC)` under
* the covers.
*/
export function setCloseOnExec(fd: number, closeOnExec: boolean): void
/**
* Get the close-on-exec flag on a file descriptor. This is `fcntl(fd, F_GETFD) & FD_CLOEXEC ==
*_CLOEXEC` under the covers.
*/
export function getCloseOnExec(fd: number): boolean
export class Pty {
/** The pid of the forked process. */
pid: number
Expand Down
4 changes: 3 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,8 @@ if (!nativeBinding) {
throw new Error(`Failed to load native binding`)
}

const { Pty } = nativeBinding
const { Pty, setCloseOnExec, getCloseOnExec } = nativeBinding

module.exports.Pty = Pty
module.exports.setCloseOnExec = setCloseOnExec
module.exports.getCloseOnExec = getCloseOnExec
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@replit/ruspty",
"version": "3.0.5",
"version": "3.1.0",
"main": "dist/wrapper.js",
"types": "dist/wrapper.d.ts",
"author": "Szymon Kaliski <[email protected]>",
Expand Down
55 changes: 54 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use napi::threadsafe_function::{ErrorStrategy, ThreadsafeFunction, ThreadsafeFun
use napi::Status::GenericFailure;
use napi::{self, Env};
use nix::errno::Errno;
use nix::fcntl::{fcntl, FcntlArg, FdFlag};
use nix::poll::{poll, PollFd, PollFlags, PollTimeout};
use nix::pty::{openpty, Winsize};
use nix::sys::termios::{self, SetArg};
Expand Down Expand Up @@ -200,7 +201,7 @@ impl Pty {
thread::spawn(move || {
let wait_result = child.wait();

// try to wait for the controller fd to be fully read
// try to wait for the controller fd to be fully read
poll_controller_fd_until_read(raw_controller_fd);

// we don't drop the controller fd immediately
Expand Down Expand Up @@ -302,3 +303,55 @@ impl Pty {
Ok(())
}
}

/// Set the close-on-exec flag on a file descriptor. This is `fcntl(fd, F_SETFD, FD_CLOEXEC)` under
/// the covers.
#[napi]
#[allow(dead_code)]
fn set_close_on_exec(fd: i32, close_on_exec: bool) -> Result<(), napi::Error> {
let mut flags = match fcntl(fd as RawFd, FcntlArg::F_GETFD) {
Ok(flags) => FdFlag::from_bits_truncate(flags),
Err(err) => {
return Err(napi::Error::new(
GenericFailure,
format!("fcntl F_GETFD: {}", err,),
));
}
};
if close_on_exec {
if flags.contains(FdFlag::FD_CLOEXEC) {
// It's already there!
return Ok(());
}
flags.insert(FdFlag::FD_CLOEXEC);
} else {
if !flags.contains(FdFlag::FD_CLOEXEC) {
// It's already removed!
return Ok(());
}
flags.remove(FdFlag::FD_CLOEXEC);
}

if let Err(err) = fcntl(fd as RawFd, FcntlArg::F_SETFD(flags)) {
return Err(napi::Error::new(
GenericFailure,
format!("fcntl F_SETFD: {}", err,),
));
};

Ok(())
}

/// Get the close-on-exec flag on a file descriptor. This is `fcntl(fd, F_GETFD) & FD_CLOEXEC ==
///_CLOEXEC` under the covers.
#[napi]
#[allow(dead_code)]
fn get_close_on_exec(fd: i32) -> Result<bool, napi::Error> {
match fcntl(fd as RawFd, FcntlArg::F_GETFD) {
Ok(flags) => Ok(FdFlag::from_bits_truncate(flags).contains(FdFlag::FD_CLOEXEC)),
Err(err) => Err(napi::Error::new(
GenericFailure,
format!("fcntl F_GETFD: {}", err,),
)),
}
}
Loading

0 comments on commit 7468b65

Please sign in to comment.