Skip to content

Commit bb8445d

Browse files
committed
Inotify
1 parent 9b65d88 commit bb8445d

File tree

6 files changed

+641
-11
lines changed

6 files changed

+641
-11
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ libc = "0.2.152"
1212
# Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix
1313
napi = { version = "2.12.2", default-features = false, features = ["napi4"] }
1414
napi-derive = "2.12.2"
15-
nix = { version = "0.29.0", features = ["fs", "term", "poll"] }
15+
nix = { version = "0.29.0", features = ["fs", "term", "poll", "inotify"] }
1616

1717
[build-dependencies]
1818
napi-build = "2.0.1"

index.d.ts

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,25 @@ export interface Size {
1919
rows: number
2020
}
2121
/** Resize the terminal. */
22-
export function ptyResize(fd: number, size: Size): void
22+
export declare function ptyResize(fd: number, size: Size): void
2323
/**
2424
* Set the close-on-exec flag on a file descriptor. This is `fcntl(fd, F_SETFD, FD_CLOEXEC)` under
2525
* the covers.
2626
*/
27-
export function setCloseOnExec(fd: number, closeOnExec: boolean): void
27+
export declare function setCloseOnExec(fd: number, closeOnExec: boolean): void
2828
/**
2929
* Get the close-on-exec flag on a file descriptor. This is `fcntl(fd, F_GETFD) & FD_CLOEXEC ==
3030
*_CLOEXEC` under the covers.
3131
*/
32-
export function getCloseOnExec(fd: number): boolean
32+
export declare function getCloseOnExec(fd: number): boolean
33+
export const IN_CLOSE_WRITE: number
34+
export const IN_MOVED_FROM: number
35+
export const IN_MOVED_TO: number
36+
export const IN_CREATE: number
37+
export const IN_DELETE: number
38+
export const IN_IGNORED: number
39+
export const IN_Q_OVERFLOW: number
40+
export const IN_UNMOUNT: number
3341
export class Pty {
3442
/** The pid of the forked process. */
3543
pid: number
@@ -41,3 +49,29 @@ export class Pty {
4149
*/
4250
takeFd(): c_int
4351
}
52+
/**
53+
* A way to access Linux' `inotify(7)` subsystem. For simplicity, this only allows subscribing for
54+
* events on directories (instead of files) and only for modify-close and rename events.
55+
*/
56+
export class Inotify {
57+
constructor()
58+
/**
59+
* Close the inotify file descriptor. Must be called at most once to avoid file descriptor
60+
* leaks.
61+
*/
62+
close(): void
63+
/**
64+
* Borrow the file descriptor. It is expected that Nod does not close the file descriptor and
65+
* instead the .close() method should be called to clean the file descriptor up. Read the file
66+
* descriptor on node according to `inotify(7)` to get events.
67+
*/
68+
fd(): c_int
69+
/**
70+
* Register one directory to be watched. Events for close-after-write, renames, and deletions
71+
* will be registered. Events for creation and modification will be ignored. Returns a watch
72+
* descriptor, which can be used in `remove_watch`.
73+
*/
74+
addCloseWrite(dir: string): number
75+
/** Stop watching the watch descriptor provided. */
76+
removeWatch(wd: number): void
77+
}

index.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,9 +310,18 @@ if (!nativeBinding) {
310310
throw new Error(`Failed to load native binding`)
311311
}
312312

313-
const { Pty, ptyResize, setCloseOnExec, getCloseOnExec } = nativeBinding
313+
const { Pty, ptyResize, setCloseOnExec, getCloseOnExec, Inotify, IN_CLOSE_WRITE, IN_MOVED_FROM, IN_MOVED_TO, IN_CREATE, IN_DELETE, IN_IGNORED, IN_Q_OVERFLOW, IN_UNMOUNT } = nativeBinding
314314

315315
module.exports.Pty = Pty
316316
module.exports.ptyResize = ptyResize
317317
module.exports.setCloseOnExec = setCloseOnExec
318318
module.exports.getCloseOnExec = getCloseOnExec
319+
module.exports.Inotify = Inotify
320+
module.exports.IN_CLOSE_WRITE = IN_CLOSE_WRITE
321+
module.exports.IN_MOVED_FROM = IN_MOVED_FROM
322+
module.exports.IN_MOVED_TO = IN_MOVED_TO
323+
module.exports.IN_CREATE = IN_CREATE
324+
module.exports.IN_DELETE = IN_DELETE
325+
module.exports.IN_IGNORED = IN_IGNORED
326+
module.exports.IN_Q_OVERFLOW = IN_Q_OVERFLOW
327+
module.exports.IN_UNMOUNT = IN_UNMOUNT

src/lib.rs

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,3 +371,257 @@ fn set_nonblocking(fd: i32) -> Result<(), napi::Error> {
371371
}
372372
Ok(())
373373
}
374+
375+
#[cfg(target_os = "linux")]
376+
use nix::sys::inotify::{AddWatchFlags, InitFlags};
377+
#[cfg(target_os = "linux")]
378+
use std::ffi::CString;
379+
380+
/// A way to access Linux' `inotify(7)` subsystem. For simplicity, this only allows subscribing for
381+
/// events on directories (instead of files) and only for modify-close and rename events.
382+
#[napi]
383+
#[allow(dead_code)]
384+
struct Inotify {
385+
fd: Option<OwnedFd>,
386+
}
387+
388+
#[cfg(target_os = "linux")]
389+
#[napi]
390+
#[allow(dead_code)]
391+
impl Inotify {
392+
#[napi(constructor)]
393+
pub fn new() -> Result<Self, napi::Error> {
394+
let fd = match Errno::result(unsafe {
395+
libc::inotify_init1((InitFlags::IN_CLOEXEC | InitFlags::IN_NONBLOCK).bits())
396+
}) {
397+
Ok(fd) => unsafe { OwnedFd::from_raw_fd(fd) },
398+
Err(err) => {
399+
return Err(napi::Error::new(
400+
GenericFailure,
401+
format!("inotify_init: {}", err),
402+
));
403+
}
404+
};
405+
Ok(Inotify { fd: Some(fd) })
406+
}
407+
408+
/// Close the inotify file descriptor. Must be called at most once to avoid file descriptor
409+
/// leaks.
410+
#[napi]
411+
#[allow(dead_code)]
412+
pub fn close(&mut self) -> Result<(), napi::Error> {
413+
let inotify = self.fd.take();
414+
if inotify.is_none() {
415+
return Err(napi::Error::new(
416+
GenericFailure,
417+
"inotify fd has already been closed",
418+
));
419+
}
420+
421+
Ok(())
422+
}
423+
424+
/// Borrow the file descriptor. It is expected that Nod does not close the file descriptor and
425+
/// instead the .close() method should be called to clean the file descriptor up. Read the file
426+
/// descriptor on node according to `inotify(7)` to get events.
427+
#[napi]
428+
#[allow(dead_code)]
429+
pub fn fd(&self) -> Result<c_int, napi::Error> {
430+
if let Some(fd) = &self.fd {
431+
Ok(fd.as_raw_fd())
432+
} else {
433+
Err(napi::Error::new(
434+
GenericFailure,
435+
"inotify fd has already been closed",
436+
))
437+
}
438+
}
439+
440+
/// Register one directory to be watched. Events for close-after-write, renames, and deletions
441+
/// will be registered. Events for creation and modification will be ignored. Returns a watch
442+
/// descriptor, which can be used in `remove_watch`.
443+
#[napi]
444+
#[allow(dead_code)]
445+
pub fn add_close_write(&self, dir: String) -> Result<i32, napi::Error> {
446+
let cstring_dir = match CString::new(dir.as_str()) {
447+
Ok(cstring_dir) => cstring_dir,
448+
Err(err) => {
449+
return Err(napi::Error::new(
450+
GenericFailure,
451+
format!("CString::new: {}", err),
452+
));
453+
}
454+
};
455+
if let Some(fd) = &self.fd {
456+
match Errno::result(unsafe {
457+
libc::inotify_add_watch(
458+
fd.as_raw_fd(),
459+
cstring_dir.as_c_str().as_ptr(),
460+
(AddWatchFlags::IN_CLOSE_WRITE | AddWatchFlags::IN_MOVED_TO | AddWatchFlags::IN_DELETE)
461+
.bits(),
462+
)
463+
}) {
464+
Ok(wd) => Ok(wd),
465+
Err(err) => Err(napi::Error::new(
466+
GenericFailure,
467+
format!("inotify_add_watch: {}", err),
468+
)),
469+
}
470+
} else {
471+
Err(napi::Error::new(
472+
GenericFailure,
473+
"inotify fd has already been closed",
474+
))
475+
}
476+
}
477+
478+
/// Stop watching the watch descriptor provided.
479+
#[napi]
480+
#[allow(dead_code)]
481+
pub fn remove_watch(&self, wd: i32) -> Result<(), napi::Error> {
482+
if let Some(fd) = &self.fd {
483+
if let Err(err) = Errno::result(unsafe { libc::inotify_rm_watch(fd.as_raw_fd(), wd) }) {
484+
Err(napi::Error::new(
485+
GenericFailure,
486+
format!("inotify_remove_watch: {}", err),
487+
))
488+
} else {
489+
Ok(())
490+
}
491+
} else {
492+
Err(napi::Error::new(
493+
GenericFailure,
494+
"inotify fd has already been closed",
495+
))
496+
}
497+
}
498+
}
499+
500+
#[cfg(target_os = "linux")]
501+
#[napi]
502+
#[allow(dead_code)]
503+
pub const IN_CLOSE_WRITE: u32 = AddWatchFlags::IN_CLOSE_WRITE.bits();
504+
505+
#[cfg(target_os = "linux")]
506+
#[napi]
507+
#[allow(dead_code)]
508+
pub const IN_MOVED_FROM: u32 = AddWatchFlags::IN_MOVED_FROM.bits();
509+
510+
#[cfg(target_os = "linux")]
511+
#[napi]
512+
#[allow(dead_code)]
513+
pub const IN_MOVED_TO: u32 = AddWatchFlags::IN_MOVED_TO.bits();
514+
515+
#[cfg(target_os = "linux")]
516+
#[napi]
517+
#[allow(dead_code)]
518+
pub const IN_CREATE: u32 = AddWatchFlags::IN_CREATE.bits();
519+
520+
#[cfg(target_os = "linux")]
521+
#[napi]
522+
#[allow(dead_code)]
523+
pub const IN_DELETE: u32 = AddWatchFlags::IN_DELETE.bits();
524+
525+
#[cfg(target_os = "linux")]
526+
#[napi]
527+
#[allow(dead_code)]
528+
pub const IN_IGNORED: u32 = AddWatchFlags::IN_IGNORED.bits();
529+
530+
#[cfg(target_os = "linux")]
531+
#[napi]
532+
#[allow(dead_code)]
533+
pub const IN_Q_OVERFLOW: u32 = AddWatchFlags::IN_Q_OVERFLOW.bits();
534+
535+
#[cfg(target_os = "linux")]
536+
#[napi]
537+
#[allow(dead_code)]
538+
pub const IN_UNMOUNT: u32 = AddWatchFlags::IN_UNMOUNT.bits();
539+
540+
#[cfg(not(target_os = "linux"))]
541+
#[napi]
542+
impl Inotify {
543+
#[napi(constructor)]
544+
#[allow(dead_code)]
545+
pub fn new() -> Result<Self, napi::Error> {
546+
Err(napi::Error::new(
547+
GenericFailure,
548+
format!("inotify not supported in non-Linux"),
549+
))
550+
}
551+
552+
#[napi]
553+
#[allow(dead_code)]
554+
pub fn close(&mut self) -> Result<(), napi::Error> {
555+
Err(napi::Error::new(
556+
GenericFailure,
557+
format!("inotify not supported in non-Linux"),
558+
))
559+
}
560+
561+
#[napi]
562+
#[allow(dead_code)]
563+
pub fn fd(&self) -> Result<c_int, napi::Error> {
564+
Err(napi::Error::new(
565+
GenericFailure,
566+
format!("inotify not supported in non-Linux"),
567+
))
568+
}
569+
570+
#[napi]
571+
#[allow(dead_code)]
572+
pub fn add_close_write(&self, dir: String) -> Result<i32, napi::Error> {
573+
Err(napi::Error::new(
574+
GenericFailure,
575+
format!("inotify not supported in non-Linux"),
576+
))
577+
}
578+
579+
#[napi]
580+
#[allow(dead_code)]
581+
pub fn remove_watch(&self, wd: i32) -> Result<(), napi::Error> {
582+
Err(napi::Error::new(
583+
GenericFailure,
584+
format!("inotify not supported in non-Linux"),
585+
))
586+
}
587+
}
588+
589+
#[cfg(not(target_os = "linux"))]
590+
#[napi]
591+
#[allow(dead_code)]
592+
pub const IN_CLOSE_WRITE: u32 = 0;
593+
594+
#[cfg(not(target_os = "linux"))]
595+
#[napi]
596+
#[allow(dead_code)]
597+
pub const IN_MOVED_FROM: u32 = 0;
598+
599+
#[cfg(not(target_os = "linux"))]
600+
#[napi]
601+
#[allow(dead_code)]
602+
pub const IN_MOVED_TO: u32 = 0;
603+
604+
#[cfg(not(target_os = "linux"))]
605+
#[napi]
606+
#[allow(dead_code)]
607+
pub const IN_CREATE: u32 = 0;
608+
609+
#[cfg(not(target_os = "linux"))]
610+
#[napi]
611+
#[allow(dead_code)]
612+
pub const IN_DELETE: u32 = 0;
613+
614+
#[cfg(not(target_os = "linux"))]
615+
#[napi]
616+
#[allow(dead_code)]
617+
pub const IN_IGNORED: u32 = 0;
618+
619+
#[cfg(not(target_os = "linux"))]
620+
#[napi]
621+
#[allow(dead_code)]
622+
pub const IN_Q_OVERFLOW: u32 = 0;
623+
624+
#[cfg(not(target_os = "linux"))]
625+
#[napi]
626+
#[allow(dead_code)]
627+
pub const IN_UNMOUNT: u32 = 0;

0 commit comments

Comments
 (0)