inotify-0.9.6/.cargo_vcs_info.json0000644000000001120000000000100125230ustar { "git": { "sha1": "4b774c9be96f5fe9a08ad8c9be5fa73810d8eed2" } } inotify-0.9.6/.github/workflows/rust.yml000064400000000000000000000011520072674642500164650ustar 00000000000000name: Rust on: [push, pull_request] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - name: Install rust uses: actions-rs/toolchain@v1 with: toolchain: 1.47.0 override: true - name: Check rust and cargo version run: rustc -V && cargo -V - name: Build run: cargo build --verbose - name: Run tests run: cargo test --verbose - name: Build (no default features) run: cargo build --verbose --no-default-features - name: Run tests (no default features) run: cargo test --verbose --no-default-features inotify-0.9.6/.gitignore000064400000000000000000000000600072674642500133350ustar 00000000000000Cargo.lock /**/target *.sublime-workspace *.swp inotify-0.9.6/CHANGELOG.md000064400000000000000000000062010072674642500131610ustar 00000000000000### v0.9.6 (2021-10-07) - Fix build status badge in README ([#185]) - Add `get_buffer_size`/`get_absolute_path_buffer_size` ([#187]) [#185]: https://github.com/hannobraun/inotify-rs/pull/185 [#187]: https://github.com/hannobraun/inotify-rs/pull/187 ### v0.9.5 (2021-10-07) - Implement `Ord`/`PartialOrd` for `WatchDescriptor` ([#183]) [#183]: https://github.com/hannobraun/inotify-rs/pull/183 ### v0.9.4 (2021-09-22) - Make `Event::into_owned` always available ([#179]) - Implement missing `Debug` implementations ([#180]) [#179]: https://github.com/hannobraun/inotify-rs/pull/179 [#180]: https://github.com/hannobraun/inotify-rs/pull/180 ### v0.9.3 (2021-05-12) - Improve documentation ([#167], [#169]) - Add missing check for invalid file descriptor ([#168]) - Fix unsound use of buffers due to misalignment ([#171]) - Add missing error checks ([#173]) [#167]: https://github.com/hannobraun/inotify-rs/pull/167 [#168]: https://github.com/hannobraun/inotify-rs/pull/168 [#169]: https://github.com/hannobraun/inotify-rs/pull/169 [#171]: https://github.com/hannobraun/inotify-rs/pull/171 [#173]: https://github.com/hannobraun/inotify-rs/pull/173 ### v0.9.2 (2020-12-30) - Upgrade to Tokio 1.0 ([#165]) [#165]: https://github.com/hannobraun/inotify/pull/165 ### v0.9.1 (2020-11-09) - Fix take wake-up ([#161]) [#161]: https://github.com/hannobraun/inotify/pull/161 ### v0.9.0 (2020-11-06) - Update minimum supported Rust version to version 1.47 ([#154]) - Fix documentation: `Inotify::read_events` doesn't handle all events ([#157]) - Update to tokio 0.3 ([#158]) [#154]: https://github.com/hannobraun/inotify/pull/154 [#157]: https://github.com/hannobraun/inotify/pull/157 [#158]: https://github.com/hannobraun/inotify/pull/158 ### v0.8.3 (2020-06-05) - Avoid using `inotify_init1` ([#146]) [#146]: https://github.com/hannobraun/inotify/pull/146 ### v0.8.2 (2020-01-25) - Ensure file descriptor is closed on drop ([#140]) [#140]: https://github.com/inotify-rs/inotify/pull/140 ### v0.8.1 (2020-01-23) No changes, due to a mistake made while releasing this version. ### v0.8.0 (2019-12-04) - Update to tokio 0.2 and futures 0.3 ([#134]) [#134]: https://github.com/inotify-rs/inotify/pull/134 ### v0.7.1 (2020-06-05) - backport: Avoid using `inotify_init1` ([#146]) [#146]: https://github.com/hannobraun/inotify/pull/146 ### v0.7.0 (2019-02-09) #### Features * Make stream API more flexible in regards to buffers ([ea3e7a394bf34a6ccce4f2136c0991fe7e8f1f42](ea3e7a394bf34a6ccce4f2136c0991fe7e8f1f42)) (breaking change) ### v0.6.1 (2018-08-28) #### Bug Fixes * Don't return spurious filenames ([2f37560f](2f37560f)) ## v0.6.0 (2018-08-16) #### Features * Handle closing of inotify instance better ([824160fe](824160fe)) * Implement `EventStream` using `mio` ([ba4cb8c7](ba4cb8c7)) ### v0.5.1 (2018-02-27) #### Features * Add future-based async API ([569e65a7](569e65a7), closes [#49](49)) inotify-0.9.6/CONTRIBUTING.md000064400000000000000000000030100072674642500135740ustar 00000000000000# Contributing to inotify-rs Thank you for considering to work on inotify-rs. We're always happy to see outside contributions, small or large. You probably found this document in the repository of either the [inotify] or [inotify-sys] crate. Both are part of the same project, so this guide is valid for both (in fact, the documents in either repository should be identical). ## Opening issues If you found a problem with inotify-rs, please open an issue to let us know. If you're not sure whether you found a problem or not, just open an issue anyway. We'd rather close a few invalid issues than miss real problems. Issues are tracked on GitHub, in the repository for the respective crate: - [Open an inotify issue](https://github.com/inotify-rs/inotify/issues/new) - [Open an inotify-sys issue](https://github.com/inotify-rs/inotify-sys/issues/new) If you're unsure where to open your issue, just open it in the [inotify] repository. ## Contributing changes If you want to make a change to the inotify-rs code, please open a pull request on the respective repository. The best way to open a pull request is usually to just push a branch to your fork, and click the button that should appear near the top of your fork's GitHub page. If you're having any problems with completing your change, feel free to open a pull request anyway and ask any questions there. We're happy to help with getting changes across the finish line. [inotify]: https://github.com/hannobraun/inotify [inotify-sys]: https://github.com/hannobraun/inotify-sys inotify-0.9.6/Cargo.lock0000644000000216360000000000100105140ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "autocfg" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "bitflags" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "futures-core" version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0402f765d8a89a26043b889b26ce3c4679d268fa6bb22cd7c6aad98340e179d1" [[package]] name = "futures-macro" version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4c40298486cdf52cc00cd6d6987892ba502c7656a16a4192a9992b1ccedd121" dependencies = [ "autocfg", "proc-macro-hack", "proc-macro2", "quote", "syn", ] [[package]] name = "futures-task" version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a16bef9fc1a4dddb5bee51c989e3fbba26569cbb0e31f5b303c184e3dd33dae" [[package]] name = "futures-util" version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "feb5c238d27e2bf94ffdfd27b2c29e3df4a68c4193bb6427384259e2bf191967" dependencies = [ "autocfg", "futures-core", "futures-macro", "futures-task", "pin-project-lite", "pin-utils", "proc-macro-hack", "proc-macro-nested", "slab", ] [[package]] name = "getrandom" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "hermit-abi" version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" dependencies = [ "libc", ] [[package]] name = "inotify" version = "0.9.6" dependencies = [ "bitflags", "futures-core", "futures-util", "inotify-sys", "libc", "tempfile", "tokio", ] [[package]] name = "inotify-sys" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" dependencies = [ "libc", ] [[package]] name = "libc" version = "0.2.94" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e" [[package]] name = "log" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ "cfg-if", ] [[package]] name = "mio" version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf80d3e903b34e0bd7282b218398aec54e082c840d9baf8339e0080a0c542956" dependencies = [ "libc", "log", "miow", "ntapi", "winapi", ] [[package]] name = "miow" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" dependencies = [ "winapi", ] [[package]] name = "ntapi" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" dependencies = [ "winapi", ] [[package]] name = "num_cpus" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" dependencies = [ "hermit-abi", "libc", ] [[package]] name = "pin-project-lite" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "ppv-lite86" version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" [[package]] name = "proc-macro-hack" version = "0.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro-nested" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" [[package]] name = "proc-macro2" version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" dependencies = [ "unicode-xid", ] [[package]] name = "quote" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" dependencies = [ "proc-macro2", ] [[package]] name = "rand" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" dependencies = [ "libc", "rand_chacha", "rand_core", "rand_hc", ] [[package]] name = "rand_chacha" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" dependencies = [ "getrandom", ] [[package]] name = "rand_hc" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" dependencies = [ "rand_core", ] [[package]] name = "redox_syscall" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "742739e41cd49414de871ea5e549afb7e2a3ac77b589bcbebe8c82fab37147fc" dependencies = [ "bitflags", ] [[package]] name = "remove_dir_all" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" dependencies = [ "winapi", ] [[package]] name = "slab" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527" [[package]] name = "syn" version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82" dependencies = [ "proc-macro2", "quote", "unicode-xid", ] [[package]] name = "tempfile" version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" dependencies = [ "cfg-if", "libc", "rand", "redox_syscall", "remove_dir_all", "winapi", ] [[package]] name = "tokio" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83f0c8e7c0addab50b663055baf787d0af7f413a46e6e7fb9559a4e4db7137a5" dependencies = [ "autocfg", "libc", "mio", "num_cpus", "pin-project-lite", "tokio-macros", ] [[package]] name = "tokio-macros" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caf7b11a536f46a809a8a9f0bb4237020f70ecbf115b842360afb127ea2fda57" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "unicode-xid" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" [[package]] name = "wasi" version = "0.10.2+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" inotify-0.9.6/Cargo.toml0000644000000032570000000000100105360ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "inotify" version = "0.9.6" authors = ["Hanno Braun ", "Félix Saparelli ", "Cristian Kubis ", "Frank Denis "] exclude = ["/.travis.yml", "/inotify-rs.sublime-project"] description = "Idiomatic wrapper for inotify" documentation = "https://docs.rs/inotify" readme = "README.md" keywords = ["inotify", "linux"] categories = ["api-bindings", "filesystem"] license = "ISC" repository = "https://github.com/hannobraun/inotify" [[example]] name = "stream" required-features = ["stream"] [[example]] name = "watch" [dependencies.bitflags] version = "1" [dependencies.futures-core] version = "0.3.1" optional = true [dependencies.inotify-sys] version = "0.1.3" [dependencies.libc] version = "0.2" [dependencies.tokio] version = "1.0.1" features = ["net"] optional = true [dev-dependencies.futures-util] version = "0.3.1" [dev-dependencies.tempfile] version = "3.1.0" [dev-dependencies.tokio] version = "1.0.1" features = ["macros", "rt-multi-thread"] [features] default = ["stream"] stream = ["futures-core", "tokio"] [badges.maintenance] status = "actively-developed" [badges.travis-ci] repository = "inotify-rs/inotify" inotify-0.9.6/Cargo.toml.orig000064400000000000000000000022760072674642500142470ustar 00000000000000[package] name = "inotify" version = "0.9.6" authors = [ "Hanno Braun ", "Félix Saparelli ", "Cristian Kubis ", "Frank Denis " ] edition = "2018" description = "Idiomatic wrapper for inotify" documentation = "https://docs.rs/inotify" repository = "https://github.com/hannobraun/inotify" license = "ISC" readme = "README.md" keywords = ["inotify", "linux"] categories = ["api-bindings", "filesystem"] exclude = ["/.travis.yml", "/inotify-rs.sublime-project"] [badges] maintenance = { status = "actively-developed" } travis-ci = { repository = "inotify-rs/inotify" } [features] default = ["stream"] stream = ["futures-core", "tokio"] [dependencies] bitflags = "1" futures-core = { version = "0.3.1", optional = true } inotify-sys = "0.1.3" libc = "0.2" tokio = { version = "1.0.1", optional = true, features = ["net"] } [dev-dependencies] tempfile = "3.1.0" futures-util = "0.3.1" tokio = { version = "1.0.1", features = ["macros", "rt-multi-thread"] } [[example]] name = "stream" required-features = ["stream"] [[example]] name = "watch" inotify-0.9.6/LICENSE000064400000000000000000000013450072674642500123610ustar 00000000000000Copyright (c) Hanno Braun and contributors Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. inotify-0.9.6/README.md000064400000000000000000000103020072674642500126240ustar 00000000000000# inotify-rs [![crates.io](https://img.shields.io/crates/v/inotify.svg)](https://crates.io/crates/inotify) [![Documentation](https://docs.rs/inotify/badge.svg)](https://docs.rs/inotify) [![Rust](https://github.com/hannobraun/inotify-rs/actions/workflows/rust.yml/badge.svg)](https://github.com/hannobraun/inotify-rs/actions/workflows/rust.yml) Idiomatic [inotify] wrapper for the [Rust programming language]. ```Rust extern crate inotify; use std::env; use inotify::{ EventMask, WatchMask, Inotify, }; fn main() { let mut inotify = Inotify::init() .expect("Failed to initialize inotify"); let current_dir = env::current_dir() .expect("Failed to determine current directory"); inotify .add_watch( current_dir, WatchMask::MODIFY | WatchMask::CREATE | WatchMask::DELETE, ) .expect("Failed to add inotify watch"); println!("Watching current directory for activity..."); let mut buffer = [0u8; 4096]; loop { let events = inotify .read_events_blocking(&mut buffer) .expect("Failed to read inotify events"); for event in events { if event.mask.contains(EventMask::CREATE) { if event.mask.contains(EventMask::ISDIR) { println!("Directory created: {:?}", event.name); } else { println!("File created: {:?}", event.name); } } else if event.mask.contains(EventMask::DELETE) { if event.mask.contains(EventMask::ISDIR) { println!("Directory deleted: {:?}", event.name); } else { println!("File deleted: {:?}", event.name); } } else if event.mask.contains(EventMask::MODIFY) { if event.mask.contains(EventMask::ISDIR) { println!("Directory modified: {:?}", event.name); } else { println!("File modified: {:?}", event.name); } } } } } ``` ## Usage Include it in your `Cargo.toml`: ```toml [dependencies] inotify = "0.9" ``` Please refer to the [documentation] and the example above, for information on how to use it in your code. Please note that inotify-rs is a relatively low-level wrapper around the original inotify API. And, of course, it is Linux-specific, just like inotify itself. If you are looking for a higher-level and platform-independent file system notification library, please consider [notify]. If you need to access inotify in a way that this wrapper doesn't support, consider using [inotify-sys] instead. ## Documentation The most important piece of documentation for inotify-rs is the **[API reference]**, as it contains a thorough description of the complete API, as well as examples. Additional examples can be found in the **[examples directory]**. Please also make sure to read the **[inotify man page]**. Inotify use can be hard to get right, and this low-level wrapper won't protect you from all mistakes. ## License Copyright (c) Hanno Braun and contributors Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. [inotify]: http://en.wikipedia.org/wiki/Inotify [Rust programming language]: http://rust-lang.org/ [documentation]: https://docs.rs/inotify [notify]: https://crates.io/crates/notify [inotify-sys]: https://crates.io/crates/inotify-sys [API reference]: https://docs.rs/inotify [examples directory]: https://github.com/inotify-rs/inotify/tree/master/examples [inotify man page]: http://man7.org/linux/man-pages/man7/inotify.7.html inotify-0.9.6/examples/stream.rs000064400000000000000000000015060072674642500150320ustar 00000000000000use std::{ fs::File, io, thread, time::Duration, }; use futures_util::StreamExt; use inotify::{ Inotify, WatchMask, }; use tempfile::TempDir; #[tokio::main] async fn main() -> Result<(), io::Error> { let mut inotify = Inotify::init() .expect("Failed to initialize inotify"); let dir = TempDir::new()?; inotify.add_watch(dir.path(), WatchMask::CREATE | WatchMask::MODIFY)?; thread::spawn::<_, Result<(), io::Error>>(move || { loop { File::create(dir.path().join("file"))?; thread::sleep(Duration::from_millis(500)); } }); let mut buffer = [0; 1024]; let mut stream = inotify.event_stream(&mut buffer)?; while let Some(event_or_error) = stream.next().await { println!("event: {:?}", event_or_error?); } Ok(()) } inotify-0.9.6/examples/watch.rs000064400000000000000000000031650072674642500146500ustar 00000000000000use std::env; use inotify::{ EventMask, Inotify, WatchMask, }; fn main() { let mut inotify = Inotify::init() .expect("Failed to initialize inotify"); let current_dir = env::current_dir() .expect("Failed to determine current directory"); inotify .add_watch( current_dir, WatchMask::MODIFY | WatchMask::CREATE | WatchMask::DELETE, ) .expect("Failed to add inotify watch"); println!("Watching current directory for activity..."); let mut buffer = [0u8; 4096]; loop { let events = inotify .read_events_blocking(&mut buffer) .expect("Failed to read inotify events"); for event in events { if event.mask.contains(EventMask::CREATE) { if event.mask.contains(EventMask::ISDIR) { println!("Directory created: {:?}", event.name); } else { println!("File created: {:?}", event.name); } } else if event.mask.contains(EventMask::DELETE) { if event.mask.contains(EventMask::ISDIR) { println!("Directory deleted: {:?}", event.name); } else { println!("File deleted: {:?}", event.name); } } else if event.mask.contains(EventMask::MODIFY) { if event.mask.contains(EventMask::ISDIR) { println!("Directory modified: {:?}", event.name); } else { println!("File modified: {:?}", event.name); } } } } } inotify-0.9.6/src/events.rs000064400000000000000000000353150072674642500140210ustar 00000000000000use std::{ ffi::{ OsStr, OsString, }, mem, os::unix::ffi::OsStrExt, sync::Weak, }; use inotify_sys as ffi; use crate::fd_guard::FdGuard; use crate::watches::WatchDescriptor; use crate::util::align_buffer; /// Iterator over inotify events /// /// Allows for iteration over the events returned by /// [`Inotify::read_events_blocking`] or [`Inotify::read_events`]. /// /// [`Inotify::read_events_blocking`]: struct.Inotify.html#method.read_events_blocking /// [`Inotify::read_events`]: struct.Inotify.html#method.read_events #[derive(Debug)] pub struct Events<'a> { fd : Weak, buffer : &'a [u8], num_bytes: usize, pos : usize, } impl<'a> Events<'a> { pub(crate) fn new(fd: Weak, buffer: &'a [u8], num_bytes: usize) -> Self { Events { fd : fd, buffer : buffer, num_bytes: num_bytes, pos : 0, } } } impl<'a> Iterator for Events<'a> { type Item = Event<&'a OsStr>; fn next(&mut self) -> Option { if self.pos < self.num_bytes { let (step, event) = Event::from_buffer(self.fd.clone(), &self.buffer[self.pos..]); self.pos += step; Some(event) } else { None } } } /// An inotify event /// /// A file system event that describes a change that the user previously /// registered interest in. To watch for events, call [`Inotify::add_watch`]. To /// retrieve events, call [`Inotify::read_events_blocking`] or /// [`Inotify::read_events`]. /// /// [`Inotify::add_watch`]: struct.Inotify.html#method.add_watch /// [`Inotify::read_events_blocking`]: struct.Inotify.html#method.read_events_blocking /// [`Inotify::read_events`]: struct.Inotify.html#method.read_events #[derive(Clone, Debug)] pub struct Event { /// Identifies the watch this event originates from /// /// This [`WatchDescriptor`] is equal to the one that [`Inotify::add_watch`] /// returned when interest for this event was registered. The /// [`WatchDescriptor`] can be used to remove the watch using /// [`Inotify::rm_watch`], thereby preventing future events of this type /// from being created. /// /// [`WatchDescriptor`]: struct.WatchDescriptor.html /// [`Inotify::add_watch`]: struct.Inotify.html#method.add_watch /// [`Inotify::rm_watch`]: struct.Inotify.html#method.rm_watch pub wd: WatchDescriptor, /// Indicates what kind of event this is pub mask: EventMask, /// Connects related events to each other /// /// When a file is renamed, this results two events: [`MOVED_FROM`] and /// [`MOVED_TO`]. The `cookie` field will be the same for both of them, /// thereby making is possible to connect the event pair. /// /// [`MOVED_FROM`]: event_mask/constant.MOVED_FROM.html /// [`MOVED_TO`]: event_mask/constant.MOVED_TO.html pub cookie: u32, /// The name of the file the event originates from /// /// This field is set only if the subject of the event is a file or directory in a /// watched directory. If the event concerns a file or directory that is /// watched directly, `name` will be `None`. pub name: Option, } impl<'a> Event<&'a OsStr> { fn new(fd: Weak, event: &ffi::inotify_event, name: &'a OsStr) -> Self { let mask = EventMask::from_bits(event.mask) .expect("Failed to convert event mask. This indicates a bug."); let wd = crate::WatchDescriptor { id: event.wd, fd, }; let name = if name == "" { None } else { Some(name) }; Event { wd, mask, cookie: event.cookie, name, } } /// Create an `Event` from a buffer /// /// Assumes that a full `inotify_event` plus its name is located at the /// beginning of `buffer`. /// /// Returns the number of bytes used from the buffer, and the event. /// /// # Panics /// /// Panics if the buffer does not contain a full event, including its name. pub(crate) fn from_buffer( fd : Weak, buffer: &'a [u8], ) -> (usize, Self) { let event_size = mem::size_of::(); let event_align = mem::align_of::(); // Make sure that the buffer can satisfy the alignment requirements for `inotify_event` assert!(buffer.len() >= event_align); // Discard the unaligned portion, if any, of the supplied buffer let buffer = align_buffer(buffer); // Make sure that the aligned buffer is big enough to contain an event, without // the name. Otherwise we can't safely convert it to an `inotify_event`. assert!(buffer.len() >= event_size); let event = buffer.as_ptr() as *const ffi::inotify_event; // We have a pointer to an `inotify_event`, pointing to the beginning of // `buffer`. Since we know, as per the assertion above, that there are // enough bytes in the buffer for at least one event, we can safely // convert that pointer into a reference. let event = unsafe { &*event }; // The name's length is given by `event.len`. There should always be // enough bytes left in the buffer to fit the name. Let's make sure that // is the case. let bytes_left_in_buffer = buffer.len() - event_size; assert!(bytes_left_in_buffer >= event.len as usize); // Directly after the event struct should be a name, if there's one // associated with the event. Let's make a new slice that starts with // that name. If there's no name, this slice might have a length of `0`. let bytes_consumed = event_size + event.len as usize; let name = &buffer[event_size..bytes_consumed]; // Remove trailing '\0' bytes // // The events in the buffer are aligned, and `name` is filled up // with '\0' up to the alignment boundary. Here we remove those // additional bytes. // // The `unwrap` here is safe, because `splitn` always returns at // least one result, even if the original slice contains no '\0'. let name = name .splitn(2, |b| b == &0u8) .next() .unwrap(); let event = Event::new( fd, event, OsStr::from_bytes(name), ); (bytes_consumed, event) } /// Returns an owned copy of the event. #[must_use = "cloning is often expensive and is not expected to have side effects"] pub fn into_owned(&self) -> EventOwned { Event { wd: self.wd.clone(), mask: self.mask, cookie: self.cookie, name: self.name.map(OsStr::to_os_string), } } } /// An owned version of `Event` pub type EventOwned = Event; bitflags! { /// Indicates the type of an event /// /// This struct can be retrieved from an [`Event`] via its `mask` field. /// You can determine the [`Event`]'s type by comparing the `EventMask` to /// its associated constants. /// /// Please refer to the documentation of [`Event`] for a usage example. /// /// [`Event`]: struct.Event.html pub struct EventMask: u32 { /// File was accessed /// /// When watching a directory, this event is only triggered for objects /// inside the directory, not the directory itself. /// /// See [`inotify_sys::IN_ACCESS`]. /// /// [`inotify_sys::IN_ACCESS`]: ../inotify_sys/constant.IN_ACCESS.html const ACCESS = ffi::IN_ACCESS; /// Metadata (permissions, timestamps, ...) changed /// /// When watching a directory, this event can be triggered for the /// directory itself, as well as objects inside the directory. /// /// See [`inotify_sys::IN_ATTRIB`]. /// /// [`inotify_sys::IN_ATTRIB`]: ../inotify_sys/constant.IN_ATTRIB.html const ATTRIB = ffi::IN_ATTRIB; /// File opened for writing was closed /// /// When watching a directory, this event is only triggered for objects /// inside the directory, not the directory itself. /// /// See [`inotify_sys::IN_CLOSE_WRITE`]. /// /// [`inotify_sys::IN_CLOSE_WRITE`]: ../inotify_sys/constant.IN_CLOSE_WRITE.html const CLOSE_WRITE = ffi::IN_CLOSE_WRITE; /// File or directory not opened for writing was closed /// /// When watching a directory, this event can be triggered for the /// directory itself, as well as objects inside the directory. /// /// See [`inotify_sys::IN_CLOSE_NOWRITE`]. /// /// [`inotify_sys::IN_CLOSE_NOWRITE`]: ../inotify_sys/constant.IN_CLOSE_NOWRITE.html const CLOSE_NOWRITE = ffi::IN_CLOSE_NOWRITE; /// File/directory created in watched directory /// /// When watching a directory, this event is only triggered for objects /// inside the directory, not the directory itself. /// /// See [`inotify_sys::IN_CREATE`]. /// /// [`inotify_sys::IN_CREATE`]: ../inotify_sys/constant.IN_CREATE.html const CREATE = ffi::IN_CREATE; /// File/directory deleted from watched directory /// /// When watching a directory, this event is only triggered for objects /// inside the directory, not the directory itself. /// /// See [`inotify_sys::IN_DELETE`]. /// /// [`inotify_sys::IN_DELETE`]: ../inotify_sys/constant.IN_DELETE.html const DELETE = ffi::IN_DELETE; /// Watched file/directory was deleted /// /// See [`inotify_sys::IN_DELETE_SELF`]. /// /// [`inotify_sys::IN_DELETE_SELF`]: ../inotify_sys/constant.IN_DELETE_SELF.html const DELETE_SELF = ffi::IN_DELETE_SELF; /// File was modified /// /// When watching a directory, this event is only triggered for objects /// inside the directory, not the directory itself. /// /// See [`inotify_sys::IN_MODIFY`]. /// /// [`inotify_sys::IN_MODIFY`]: ../inotify_sys/constant.IN_MODIFY.html const MODIFY = ffi::IN_MODIFY; /// Watched file/directory was moved /// /// See [`inotify_sys::IN_MOVE_SELF`]. /// /// [`inotify_sys::IN_MOVE_SELF`]: ../inotify_sys/constant.IN_MOVE_SELF.html const MOVE_SELF = ffi::IN_MOVE_SELF; /// File was renamed/moved; watched directory contained old name /// /// When watching a directory, this event is only triggered for objects /// inside the directory, not the directory itself. /// /// See [`inotify_sys::IN_MOVED_FROM`]. /// /// [`inotify_sys::IN_MOVED_FROM`]: ../inotify_sys/constant.IN_MOVED_FROM.html const MOVED_FROM = ffi::IN_MOVED_FROM; /// File was renamed/moved; watched directory contains new name /// /// When watching a directory, this event is only triggered for objects /// inside the directory, not the directory itself. /// /// See [`inotify_sys::IN_MOVED_TO`]. /// /// [`inotify_sys::IN_MOVED_TO`]: ../inotify_sys/constant.IN_MOVED_TO.html const MOVED_TO = ffi::IN_MOVED_TO; /// File or directory was opened /// /// When watching a directory, this event can be triggered for the /// directory itself, as well as objects inside the directory. /// /// See [`inotify_sys::IN_OPEN`]. /// /// [`inotify_sys::IN_OPEN`]: ../inotify_sys/constant.IN_OPEN.html const OPEN = ffi::IN_OPEN; /// Watch was removed /// /// This event will be generated, if the watch was removed explicitly /// (via [`Inotify::rm_watch`]), or automatically (because the file was /// deleted or the file system was unmounted). /// /// See [`inotify_sys::IN_IGNORED`]. /// /// [`inotify_sys::IN_IGNORED`]: ../inotify_sys/constant.IN_IGNORED.html const IGNORED = ffi::IN_IGNORED; /// Event related to a directory /// /// The subject of the event is a directory. /// /// See [`inotify_sys::IN_ISDIR`]. /// /// [`inotify_sys::IN_ISDIR`]: ../inotify_sys/constant.IN_ISDIR.html const ISDIR = ffi::IN_ISDIR; /// Event queue overflowed /// /// The event queue has overflowed and events have presumably been lost. /// /// See [`inotify_sys::IN_Q_OVERFLOW`]. /// /// [`inotify_sys::IN_Q_OVERFLOW`]: ../inotify_sys/constant.IN_Q_OVERFLOW.html const Q_OVERFLOW = ffi::IN_Q_OVERFLOW; /// File system containing watched object was unmounted. /// File system was unmounted /// /// The file system that contained the watched object has been /// unmounted. An event with [`WatchMask::IGNORED`] will subsequently be /// generated for the same watch descriptor. /// /// See [`inotify_sys::IN_UNMOUNT`]. /// /// [`WatchMask::IGNORED`]: #associatedconstant.IGNORED /// [`inotify_sys::IN_UNMOUNT`]: ../inotify_sys/constant.IN_UNMOUNT.html const UNMOUNT = ffi::IN_UNMOUNT; } } #[cfg(test)] mod tests { use std::{ io::prelude::*, mem, slice, sync, }; use crate::util; use inotify_sys as ffi; use super::Event; #[test] fn from_buffer_should_not_mistake_next_event_for_name_of_previous_event() { let mut buffer = [0u8; 1024]; // Make sure the buffer is properly aligned before writing raw events into it let buffer = util::align_buffer_mut(&mut buffer); // First, put a normal event into the buffer let event = ffi::inotify_event { wd: 0, mask: 0, cookie: 0, len: 0, // no name following after event }; let event = unsafe { slice::from_raw_parts( &event as *const _ as *const u8, mem::size_of_val(&event), ) }; (&mut buffer[..]).write(event) .expect("Failed to write into buffer"); // After that event, simulate an event that starts with a non-zero byte. buffer[mem::size_of_val(&event)] = 1; // Now create the event and verify that the name is actually `None`, as // dictated by the value `len` above. let (_, event) = Event::from_buffer( sync::Weak::new(), &buffer, ); assert_eq!(event.name, None); } } inotify-0.9.6/src/fd_guard.rs000064400000000000000000000032500072674642500142610ustar 00000000000000use std::{ ops::Deref, os::unix::io::{ AsRawFd, FromRawFd, IntoRawFd, RawFd, }, sync::atomic::{ AtomicBool, Ordering, }, }; use inotify_sys as ffi; /// A RAII guard around a `RawFd` that closes it automatically on drop. #[derive(Debug)] pub struct FdGuard { pub(crate) fd : RawFd, pub(crate) close_on_drop: AtomicBool, } impl FdGuard { /// Indicate that the wrapped file descriptor should _not_ be closed /// when the guard is dropped. /// /// This should be called in cases where ownership of the wrapped file /// descriptor has been "moved" out of the guard. /// /// This is factored out into a separate function to ensure that it's /// always used consistently. #[inline] pub fn should_not_close(&self) { self.close_on_drop.store(false, Ordering::Release); } } impl Deref for FdGuard { type Target = RawFd; #[inline] fn deref(&self) -> &Self::Target { &self.fd } } impl Drop for FdGuard { fn drop(&mut self) { if self.close_on_drop.load(Ordering::Acquire) { unsafe { ffi::close(self.fd); } } } } impl FromRawFd for FdGuard { unsafe fn from_raw_fd(fd: RawFd) -> Self { FdGuard { fd, close_on_drop: AtomicBool::new(true), } } } impl IntoRawFd for FdGuard { fn into_raw_fd(self) -> RawFd { self.should_not_close(); self.fd } } impl AsRawFd for FdGuard { fn as_raw_fd(&self) -> RawFd { self.fd } } impl PartialEq for FdGuard { fn eq(&self, other: &FdGuard) -> bool { self.fd == other.fd } } inotify-0.9.6/src/inotify.rs000064400000000000000000000415340072674642500141760ustar 00000000000000use std::{ ffi::CString, io, os::unix::ffi::OsStrExt, os::unix::io::{ AsRawFd, FromRawFd, IntoRawFd, RawFd, }, path::Path, sync::{ atomic::AtomicBool, Arc, } }; use inotify_sys as ffi; use libc::{ F_GETFL, F_SETFD, F_SETFL, FD_CLOEXEC, O_NONBLOCK, fcntl, }; use crate::events::Events; use crate::fd_guard::FdGuard; use crate::util::read_into_buffer; use crate::watches::{ WatchDescriptor, WatchMask, }; #[cfg(feature = "stream")] use crate::stream::EventStream; /// Idiomatic Rust wrapper around Linux's inotify API /// /// `Inotify` is a wrapper around an inotify instance. It generally tries to /// adhere to the underlying inotify API closely, while making access to it /// safe and convenient. /// /// Please refer to the [top-level documentation] for further details and a /// usage example. /// /// [top-level documentation]: index.html #[derive(Debug)] pub struct Inotify { fd: Arc, } impl Inotify { /// Creates an [`Inotify`] instance /// /// Initializes an inotify instance by calling [`inotify_init1`]. /// /// This method passes both flags accepted by [`inotify_init1`], not giving /// the user any choice in the matter, as not passing the flags would be /// inappropriate in the context of this wrapper: /// /// - [`IN_CLOEXEC`] prevents leaking file descriptors to other processes. /// - [`IN_NONBLOCK`] controls the blocking behavior of the inotify API, /// which is entirely managed by this wrapper. /// /// # Errors /// /// Directly returns the error from the call to [`inotify_init1`], without /// adding any error conditions of its own. /// /// # Examples /// /// ``` /// use inotify::Inotify; /// /// let inotify = Inotify::init() /// .expect("Failed to initialize an inotify instance"); /// ``` /// /// [`Inotify`]: struct.Inotify.html /// [`inotify_init1`]: ../inotify_sys/fn.inotify_init1.html /// [`IN_CLOEXEC`]: ../inotify_sys/constant.IN_CLOEXEC.html /// [`IN_NONBLOCK`]: ../inotify_sys/constant.IN_NONBLOCK.html pub fn init() -> io::Result { // Initialize inotify and set CLOEXEC and NONBLOCK flags. // // NONBLOCK is needed, because `Inotify` manages blocking behavior for // the API consumer, and the way we do that is to make everything non- // blocking by default and later override that as required. // // CLOEXEC prevents leaking file descriptors to processes executed by // this process and seems to be a best practice. I don't grasp this // issue completely and failed to find any authoritative sources on the // topic. There's some discussion in the open(2) and fcntl(2) man pages, // but I didn't find that helpful in understanding the issue of leaked // file descriptors. For what it's worth, there's a Rust issue about // this: // https://github.com/rust-lang/rust/issues/12148 let fd = unsafe { let fd = ffi::inotify_init(); if fd == -1 { return Err(io::Error::last_os_error()); } if fcntl(fd, F_SETFD, FD_CLOEXEC) == -1 { return Err(io::Error::last_os_error()); } if fcntl(fd, F_SETFL, O_NONBLOCK) == -1 { return Err(io::Error::last_os_error()); } fd }; Ok(Inotify { fd: Arc::new(FdGuard { fd, close_on_drop: AtomicBool::new(true), }), }) } /// Adds or updates a watch for the given path /// /// Adds a new watch or updates an existing one for the file referred to by /// `path`. Returns a watch descriptor that can be used to refer to this /// watch later. /// /// The `mask` argument defines what kind of changes the file should be /// watched for, and how to do that. See the documentation of [`WatchMask`] /// for details. /// /// If this method is used to add a new watch, a new [`WatchDescriptor`] is /// returned. If it is used to update an existing watch, a /// [`WatchDescriptor`] that equals the previously returned /// [`WatchDescriptor`] for that watch is returned instead. /// /// Under the hood, this method just calls [`inotify_add_watch`] and does /// some trivial translation between the types on the Rust side and the C /// side. /// /// # Attention: Updating watches and hardlinks /// /// As mentioned above, this method can be used to update an existing watch. /// This is usually done by calling this method with the same `path` /// argument that it has been called with before. But less obviously, it can /// also happen if the method is called with a different path that happens /// to link to the same inode. /// /// You can detect this by keeping track of [`WatchDescriptor`]s and the /// paths they have been returned for. If the same [`WatchDescriptor`] is /// returned for a different path (and you haven't freed the /// [`WatchDescriptor`] by removing the watch), you know you have two paths /// pointing to the same inode, being watched by the same watch. /// /// # Errors /// /// Directly returns the error from the call to /// [`inotify_add_watch`][`inotify_add_watch`] (translated into an /// `io::Error`), without adding any error conditions of /// its own. /// /// # Examples /// /// ``` /// use inotify::{ /// Inotify, /// WatchMask, /// }; /// /// let mut inotify = Inotify::init() /// .expect("Failed to initialize an inotify instance"); /// /// # // Create a temporary file, so `add_watch` won't return an error. /// # use std::fs::File; /// # File::create("/tmp/inotify-rs-test-file") /// # .expect("Failed to create test file"); /// # /// inotify.add_watch("/tmp/inotify-rs-test-file", WatchMask::MODIFY) /// .expect("Failed to add file watch"); /// /// // Handle events for the file here /// ``` /// /// [`inotify_add_watch`]: ../inotify_sys/fn.inotify_add_watch.html /// [`WatchMask`]: struct.WatchMask.html /// [`WatchDescriptor`]: struct.WatchDescriptor.html pub fn add_watch

(&mut self, path: P, mask: WatchMask) -> io::Result where P: AsRef { let path = CString::new(path.as_ref().as_os_str().as_bytes())?; let wd = unsafe { ffi::inotify_add_watch( **self.fd, path.as_ptr() as *const _, mask.bits(), ) }; match wd { -1 => Err(io::Error::last_os_error()), _ => Ok(WatchDescriptor{ id: wd, fd: Arc::downgrade(&self.fd) }), } } /// Stops watching a file /// /// Removes the watch represented by the provided [`WatchDescriptor`] by /// calling [`inotify_rm_watch`]. [`WatchDescriptor`]s can be obtained via /// [`Inotify::add_watch`], or from the `wd` field of [`Event`]. /// /// # Errors /// /// Directly returns the error from the call to [`inotify_rm_watch`]. /// Returns an [`io::Error`] with [`ErrorKind`]`::InvalidInput`, if the given /// [`WatchDescriptor`] did not originate from this [`Inotify`] instance. /// /// # Examples /// /// ``` /// use inotify::Inotify; /// /// let mut inotify = Inotify::init() /// .expect("Failed to initialize an inotify instance"); /// /// # // Create a temporary file, so `add_watch` won't return an error. /// # use std::fs::File; /// # let mut test_file = File::create("/tmp/inotify-rs-test-file") /// # .expect("Failed to create test file"); /// # /// # // Add a watch and modify the file, so the code below doesn't block /// # // forever. /// # use inotify::WatchMask; /// # inotify.add_watch("/tmp/inotify-rs-test-file", WatchMask::MODIFY) /// # .expect("Failed to add file watch"); /// # use std::io::Write; /// # write!(&mut test_file, "something\n") /// # .expect("Failed to write something to test file"); /// # /// let mut buffer = [0; 1024]; /// let events = inotify /// .read_events_blocking(&mut buffer) /// .expect("Error while waiting for events"); /// /// for event in events { /// inotify.rm_watch(event.wd); /// } /// ``` /// /// [`WatchDescriptor`]: struct.WatchDescriptor.html /// [`inotify_rm_watch`]: ../inotify_sys/fn.inotify_rm_watch.html /// [`Inotify::add_watch`]: struct.Inotify.html#method.add_watch /// [`Event`]: struct.Event.html /// [`Inotify`]: struct.Inotify.html /// [`io::Error`]: https://doc.rust-lang.org/std/io/struct.Error.html /// [`ErrorKind`]: https://doc.rust-lang.org/std/io/enum.ErrorKind.html pub fn rm_watch(&mut self, wd: WatchDescriptor) -> io::Result<()> { if wd.fd.upgrade().as_ref() != Some(&self.fd) { return Err(io::Error::new( io::ErrorKind::InvalidInput, "Invalid WatchDescriptor", )); } let result = unsafe { ffi::inotify_rm_watch(**self.fd, wd.id) }; match result { 0 => Ok(()), -1 => Err(io::Error::last_os_error()), _ => panic!( "unexpected return code from inotify_rm_watch ({})", result) } } /// Waits until events are available, then returns them /// /// Blocks the current thread until at least one event is available. If this /// is not desirable, please consider [`Inotify::read_events`]. /// /// This method calls [`Inotify::read_events`] internally and behaves /// essentially the same, apart from the blocking behavior. Please refer to /// the documentation of [`Inotify::read_events`] for more information. /// /// [`Inotify::read_events`]: struct.Inotify.html#method.read_events /// [`read`]: ../libc/fn.read.html pub fn read_events_blocking<'a>(&mut self, buffer: &'a mut [u8]) -> io::Result> { unsafe { let res = fcntl(**self.fd, F_GETFL); if res == -1 { return Err(io::Error::last_os_error()); } if fcntl(**self.fd, F_SETFL, res & !O_NONBLOCK) == -1 { return Err(io::Error::last_os_error()); } }; let result = self.read_events(buffer); unsafe { let res = fcntl(**self.fd, F_GETFL); if res == -1 { return Err(io::Error::last_os_error()); } if fcntl(**self.fd, F_SETFL, res | O_NONBLOCK) == -1 { return Err(io::Error::last_os_error()); } }; result } /// Returns one buffer's worth of available events /// /// Reads as many events as possible into `buffer`, and returns an iterator /// over them. If no events are available, an iterator is still returned. If /// you need a method that will block until at least one event is available, /// please consider [`read_events_blocking`]. /// /// Please note that inotify will merge identical successive unread events /// into a single event. This means this method can not be used to count the /// number of file system events. /// /// The `buffer` argument, as the name indicates, is used as a buffer for /// the inotify events. Its contents may be overwritten. /// /// # Errors /// /// This function directly returns all errors from the call to [`read`] /// (except EGAIN/EWOULDBLOCK, which result in an empty iterator). In /// addition, [`ErrorKind::UnexpectedEof`] is returned, if the call to /// [`read`] returns `0`, signaling end-of-file. /// /// If `buffer` is too small, this will result in an error with /// [`ErrorKind::InvalidInput`]. On very old Linux kernels, /// [`ErrorKind::UnexpectedEof`] will be returned instead. /// /// # Examples /// /// ``` /// use inotify::Inotify; /// /// let mut inotify = Inotify::init() /// .expect("Failed to initialize an inotify instance"); /// /// let mut buffer = [0; 1024]; /// let events = inotify.read_events(&mut buffer) /// .expect("Error while reading events"); /// /// for event in events { /// // Handle event /// } /// ``` /// /// [`read_events_blocking`]: struct.Inotify.html#method.read_events_blocking /// [`read`]: ../libc/fn.read.html /// [`ErrorKind::UnexpectedEof`]: https://doc.rust-lang.org/std/io/enum.ErrorKind.html#variant.UnexpectedEof /// [`ErrorKind::InvalidInput`]: https://doc.rust-lang.org/std/io/enum.ErrorKind.html#variant.InvalidInput pub fn read_events<'a>(&mut self, buffer: &'a mut [u8]) -> io::Result> { let num_bytes = read_into_buffer(**self.fd, buffer); let num_bytes = match num_bytes { 0 => { return Err( io::Error::new( io::ErrorKind::UnexpectedEof, "`read` return `0`, signaling end-of-file" ) ); } -1 => { let error = io::Error::last_os_error(); if error.kind() == io::ErrorKind::WouldBlock { return Ok(Events::new(Arc::downgrade(&self.fd), buffer, 0)); } else { return Err(error); } }, _ if num_bytes < 0 => { panic!("{} {} {} {} {} {}", "Unexpected return value from `read`. Received a negative", "value that was not `-1`. According to the `read` man page", "this shouldn't happen, as either `-1` is returned on", "error, `0` on end-of-file, or a positive value for the", "number of bytes read. Returned value:", num_bytes, ); } _ => { // The value returned by `read` should be `isize`. Let's quickly // verify this with the following assignment, so we can be sure // our cast below is valid. let num_bytes: isize = num_bytes; // The type returned by `read` is `isize`, and we've ruled out // all negative values with the match arms above. This means we // can safely cast to `usize`. debug_assert!(num_bytes > 0); num_bytes as usize } }; Ok(Events::new(Arc::downgrade(&self.fd), buffer, num_bytes)) } /// Create a stream which collects events /// /// Returns a `Stream` over all events that are available. This stream is an /// infinite source of events. /// /// An internal buffer which can hold the largest possible event is used. #[cfg(feature = "stream")] pub fn event_stream(&mut self, buffer: T) -> io::Result> where T: AsMut<[u8]> + AsRef<[u8]>, { EventStream::new(self.fd.clone(), buffer) } /// Closes the inotify instance /// /// Closes the file descriptor referring to the inotify instance. The user /// usually doesn't have to call this function, as the underlying inotify /// instance is closed automatically, when [`Inotify`] is dropped. /// /// # Errors /// /// Directly returns the error from the call to [`close`], without adding any /// error conditions of its own. /// /// # Examples /// /// ``` /// use inotify::Inotify; /// /// let mut inotify = Inotify::init() /// .expect("Failed to initialize an inotify instance"); /// /// inotify.close() /// .expect("Failed to close inotify instance"); /// ``` /// /// [`Inotify`]: struct.Inotify.html /// [`close`]: ../libc/fn.close.html pub fn close(self) -> io::Result<()> { // `self` will be dropped when this method returns. If this is the only // owner of `fd`, the `Arc` will also be dropped. The `Drop` // implementation for `FdGuard` will attempt to close the file descriptor // again, unless this flag here is cleared. self.fd.should_not_close(); match unsafe { ffi::close(**self.fd) } { 0 => Ok(()), _ => Err(io::Error::last_os_error()), } } } impl AsRawFd for Inotify { #[inline] fn as_raw_fd(&self) -> RawFd { self.fd.as_raw_fd() } } impl FromRawFd for Inotify { unsafe fn from_raw_fd(fd: RawFd) -> Self { Inotify { fd: Arc::new(FdGuard::from_raw_fd(fd)) } } } impl IntoRawFd for Inotify { #[inline] fn into_raw_fd(self) -> RawFd { self.fd.should_not_close(); self.fd.fd } } inotify-0.9.6/src/lib.rs000064400000000000000000000056310072674642500132610ustar 00000000000000//! Idiomatic inotify wrapper for the Rust programming language //! //! # About //! //! [inotify-rs] is an idiomatic wrapper around the Linux kernel's [inotify] API //! for the Rust programming language. It can be used for monitoring changes to //! files or directories. //! //! The [`Inotify`] struct is the main entry point into the API. //! //! # Example //! //! ``` //! use inotify::{ //! Inotify, //! WatchMask, //! }; //! //! let mut inotify = Inotify::init() //! .expect("Error while initializing inotify instance"); //! //! # // Create a temporary file, so `add_watch` won't return an error. //! # use std::fs::File; //! # let mut test_file = File::create("/tmp/inotify-rs-test-file") //! # .expect("Failed to create test file"); //! # //! // Watch for modify and close events. //! inotify //! .add_watch( //! "/tmp/inotify-rs-test-file", //! WatchMask::MODIFY | WatchMask::CLOSE, //! ) //! .expect("Failed to add file watch"); //! //! # // Modify file, so the following `read_events_blocking` won't block. //! # use std::io::Write; //! # write!(&mut test_file, "something\n") //! # .expect("Failed to write something to test file"); //! # //! // Read events that were added with `add_watch` above. //! let mut buffer = [0; 1024]; //! let events = inotify.read_events_blocking(&mut buffer) //! .expect("Error while reading events"); //! //! for event in events { //! // Handle event //! } //! ``` //! //! # Attention: inotify gotchas //! //! inotify (as in, the Linux API, not this wrapper) has many edge cases, making //! it hard to use correctly. This can lead to weird and hard to find bugs in //! applications that are based on it. inotify-rs does its best to fix these //! issues, but sometimes this would require an amount of runtime overhead that //! is just unacceptable for a low-level wrapper such as this. //! //! We've documented any issues that inotify-rs has inherited from inotify, as //! far as we are aware of them. Please watch out for any further warnings //! throughout this documentation. If you want to be on the safe side, in case //! we have missed something, please read the [inotify man pages] carefully. //! //! [inotify-rs]: https://crates.io/crates/inotify //! [inotify]: https://en.wikipedia.org/wiki/Inotify //! [`Inotify`]: struct.Inotify.html //! [inotify man pages]: http://man7.org/linux/man-pages/man7/inotify.7.html #![deny(missing_docs)] #![deny(warnings)] #![deny(missing_debug_implementations)] #[macro_use] extern crate bitflags; mod events; mod fd_guard; mod inotify; mod util; mod watches; #[cfg(feature = "stream")] mod stream; pub use crate::events::{ Event, EventMask, EventOwned, Events, }; pub use crate::inotify::Inotify; pub use crate::util::{ get_buffer_size, get_absolute_path_buffer_size, }; pub use crate::watches::{ WatchDescriptor, WatchMask, }; #[cfg(feature = "stream")] pub use self::stream::EventStream; inotify-0.9.6/src/stream.rs000064400000000000000000000060540072674642500140060ustar 00000000000000use std::{ io, os::unix::io::{AsRawFd, RawFd}, pin::Pin, sync::Arc, task::{Context, Poll}, }; use futures_core::{ready, Stream}; use tokio::io::unix::AsyncFd; use crate::events::{Event, EventOwned}; use crate::fd_guard::FdGuard; use crate::util::read_into_buffer; /// Stream of inotify events /// /// Allows for streaming events returned by [`Inotify::event_stream`]. /// /// [`Inotify::event_stream`]: struct.Inotify.html#method.event_stream #[derive(Debug)] pub struct EventStream { fd: AsyncFd, buffer: T, buffer_pos: usize, unused_bytes: usize, } impl EventStream where T: AsMut<[u8]> + AsRef<[u8]>, { /// Returns a new `EventStream` associated with the default reactor. pub(crate) fn new(fd: Arc, buffer: T) -> io::Result { Ok(EventStream { fd: AsyncFd::new(ArcFdGuard(fd))?, buffer: buffer, buffer_pos: 0, unused_bytes: 0, }) } } impl Stream for EventStream where T: AsMut<[u8]> + AsRef<[u8]>, { type Item = io::Result; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { // Safety: safe because we never move out of `self_`. let self_ = unsafe { self.get_unchecked_mut() }; if self_.unused_bytes == 0 { // Nothing usable in buffer. Need to reset and fill buffer. self_.buffer_pos = 0; self_.unused_bytes = ready!(read(&self_.fd, self_.buffer.as_mut(), cx))?; } if self_.unused_bytes == 0 { // The previous read returned `0` signalling end-of-file. Let's // signal end-of-stream to the caller. return Poll::Ready(None); } // We have bytes in the buffer. inotify doesn't put partial events in // there, and we only take complete events out. That means we have at // least one event in there and can call `from_buffer` to take it out. let (bytes_consumed, event) = Event::from_buffer( Arc::downgrade(&self_.fd.get_ref().0), &self_.buffer.as_ref()[self_.buffer_pos..], ); self_.buffer_pos += bytes_consumed; self_.unused_bytes -= bytes_consumed; Poll::Ready(Some(Ok(event.into_owned()))) } } // Newtype wrapper because AsRawFd isn't implemented for Arc where T: AsRawFd. #[derive(Debug)] struct ArcFdGuard(Arc); impl AsRawFd for ArcFdGuard { fn as_raw_fd(&self) -> RawFd { self.0.as_raw_fd() } } fn read(fd: &AsyncFd, buffer: &mut [u8], cx: &mut Context) -> Poll> { let mut guard = ready!(fd.poll_read_ready(cx))?; let result = guard.try_io(|_| { let read = read_into_buffer(fd.as_raw_fd(), buffer); if read == -1 { return Err(io::Error::last_os_error()); } Ok(read as usize) }); match result { Ok(result) => Poll::Ready(result), Err(_would_block) => { cx.waker().wake_by_ref(); Poll::Pending } } } inotify-0.9.6/src/util.rs000064400000000000000000000057400072674642500134710ustar 00000000000000use std::{ io, mem, os::unix::io::RawFd, path::Path, }; use inotify_sys as ffi; use libc::{ c_void, size_t, }; const INOTIFY_EVENT_SIZE: usize = mem::size_of::() + 257; pub fn read_into_buffer(fd: RawFd, buffer: &mut [u8]) -> isize { unsafe { // Discard the unaligned portion, if any, of the supplied buffer let buffer = align_buffer_mut(buffer); ffi::read( fd, buffer.as_mut_ptr() as *mut c_void, buffer.len() as size_t ) } } pub fn align_buffer(buffer: &[u8]) -> &[u8] { if buffer.len() >= mem::align_of::() { let ptr = buffer.as_ptr(); let offset = ptr.align_offset(mem::align_of::()); &buffer[offset..] } else { &buffer[0..0] } } pub fn align_buffer_mut(buffer: &mut [u8]) -> &mut [u8] { if buffer.len() >= mem::align_of::() { let ptr = buffer.as_mut_ptr(); let offset = ptr.align_offset(mem::align_of::()); &mut buffer[offset..] } else { &mut buffer[0..0] } } /// Get the inotify event buffer size /// /// The maximum size of an inotify event and thus the buffer size to hold it /// can be calculated using this formula: /// `sizeof(struct inotify_event) + NAME_MAX + 1` /// /// See: [https://man7.org/linux/man-pages/man7/inotify.7.html](https://man7.org/linux/man-pages/man7/inotify.7.html) /// /// The NAME_MAX size formula is: /// `ABSOLUTE_PARENT_PATH_LEN + 1 + 255` /// /// - `ABSOLUTE_PARENT_PATH_LEN` will be calculated at runtime. /// - Add 1 to account for a `/`, either in between the parent path and a filename /// or for the root directory. /// - Add the maximum number of chars in a filename, 255. /// /// See: [https://github.com/torvalds/linux/blob/master/include/uapi/linux/limits.h](https://github.com/torvalds/linux/blob/master/include/uapi/linux/limits.h) /// /// Unfortunately, we can't just do the same with max path length itself. /// /// See: [https://eklitzke.org/path-max-is-tricky](https://eklitzke.org/path-max-is-tricky) /// /// This function is really just a fallible wrapper around `get_absolute_path_buffer_size()`. /// /// path: A relative or absolute path for the inotify events. pub fn get_buffer_size(path: &Path) -> io::Result { Ok(get_absolute_path_buffer_size(&path.canonicalize()?)) } /// Get the inotify event buffer size for an absolute path /// /// For relative paths, consider using `get_buffer_size()` which provides a fallible wrapper /// for this function. /// /// path: An absolute path for the inotify events. pub fn get_absolute_path_buffer_size(path: &Path) -> usize { INOTIFY_EVENT_SIZE // Get the length of the absolute parent path, if the path is not the root directory. // Because we canonicalize the path, we do not need to worry about prefixes. + if let Some(parent_path) = path.parent() { parent_path.as_os_str().len() } else { 0 } } inotify-0.9.6/src/watches.rs000064400000000000000000000265630072674642500141600ustar 00000000000000use std::{ hash::{ Hash, Hasher, }, cmp::Ordering, os::raw::c_int, sync::Weak, }; use inotify_sys as ffi; use crate::fd_guard::FdGuard; bitflags! { /// Describes a file system watch /// /// Passed to [`Inotify::add_watch`], to describe what file system events /// to watch for, and how to do that. /// /// # Examples /// /// `WatchMask` constants can be passed to [`Inotify::add_watch`] as is. For /// example, here's how to create a watch that triggers an event when a file /// is accessed: /// /// ``` rust /// # use inotify::{ /// # Inotify, /// # WatchMask, /// # }; /// # /// # let mut inotify = Inotify::init().unwrap(); /// # /// # // Create a temporary file, so `add_watch` won't return an error. /// # use std::fs::File; /// # File::create("/tmp/inotify-rs-test-file") /// # .expect("Failed to create test file"); /// # /// inotify.add_watch("/tmp/inotify-rs-test-file", WatchMask::ACCESS) /// .expect("Error adding watch"); /// ``` /// /// You can also combine multiple `WatchMask` constants. Here we add a watch /// this is triggered both when files are created or deleted in a directory: /// /// ``` rust /// # use inotify::{ /// # Inotify, /// # WatchMask, /// # }; /// # /// # let mut inotify = Inotify::init().unwrap(); /// inotify.add_watch("/tmp/", WatchMask::CREATE | WatchMask::DELETE) /// .expect("Error adding watch"); /// ``` /// /// [`Inotify::add_watch`]: struct.Inotify.html#method.add_watch pub struct WatchMask: u32 { /// File was accessed /// /// When watching a directory, this event is only triggered for objects /// inside the directory, not the directory itself. /// /// See [`inotify_sys::IN_ACCESS`]. /// /// [`inotify_sys::IN_ACCESS`]: ../inotify_sys/constant.IN_ACCESS.html const ACCESS = ffi::IN_ACCESS; /// Metadata (permissions, timestamps, ...) changed /// /// When watching a directory, this event can be triggered for the /// directory itself, as well as objects inside the directory. /// /// See [`inotify_sys::IN_ATTRIB`]. /// /// [`inotify_sys::IN_ATTRIB`]: ../inotify_sys/constant.IN_ATTRIB.html const ATTRIB = ffi::IN_ATTRIB; /// File opened for writing was closed /// /// When watching a directory, this event is only triggered for objects /// inside the directory, not the directory itself. /// /// See [`inotify_sys::IN_CLOSE_WRITE`]. /// /// [`inotify_sys::IN_CLOSE_WRITE`]: ../inotify_sys/constant.IN_CLOSE_WRITE.html const CLOSE_WRITE = ffi::IN_CLOSE_WRITE; /// File or directory not opened for writing was closed /// /// When watching a directory, this event can be triggered for the /// directory itself, as well as objects inside the directory. /// /// See [`inotify_sys::IN_CLOSE_NOWRITE`]. /// /// [`inotify_sys::IN_CLOSE_NOWRITE`]: ../inotify_sys/constant.IN_CLOSE_NOWRITE.html const CLOSE_NOWRITE = ffi::IN_CLOSE_NOWRITE; /// File/directory created in watched directory /// /// When watching a directory, this event is only triggered for objects /// inside the directory, not the directory itself. /// /// See [`inotify_sys::IN_CREATE`]. /// /// [`inotify_sys::IN_CREATE`]: ../inotify_sys/constant.IN_CREATE.html const CREATE = ffi::IN_CREATE; /// File/directory deleted from watched directory /// /// When watching a directory, this event is only triggered for objects /// inside the directory, not the directory itself. /// /// See [`inotify_sys::IN_DELETE`]. /// /// [`inotify_sys::IN_DELETE`]: ../inotify_sys/constant.IN_DELETE.html const DELETE = ffi::IN_DELETE; /// Watched file/directory was deleted /// /// See [`inotify_sys::IN_DELETE_SELF`]. /// /// [`inotify_sys::IN_DELETE_SELF`]: ../inotify_sys/constant.IN_DELETE_SELF.html const DELETE_SELF = ffi::IN_DELETE_SELF; /// File was modified /// /// When watching a directory, this event is only triggered for objects /// inside the directory, not the directory itself. /// /// See [`inotify_sys::IN_MODIFY`]. /// /// [`inotify_sys::IN_MODIFY`]: ../inotify_sys/constant.IN_MODIFY.html const MODIFY = ffi::IN_MODIFY; /// Watched file/directory was moved /// /// See [`inotify_sys::IN_MOVE_SELF`]. /// /// [`inotify_sys::IN_MOVE_SELF`]: ../inotify_sys/constant.IN_MOVE_SELF.html const MOVE_SELF = ffi::IN_MOVE_SELF; /// File was renamed/moved; watched directory contained old name /// /// When watching a directory, this event is only triggered for objects /// inside the directory, not the directory itself. /// /// See [`inotify_sys::IN_MOVED_FROM`]. /// /// [`inotify_sys::IN_MOVED_FROM`]: ../inotify_sys/constant.IN_MOVED_FROM.html const MOVED_FROM = ffi::IN_MOVED_FROM; /// File was renamed/moved; watched directory contains new name /// /// When watching a directory, this event is only triggered for objects /// inside the directory, not the directory itself. /// /// See [`inotify_sys::IN_MOVED_TO`]. /// /// [`inotify_sys::IN_MOVED_TO`]: ../inotify_sys/constant.IN_MOVED_TO.html const MOVED_TO = ffi::IN_MOVED_TO; /// File or directory was opened /// /// When watching a directory, this event can be triggered for the /// directory itself, as well as objects inside the directory. /// /// See [`inotify_sys::IN_OPEN`]. /// /// [`inotify_sys::IN_OPEN`]: ../inotify_sys/constant.IN_OPEN.html const OPEN = ffi::IN_OPEN; /// Watch for all events /// /// This constant is simply a convenient combination of the following /// other constants: /// /// - [`ACCESS`] /// - [`ATTRIB`] /// - [`CLOSE_WRITE`] /// - [`CLOSE_NOWRITE`] /// - [`CREATE`] /// - [`DELETE`] /// - [`DELETE_SELF`] /// - [`MODIFY`] /// - [`MOVE_SELF`] /// - [`MOVED_FROM`] /// - [`MOVED_TO`] /// - [`OPEN`] /// /// See [`inotify_sys::IN_ALL_EVENTS`]. /// /// [`ACCESS`]: #associatedconstant.ACCESS /// [`ATTRIB`]: #associatedconstant.ATTRIB /// [`CLOSE_WRITE`]: #associatedconstant.CLOSE_WRITE /// [`CLOSE_NOWRITE`]: #associatedconstant.CLOSE_NOWRITE /// [`CREATE`]: #associatedconstant.CREATE /// [`DELETE`]: #associatedconstant.DELETE /// [`DELETE_SELF`]: #associatedconstant.DELETE_SELF /// [`MODIFY`]: #associatedconstant.MODIFY /// [`MOVE_SELF`]: #associatedconstant.MOVE_SELF /// [`MOVED_FROM`]: #associatedconstant.MOVED_FROM /// [`MOVED_TO`]: #associatedconstant.MOVED_TO /// [`OPEN`]: #associatedconstant.OPEN /// [`inotify_sys::IN_ALL_EVENTS`]: ../inotify_sys/constant.IN_ALL_EVENTS.html const ALL_EVENTS = ffi::IN_ALL_EVENTS; /// Watch for all move events /// /// This constant is simply a convenient combination of the following /// other constants: /// /// - [`MOVED_FROM`] /// - [`MOVED_TO`] /// /// See [`inotify_sys::IN_MOVE`]. /// /// [`MOVED_FROM`]: #associatedconstant.MOVED_FROM /// [`MOVED_TO`]: #associatedconstant.MOVED_TO /// [`inotify_sys::IN_MOVE`]: ../inotify_sys/constant.IN_MOVE.html const MOVE = ffi::IN_MOVE; /// Watch for all close events /// /// This constant is simply a convenient combination of the following /// other constants: /// /// - [`CLOSE_WRITE`] /// - [`CLOSE_NOWRITE`] /// /// See [`inotify_sys::IN_CLOSE`]. /// /// [`CLOSE_WRITE`]: #associatedconstant.CLOSE_WRITE /// [`CLOSE_NOWRITE`]: #associatedconstant.CLOSE_NOWRITE /// [`inotify_sys::IN_CLOSE`]: ../inotify_sys/constant.IN_CLOSE.html const CLOSE = ffi::IN_CLOSE; /// Don't dereference the path if it is a symbolic link /// /// See [`inotify_sys::IN_DONT_FOLLOW`]. /// /// [`inotify_sys::IN_DONT_FOLLOW`]: ../inotify_sys/constant.IN_DONT_FOLLOW.html const DONT_FOLLOW = ffi::IN_DONT_FOLLOW; /// Filter events for directory entries that have been unlinked /// /// See [`inotify_sys::IN_EXCL_UNLINK`]. /// /// [`inotify_sys::IN_EXCL_UNLINK`]: ../inotify_sys/constant.IN_EXCL_UNLINK.html const EXCL_UNLINK = ffi::IN_EXCL_UNLINK; /// If a watch for the inode exists, amend it instead of replacing it /// /// See [`inotify_sys::IN_MASK_ADD`]. /// /// [`inotify_sys::IN_MASK_ADD`]: ../inotify_sys/constant.IN_MASK_ADD.html const MASK_ADD = ffi::IN_MASK_ADD; /// Only receive one event, then remove the watch /// /// See [`inotify_sys::IN_ONESHOT`]. /// /// [`inotify_sys::IN_ONESHOT`]: ../inotify_sys/constant.IN_ONESHOT.html const ONESHOT = ffi::IN_ONESHOT; /// Only watch path, if it is a directory /// /// See [`inotify_sys::IN_ONLYDIR`]. /// /// [`inotify_sys::IN_ONLYDIR`]: ../inotify_sys/constant.IN_ONLYDIR.html const ONLYDIR = ffi::IN_ONLYDIR; } } /// Represents a watch on an inode /// /// Can be obtained from [`Inotify::add_watch`] or from an [`Event`]. A watch /// descriptor can be used to get inotify to stop watching an inode by passing /// it to [`Inotify::rm_watch`]. /// /// [`Inotify::add_watch`]: struct.Inotify.html#method.add_watch /// [`Inotify::rm_watch`]: struct.Inotify.html#method.rm_watch /// [`Event`]: struct.Event.html #[derive(Clone, Debug)] pub struct WatchDescriptor{ pub(crate) id: c_int, pub(crate) fd: Weak, } impl Eq for WatchDescriptor {} impl PartialEq for WatchDescriptor { fn eq(&self, other: &Self) -> bool { let self_fd = self.fd.upgrade(); let other_fd = other.fd.upgrade(); self.id == other.id && self_fd.is_some() && self_fd == other_fd } } impl Ord for WatchDescriptor { fn cmp(&self, other: &Self) -> Ordering { self.id.cmp(&other.id) } } impl PartialOrd for WatchDescriptor { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Hash for WatchDescriptor { fn hash(&self, state: &mut H) { // This function only takes `self.id` into account, as `self.fd` is a // weak pointer that might no longer be available. Since neither // panicking nor changing the hash depending on whether it's available // is acceptable, we just don't look at it at all. // I don't think that this influences storage in a `HashMap` or // `HashSet` negatively, as storing `WatchDescriptor`s from different // `Inotify` instances seems like something of an anti-pattern anyway. self.id.hash(state); } } inotify-0.9.6/tests/main.rs000064400000000000000000000160320072674642500140070ustar 00000000000000#![deny(warnings)] // This test suite is incomplete and doesn't cover all available functionality. // Contributions to improve test coverage would be highly appreciated! use inotify::{ Inotify, WatchMask, }; use std::fs::File; use std::io::{ Write, ErrorKind, }; use std::os::unix::io::{ AsRawFd, FromRawFd, IntoRawFd, }; use std::path::PathBuf; use tempfile::TempDir; #[test] fn it_should_watch_a_file() { let mut testdir = TestDir::new(); let (path, mut file) = testdir.new_file(); let mut inotify = Inotify::init().unwrap(); let watch = inotify.add_watch(&path, WatchMask::MODIFY).unwrap(); write_to(&mut file); let mut buffer = [0; 1024]; let events = inotify.read_events_blocking(&mut buffer).unwrap(); let mut num_events = 0; for event in events { assert_eq!(watch, event.wd); num_events += 1; } assert!(num_events > 0); } #[cfg(feature = "stream")] #[tokio::test] async fn it_should_watch_a_file_async() { let mut testdir = TestDir::new(); let (path, mut file) = testdir.new_file(); let mut inotify = Inotify::init().unwrap(); let watch = inotify.add_watch(&path, WatchMask::MODIFY).unwrap(); write_to(&mut file); let mut buffer = [0; 1024]; use futures_util::StreamExt; let events = inotify .event_stream(&mut buffer[..]) .unwrap() .take(1) .collect::>() .await; let mut num_events = 0; for event in events { if let Ok(event) = event { assert_eq!(watch, event.wd); num_events += 1; } } assert!(num_events > 0); } #[test] fn it_should_return_immediately_if_no_events_are_available() { let mut inotify = Inotify::init().unwrap(); let mut buffer = [0; 1024]; assert_eq!(0, inotify.read_events(&mut buffer).unwrap().count()); } #[test] fn it_should_convert_the_name_into_an_os_str() { let mut testdir = TestDir::new(); let (path, mut file) = testdir.new_file(); let mut inotify = Inotify::init().unwrap(); inotify.add_watch(&path.parent().unwrap(), WatchMask::MODIFY).unwrap(); write_to(&mut file); let mut buffer = [0; 1024]; let mut events = inotify.read_events_blocking(&mut buffer).unwrap(); if let Some(event) = events.next() { assert_eq!(path.file_name(), event.name); } else { panic!("Expected inotify event"); } } #[test] fn it_should_set_name_to_none_if_it_is_empty() { let mut testdir = TestDir::new(); let (path, mut file) = testdir.new_file(); let mut inotify = Inotify::init().unwrap(); inotify.add_watch(&path, WatchMask::MODIFY).unwrap(); write_to(&mut file); let mut buffer = [0; 1024]; let mut events = inotify.read_events_blocking(&mut buffer).unwrap(); if let Some(event) = events.next() { assert_eq!(event.name, None); } else { panic!("Expected inotify event"); } } #[test] fn it_should_not_accept_watchdescriptors_from_other_instances() { let mut testdir = TestDir::new(); let (path, _) = testdir.new_file(); let mut inotify = Inotify::init().unwrap(); let _ = inotify.add_watch(&path, WatchMask::ACCESS).unwrap(); let mut second_inotify = Inotify::init().unwrap(); let wd2 = second_inotify.add_watch(&path, WatchMask::ACCESS).unwrap(); assert_eq!(inotify.rm_watch(wd2).unwrap_err().kind(), ErrorKind::InvalidInput); } #[test] fn watch_descriptors_from_different_inotify_instances_should_not_be_equal() { let mut testdir = TestDir::new(); let (path, _) = testdir.new_file(); let mut inotify_1 = Inotify::init() .unwrap(); let mut inotify_2 = Inotify::init() .unwrap(); let wd_1 = inotify_1 .add_watch(&path, WatchMask::ACCESS) .unwrap(); let wd_2 = inotify_2 .add_watch(&path, WatchMask::ACCESS) .unwrap(); // As far as inotify is concerned, watch descriptors are just integers that // are scoped per inotify instance. This means that multiple instances will // produce the same watch descriptor number, a case we want inotify-rs to // detect. assert!(wd_1 != wd_2); } #[test] fn watch_descriptor_equality_should_not_be_confused_by_reused_fds() { let mut testdir = TestDir::new(); let (path, _) = testdir.new_file(); // When a new inotify instance is created directly after closing another // one, it is possible that the file descriptor is reused immediately, and // we end up with a new instance that has the same file descriptor as the // old one. // This is quite likely, but it doesn't happen every time. Therefore we may // need a few tries until we find two instances where that is the case. let (wd_1, mut inotify_2) = loop { let mut inotify_1 = Inotify::init() .unwrap(); let wd_1 = inotify_1 .add_watch(&path, WatchMask::ACCESS) .unwrap(); let fd_1 = inotify_1.as_raw_fd(); inotify_1 .close() .unwrap(); let inotify_2 = Inotify::init() .unwrap(); if fd_1 == inotify_2.as_raw_fd() { break (wd_1, inotify_2); } }; let wd_2 = inotify_2 .add_watch(&path, WatchMask::ACCESS) .unwrap(); // The way we engineered this situation, both `WatchDescriptor` instances // have the same fields. They still come from different inotify instances // though, so they shouldn't be equal. assert!(wd_1 != wd_2); inotify_2 .close() .unwrap(); // A little extra gotcha: If both inotify instances are closed, and the `Eq` // implementation naively compares the weak pointers, both will be `None`, // making them equal. Let's make sure this isn't the case. assert!(wd_1 != wd_2); } #[test] fn it_should_implement_raw_fd_traits_correctly() { let fd = Inotify::init() .expect("Failed to initialize inotify instance") .into_raw_fd(); // If `IntoRawFd` has been implemented naively, `Inotify`'s `Drop` // implementation will have closed the inotify instance at this point. Let's // make sure this didn't happen. let mut inotify = unsafe { ::from_raw_fd(fd) }; let mut buffer = [0; 1024]; if let Err(error) = inotify.read_events(&mut buffer) { panic!("Failed to add watch: {}", error); } } struct TestDir { dir: TempDir, counter: u32, } impl TestDir { fn new() -> TestDir { TestDir { dir: TempDir::new().unwrap(), counter: 0, } } fn new_file(&mut self) -> (PathBuf, File) { let id = self.counter; self.counter += 1; let path = self.dir.path().join("file-".to_string() + &id.to_string()); let file = File::create(&path) .unwrap_or_else(|error| panic!("Failed to create temporary file: {}", error)); (path, file) } } fn write_to(file: &mut File) { file .write(b"This should trigger an inotify event.") .unwrap_or_else(|error| panic!("Failed to write to file: {}", error) ); }