Import 'inotify' crate

Request Document: go/android-rust-importing-crates
For CL Reviewers: go/android3p#cl-review
For Build Team: go/ab-third-party-imports
Bug: 374129524
Test: m libinotify

Change-Id: I764e81e10d1a1ba8a5da3b24e79e25706ac22167
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..61f9b03
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,108 @@
+// This file is generated by cargo_embargo.
+// Do not modify this file because the changes will be overridden on upgrade.
+
+package {
+    default_applicable_licenses: ["external_rust_crates_inotify_license"],
+    default_team: "trendy_team_android_rust",
+}
+
+license {
+    name: "external_rust_crates_inotify_license",
+    visibility: [":__subpackages__"],
+    license_kinds: ["SPDX-license-identifier-ISC"],
+    license_text: ["LICENSE"],
+}
+
+rust_test {
+    name: "inotify_test_src_lib",
+    host_supported: true,
+    crate_name: "inotify",
+    cargo_env_compat: true,
+    cargo_pkg_version: "0.11.0",
+    crate_root: "src/lib.rs",
+    test_suites: ["general-tests"],
+    auto_gen_config: true,
+    test_options: {
+        unit_test: true,
+    },
+    edition: "2018",
+    features: [
+        "default",
+        "futures-core",
+        "stream",
+        "tokio",
+    ],
+    rustlibs: [
+        "libbitflags",
+        "libfutures_core",
+        "libfutures_util",
+        "libinotify_sys",
+        "liblibc",
+        "libmaplit",
+        "librand",
+        "libtempfile",
+        "libtokio",
+    ],
+}
+
+rust_test {
+    name: "inotify_test_tests_main",
+    host_supported: true,
+    crate_name: "main",
+    cargo_env_compat: true,
+    cargo_pkg_version: "0.11.0",
+    crate_root: "tests/main.rs",
+    test_suites: ["general-tests"],
+    auto_gen_config: true,
+    test_options: {
+        unit_test: true,
+    },
+    edition: "2018",
+    features: [
+        "default",
+        "futures-core",
+        "stream",
+        "tokio",
+    ],
+    rustlibs: [
+        "libbitflags",
+        "libfutures_core",
+        "libfutures_util",
+        "libinotify",
+        "libinotify_sys",
+        "liblibc",
+        "libmaplit",
+        "librand",
+        "libtempfile",
+        "libtokio",
+    ],
+}
+
+rust_library {
+    name: "libinotify",
+    host_supported: true,
+    crate_name: "inotify",
+    cargo_env_compat: true,
+    cargo_pkg_version: "0.11.0",
+    crate_root: "src/lib.rs",
+    edition: "2018",
+    features: [
+        "default",
+        "futures-core",
+        "stream",
+        "tokio",
+    ],
+    rustlibs: [
+        "libbitflags",
+        "libfutures_core",
+        "libinotify_sys",
+        "liblibc",
+        "libtokio",
+    ],
+    apex_available: [
+        "//apex_available:platform",
+        "//apex_available:anyapex",
+    ],
+    product_available: true,
+    vendor_available: true,
+}
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..710d1d9
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,165 @@
+# Changelog
+
+## v0.11.0 (2024-08-19)
+
+- Fix link in README ([#209])
+- **Breaking change:** Make `bits` field of `EventMask`/`WatchMask` inaccessible. You can use the `.bits()` method instead. ([#211], [#218])
+- Fix various links in documentation ([#213])
+- Bump minimum supported Rust version (MSRV) to 1.70. ([#219])
+
+[#209]: https://github.com/hannobraun/inotify-rs/pull/209
+[#211]: https://github.com/hannobraun/inotify-rs/pull/211
+[#213]: https://github.com/hannobraun/inotify-rs/pull/213
+[#218]: https://github.com/hannobraun/inotify-rs/pull/218
+[#219]: https://github.com/hannobraun/inotify-rs/pull/219
+
+
+## v0.10.2 (2023-07-27)
+
+- Fix broken links to `Watches` in documentation ([#205])
+
+[#205]: https://github.com/hannobraun/inotify-rs/pull/205
+
+
+## v0.10.1 (2023-06-07)
+
+- Add `WatchDescriptor::get_watch_descriptor_id` ([#193])
+- Add `Event::to_owned` ([#196])
+- Deprecate `Event::into_owned` ([#196])
+- Add `Watches`/`Inotify::watches`/`EventStream::watches` ([#197])
+- Deprecate `Inotify::add_watch`/`Inotify::rm_watch` ([#197])
+- Add `Inotify::into_event_stream`/`EventStream::into_inotify` ([#199])
+- Deprecate `Inotify::event_stream` ([#199])
+- Implement `AsFd` and bidirectional conversion to/from `OwnedFd` ([#202])
+- Raise Minimum Supported Rust Version (MSRV) to 1.63.0 ([#202])
+
+[#193]: https://github.com/hannobraun/inotify-rs/pull/193
+[#196]: https://github.com/hannobraun/inotify-rs/pull/196
+[#197]: https://github.com/hannobraun/inotify-rs/pull/197
+[#199]: https://github.com/hannobraun/inotify-rs/pull/199
+[#202]: https://github.com/hannobraun/inotify-rs/pull/202
+
+
+## v0.10.0 (2021-12-07)
+
+- **Breaking change:** Remove special handling of `WouldBlock` error ([#190])
+
+[#190]: https://github.com/hannobraun/inotify-rs/pull/190
+
+
+## v0.9.6 (2021-11-03)
+
+- 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)
+
+- Make stream API more flexible in regards to buffers ([ea3e7a394bf34a6ccce4f2136c0991fe7e8f1f42](ea3e7a394bf34a6ccce4f2136c0991fe7e8f1f42)) (breaking change)
+
+
+## v0.6.1 (2018-08-28)
+
+- Don't return spurious filenames ([2f37560f](2f37560f))
+
+
+## v0.6.0 (2018-08-16)
+
+- Handle closing of inotify instance better ([824160fe](824160fe))
+- Implement `EventStream` using `mio` ([ba4cb8c7](ba4cb8c7))
+
+
+## v0.5.1 (2018-02-27)
+
+- Add future-based async API ([569e65a7](569e65a7), closes [#49](49))
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..73da607
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,25 @@
+# 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
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..69ded48
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,495 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "addr2line"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "autocfg"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
+
+[[package]]
+name = "backtrace"
+version = "0.3.73"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a"
+dependencies = [
+ "addr2line",
+ "cc",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+]
+
+[[package]]
+name = "bitflags"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "cc"
+version = "1.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e9e8aabfac534be767c909e0690571677d49f41bd8465ae876fe043d52ba5292"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "errno"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a"
+
+[[package]]
+name = "futures-core"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "futures-task"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
+
+[[package]]
+name = "futures-util"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
+dependencies = [
+ "futures-core",
+ "futures-macro",
+ "futures-task",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "gimli"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
+
+[[package]]
+name = "hermit-abi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
+
+[[package]]
+name = "inotify"
+version = "0.11.0"
+dependencies = [
+ "bitflags",
+ "futures-core",
+ "futures-util",
+ "inotify-sys",
+ "libc",
+ "maplit",
+ "rand",
+ "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.155"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
+
+[[package]]
+name = "maplit"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
+
+[[package]]
+name = "memchr"
+version = "2.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "mio"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "wasi",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "object"
+version = "0.36.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
+
+[[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.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
+dependencies = [
+ "zerocopy",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.86"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
+
+[[package]]
+name = "rustix"
+version = "0.38.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
+dependencies = [
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "socket2"
+version = "0.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.74"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64"
+dependencies = [
+ "cfg-if",
+ "fastrand",
+ "once_cell",
+ "rustix",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "tokio"
+version = "1.39.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1"
+dependencies = [
+ "backtrace",
+ "libc",
+ "mio",
+ "pin-project-lite",
+ "socket2",
+ "tokio-macros",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "zerocopy"
+version = "0.7.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
+dependencies = [
+ "byteorder",
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.7.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..551b475
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,111 @@
+# 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"
+rust-version = "1.70"
+name = "inotify"
+version = "0.11.0"
+authors = [
+    "Hanno Braun <[email protected]>",
+    "Félix Saparelli <[email protected]>",
+    "Cristian Kubis <[email protected]>",
+    "Frank Denis <[email protected]>",
+]
+build = false
+exclude = [
+    "/.travis.yml",
+    "/inotify-rs.sublime-project",
+]
+autobins = false
+autoexamples = false
+autotests = false
+autobenches = false
+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"
+
+[lib]
+name = "inotify"
+path = "src/lib.rs"
+
+[[example]]
+name = "stream"
+path = "examples/stream.rs"
+required-features = ["stream"]
+
+[[example]]
+name = "watch"
+path = "examples/watch.rs"
+
+[[test]]
+name = "main"
+path = "tests/main.rs"
+
+[dependencies.bitflags]
+version = "2"
+
+[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.maplit]
+version = "1.0"
+
+[dev-dependencies.rand]
+version = "0.8"
+
+[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"
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
new file mode 100644
index 0000000..e485f19
--- /dev/null
+++ b/Cargo.toml.orig
@@ -0,0 +1,53 @@
+[package]
+
+name    = "inotify"
+version = "0.11.0"
+authors = [
+  "Hanno Braun <[email protected]>",
+  "Félix Saparelli <[email protected]>",
+  "Cristian Kubis <[email protected]>",
+  "Frank Denis <[email protected]>"
+]
+edition = "2018"
+rust-version = "1.70"
+
+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     = "2"
+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]
+maplit = "1.0"
+rand = "0.8"
+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"
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..855ee51
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,13 @@
+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.
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..a9386db
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,16 @@
+name: "inotify"
+description:
+    "Idiomatic inotify wrapper for the Rust programming language."
+
+third_party {
+homepage: "https://crates.io/crates/inotify"
+  identifier {
+    type: "Archive"
+    value: "https://static.crates.io/crates/inotify/inotify-0.11.0.crate"
+    primary_source: true
+    version: "0.11.0"
+  }
+  version: "0.11.0"
+  last_upgrade_date { year: 2024 month: 11 day: 12 }
+  license_type: NOTICE
+}
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..48bea6e
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 688011
+include platform/prebuilts/rust:main:/OWNERS
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e5ecd52
--- /dev/null
+++ b/README.md
@@ -0,0 +1,116 @@
+# 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
+        .watches()
+        .add(
+            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.11"
+```
+
+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/main/examples
+[inotify man page]: http://man7.org/linux/man-pages/man7/inotify.7.html
diff --git a/cargo_embargo.json b/cargo_embargo.json
new file mode 100644
index 0000000..c8842d1
--- /dev/null
+++ b/cargo_embargo.json
@@ -0,0 +1,4 @@
+{
+  "run_cargo": false,
+  "tests": true
+}
diff --git a/examples/stream.rs b/examples/stream.rs
new file mode 100644
index 0000000..ca2282f
--- /dev/null
+++ b/examples/stream.rs
@@ -0,0 +1,39 @@
+use 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 inotify = Inotify::init()
+        .expect("Failed to initialize inotify");
+
+    let dir = TempDir::new()?;
+
+    inotify.watches().add(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.into_event_stream(&mut buffer)?;
+
+    while let Some(event_or_error) = stream.next().await {
+        println!("event: {:?}", event_or_error?);
+    }
+
+    Ok(())
+}
diff --git a/examples/watch.rs b/examples/watch.rs
new file mode 100644
index 0000000..8bdeb7b
--- /dev/null
+++ b/examples/watch.rs
@@ -0,0 +1,55 @@
+use 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
+        .watches()
+        .add(
+            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);
+                }
+            }
+        }
+    }
+}
diff --git a/src/events.rs b/src/events.rs
new file mode 100644
index 0000000..88473f3
--- /dev/null
+++ b/src/events.rs
@@ -0,0 +1,419 @@
+use 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;
+
+
+/// Iterator over inotify events
+///
+/// Allows for iteration over the events returned by
+/// [`Inotify::read_events_blocking`] or [`Inotify::read_events`].
+///
+/// [`Inotify::read_events_blocking`]: crate::Inotify::read_events_blocking
+/// [`Inotify::read_events`]: crate::Inotify::read_events
+#[derive(Debug)]
+pub struct Events<'a> {
+    fd       : Weak<FdGuard>,
+    buffer   : &'a [u8],
+    num_bytes: usize,
+    pos      : usize,
+}
+
+impl<'a> Events<'a> {
+    pub(crate) fn new(fd: Weak<FdGuard>, buffer: &'a [u8], num_bytes: usize)
+        -> Self
+    {
+        Events {
+            fd,
+            buffer,
+            num_bytes,
+            pos: 0,
+        }
+    }
+}
+
+impl<'a> Iterator for Events<'a> {
+    type Item = Event<&'a OsStr>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        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 [`Watches::add`]. To
+/// retrieve events, call [`Inotify::read_events_blocking`] or
+/// [`Inotify::read_events`].
+///
+/// [`Watches::add`]: crate::Watches::add
+/// [`Inotify::read_events_blocking`]: crate::Inotify::read_events_blocking
+/// [`Inotify::read_events`]: crate::Inotify::read_events
+#[derive(Clone, Debug)]
+pub struct Event<S> {
+    /// Identifies the watch this event originates from
+    ///
+    /// This [`WatchDescriptor`] is equal to the one that [`Watches::add`]
+    /// returned when interest for this event was registered. The
+    /// [`WatchDescriptor`] can be used to remove the watch using
+    /// [`Watches::remove`], thereby preventing future events of this type
+    /// from being created.
+    ///
+    /// [`Watches::add`]: crate::Watches::add
+    /// [`Watches::remove`]: crate::Watches::remove
+    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`]: EventMask::MOVED_FROM
+    /// [`MOVED_TO`]: EventMask::MOVED_TO
+    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<S>,
+}
+
+impl<'a> Event<&'a OsStr> {
+    fn new(fd: Weak<FdGuard>, 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.is_empty() {
+            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<FdGuard>,
+        buffer: &'a [u8],
+    )
+        -> (usize, Self)
+    {
+        let event_size = mem::size_of::<ffi::inotify_event>();
+
+        // Make sure that the 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 ffi_event_ptr = 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
+        // read that `inotify_event`.
+        // We call `read_unaligned()` since the byte buffer has alignment 1
+        // and `inotify_event` has a higher alignment, so `*` cannot be used to dereference
+        // the unaligned pointer (undefined behavior).
+        let ffi_event = unsafe { ffi_event_ptr.read_unaligned() };
+
+        // 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 >= ffi_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 + ffi_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,
+            &ffi_event,
+            OsStr::from_bytes(name),
+        );
+
+        (bytes_consumed, event)
+    }
+
+    /// Returns an owned copy of the event.
+    #[deprecated = "use `to_owned()` instead; methods named `into_owned()` usually take self by value"]
+    #[allow(clippy::wrong_self_convention)]
+    pub fn into_owned(&self) -> EventOwned {
+        self.to_owned()
+    }
+
+    /// Returns an owned copy of the event.
+    #[must_use = "cloning is often expensive and is not expected to have side effects"]
+    pub fn to_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<OsString>;
+
+
+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.
+    #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
+    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`].
+        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`].
+        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`].
+        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`].
+        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`].
+        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.
+        const DELETE = ffi::IN_DELETE;
+
+        /// Watched file/directory was deleted
+        ///
+        /// See [`inotify_sys::IN_DELETE_SELF`].
+        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`].
+        const MODIFY = ffi::IN_MODIFY;
+
+        /// Watched file/directory was moved
+        ///
+        /// See [`inotify_sys::IN_MOVE_SELF`].
+        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`].
+        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`].
+        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`].
+        const OPEN = ffi::IN_OPEN;
+
+        /// Watch was removed
+        ///
+        /// This event will be generated, if the watch was removed explicitly
+        /// (via [`Watches::remove`]), or automatically (because the file was
+        /// deleted or the file system was unmounted).
+        ///
+        /// See [`inotify_sys::IN_IGNORED`].
+        ///
+        /// [`Watches::remove`]: crate::Watches::remove
+        const IGNORED = ffi::IN_IGNORED;
+
+        /// Event related to a directory
+        ///
+        /// The subject of the event is a directory.
+        ///
+        /// See [`inotify_sys::IN_ISDIR`].
+        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`].
+        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 [`EventMask::IGNORED`] will subsequently be
+        /// generated for the same watch descriptor.
+        ///
+        /// See [`inotify_sys::IN_UNMOUNT`].
+        const UNMOUNT = ffi::IN_UNMOUNT;
+    }
+}
+
+impl EventMask {
+    /// Wrapper around [`Self::from_bits_retain`] for backwards compatibility
+    ///
+    /// # Safety
+    ///
+    /// This function is not actually unsafe. It is just a wrapper around the
+    /// safe [`Self::from_bits_retain`].
+    #[deprecated = "Use the safe `from_bits_retain` method instead"]
+    pub unsafe fn from_bits_unchecked(bits: u32) -> Self {
+        Self::from_bits_retain(bits)
+    }
+}
+
+
+#[cfg(test)]
+mod tests {
+    use std::{
+        io::prelude::*,
+        mem,
+        slice,
+        sync,
+    };
+
+    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];
+
+        // 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);
+    }
+}
diff --git a/src/fd_guard.rs b/src/fd_guard.rs
new file mode 100644
index 0000000..dfadfcd
--- /dev/null
+++ b/src/fd_guard.rs
@@ -0,0 +1,93 @@
+use std::{
+    ops::Deref,
+    os::unix::io::{
+        AsFd,
+        AsRawFd,
+        BorrowedFd,
+        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 AsFd for FdGuard {
+    #[inline]
+    fn as_fd(&self) -> BorrowedFd<'_> {
+        unsafe { BorrowedFd::borrow_raw(self.fd) }
+    }
+}
+
+impl PartialEq for FdGuard {
+    fn eq(&self, other: &FdGuard) -> bool {
+        self.fd == other.fd
+    }
+}
diff --git a/src/inotify.rs b/src/inotify.rs
new file mode 100644
index 0000000..0b1080f
--- /dev/null
+++ b/src/inotify.rs
@@ -0,0 +1,386 @@
+use std::{
+    io,
+    os::unix::io::{
+        AsFd,
+        AsRawFd,
+        BorrowedFd,
+        FromRawFd,
+        IntoRawFd,
+        OwnedFd,
+        RawFd,
+    },
+    path::Path,
+    sync::{
+        atomic::AtomicBool,
+        Arc,
+    }
+};
+
+use inotify_sys as ffi;
+use libc::{
+    F_GETFL,
+    F_SETFL,
+    O_NONBLOCK,
+    fcntl,
+};
+
+use crate::events::Events;
+use crate::fd_guard::FdGuard;
+use crate::util::read_into_buffer;
+use crate::watches::{
+    WatchDescriptor,
+    WatchMask,
+    Watches,
+};
+
+
+#[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]: crate
+#[derive(Debug)]
+pub struct Inotify {
+    fd: Arc<FdGuard>,
+}
+
+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_init1`]: inotify_sys::inotify_init1
+    /// [`IN_CLOEXEC`]: inotify_sys::IN_CLOEXEC
+    /// [`IN_NONBLOCK`]: inotify_sys::IN_NONBLOCK
+    pub fn init() -> io::Result<Inotify> {
+        let fd = unsafe {
+            // Initialize inotify and pass both `IN_CLOEXEC` and `IN_NONBLOCK`.
+            //
+            // `IN_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.
+            //
+            // Passing `IN_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
+            ffi::inotify_init1(ffi::IN_CLOEXEC | ffi::IN_NONBLOCK)
+        };
+
+        if fd == -1 {
+            return Err(io::Error::last_os_error());
+        }
+
+        Ok(Inotify {
+            fd: Arc::new(FdGuard {
+                fd,
+                close_on_drop: AtomicBool::new(true),
+            }),
+        })
+    }
+
+    /// Gets an interface that allows adding and removing watches.
+    /// See [`Watches::add`] and [`Watches::remove`].
+    pub fn watches(&self) -> Watches {
+        Watches::new(self.fd.clone())
+    }
+
+    /// Deprecated: use `Inotify.watches().add()` instead
+    #[deprecated = "use `Inotify.watches().add()` instead"]
+    pub fn add_watch<P>(&mut self, path: P, mask: WatchMask)
+        -> io::Result<WatchDescriptor>
+        where P: AsRef<Path>
+    {
+        self.watches().add(path, mask)
+    }
+
+    /// Deprecated: use `Inotify.watches().remove()` instead
+    #[deprecated = "use `Inotify.watches().remove()` instead"]
+    pub fn rm_watch(&mut self, wd: WatchDescriptor) -> io::Result<()> {
+        self.watches().remove(wd)
+    }
+
+    /// 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.
+    pub fn read_events_blocking<'a>(&mut self, buffer: &'a mut [u8])
+        -> io::Result<Events<'a>>
+    {
+        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`].
+    /// 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
+    ///
+    /// ```no_run
+    /// use inotify::Inotify;
+    /// use std::io::ErrorKind;
+    ///
+    /// let mut inotify = Inotify::init()
+    ///     .expect("Failed to initialize an inotify instance");
+    ///
+    /// let mut buffer = [0; 1024];
+    /// let events = loop {
+    ///     match inotify.read_events(&mut buffer) {
+    ///         Ok(events) => break events,
+    ///         Err(error) if error.kind() == ErrorKind::WouldBlock => continue,
+    ///         _ => panic!("Error while reading events"),
+    ///     }
+    /// };
+    ///
+    /// for event in events {
+    ///     // Handle event
+    /// }
+    /// ```
+    ///
+    /// [`read_events_blocking`]: Self::read_events_blocking
+    /// [`read`]: libc::read
+    /// [`ErrorKind::UnexpectedEof`]: std::io::ErrorKind::UnexpectedEof
+    /// [`ErrorKind::InvalidInput`]: std::io::ErrorKind::InvalidInput
+    pub fn read_events<'a>(&mut self, buffer: &'a mut [u8])
+        -> io::Result<Events<'a>>
+    {
+        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();
+                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))
+    }
+
+    /// Deprecated: use `into_event_stream()` instead, which enforces a single `Stream` and predictable reads.
+    /// Using this method to create multiple `EventStream` instances from one `Inotify` is unsupported,
+    /// as they will contend over one event source and each produce unpredictable stream contents.
+    #[deprecated = "use `into_event_stream()` instead, which enforces a single Stream and predictable reads"]
+    #[cfg(feature = "stream")]
+    pub fn event_stream<T>(&mut self, buffer: T)
+        -> io::Result<EventStream<T>>
+    where
+        T: AsMut<[u8]> + AsRef<[u8]>,
+    {
+        EventStream::new(self.fd.clone(), buffer)
+    }
+
+    /// Create a stream which collects events. Consumes the `Inotify` instance.
+    ///
+    /// 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 into_event_stream<T>(self, buffer: T)
+        -> io::Result<EventStream<T>>
+    where
+        T: AsMut<[u8]> + AsRef<[u8]>,
+    {
+        EventStream::new(self.fd, buffer)
+    }
+
+    /// Creates an `Inotify` instance using the file descriptor which was originally
+    /// initialized in `Inotify::init`. This is intended to be used to transform an
+    /// `EventStream` back into an `Inotify`. Do not attempt to clone `Inotify` with this.
+    #[cfg(feature = "stream")]
+    pub(crate) fn from_file_descriptor(fd: Arc<FdGuard>) -> Self
+    {
+        Inotify {
+            fd,
+        }
+    }
+
+    /// 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");
+    /// ```
+    ///
+    /// [`close`]: libc::close
+    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
+    }
+}
+
+impl AsFd for Inotify {
+    #[inline]
+    fn as_fd(&self) -> BorrowedFd<'_> {
+        self.fd.as_fd()
+    }
+}
+
+impl From<Inotify> for OwnedFd {
+    fn from(fd: Inotify) -> OwnedFd {
+        unsafe { OwnedFd::from_raw_fd(fd.into_raw_fd()) }
+    }
+}
+
+impl From<OwnedFd> for Inotify {
+    fn from(fd: OwnedFd) -> Inotify {
+        unsafe { Inotify::from_raw_fd(fd.into_raw_fd()) }
+    }
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..e26e339
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,105 @@
+//! 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 `Watches::add` 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
+//!     .watches()
+//!     .add(
+//!         "/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 `Watches::add` 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 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::{
+    Watches,
+    WatchDescriptor,
+    WatchMask,
+};
+
+#[cfg(feature = "stream")]
+pub use self::stream::EventStream;
diff --git a/src/stream.rs b/src/stream.rs
new file mode 100644
index 0000000..7bbf434
--- /dev/null
+++ b/src/stream.rs
@@ -0,0 +1,120 @@
+use 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::Inotify;
+use crate::util::read_into_buffer;
+use crate::watches::Watches;
+
+/// Stream of inotify events
+///
+/// Allows for streaming events returned by [`Inotify::into_event_stream`].
+#[derive(Debug)]
+pub struct EventStream<T> {
+    fd: AsyncFd<ArcFdGuard>,
+    buffer: T,
+    buffer_pos: usize,
+    unused_bytes: usize,
+}
+
+impl<T> EventStream<T>
+where
+    T: AsMut<[u8]> + AsRef<[u8]>,
+{
+    /// Returns a new `EventStream` associated with the default reactor.
+    pub(crate) fn new(fd: Arc<FdGuard>, buffer: T) -> io::Result<Self> {
+        Ok(EventStream {
+            fd: AsyncFd::new(ArcFdGuard(fd))?,
+            buffer,
+            buffer_pos: 0,
+            unused_bytes: 0,
+        })
+    }
+
+    /// Returns an instance of `Watches` to add and remove watches.
+    /// See [`Watches::add`] and [`Watches::remove`].
+    pub fn watches(&self) -> Watches {
+        Watches::new(self.fd.get_ref().0.clone())
+    }
+
+    /// Consumes the `EventStream` instance and returns an `Inotify` using the original
+    /// file descriptor that was passed from `Inotify` to create the `EventStream`.
+    pub fn into_inotify(self) -> Inotify {
+        Inotify::from_file_descriptor(self.fd.into_inner().0)
+    }
+}
+
+impl<T> Stream for EventStream<T>
+where
+    T: AsMut<[u8]> + AsRef<[u8]>,
+{
+    type Item = io::Result<EventOwned>;
+
+    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
+        // 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.to_owned())))
+    }
+}
+
+// Newtype wrapper because AsRawFd isn't implemented for Arc<T> where T: AsRawFd.
+#[derive(Debug)]
+struct ArcFdGuard(Arc<FdGuard>);
+
+impl AsRawFd for ArcFdGuard {
+    fn as_raw_fd(&self) -> RawFd {
+        self.0.as_raw_fd()
+    }
+}
+
+fn read(fd: &AsyncFd<ArcFdGuard>, buffer: &mut [u8], cx: &mut Context) -> Poll<io::Result<usize>> {
+    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
+        }
+    }
+}
diff --git a/src/util.rs b/src/util.rs
new file mode 100644
index 0000000..28376bc
--- /dev/null
+++ b/src/util.rs
@@ -0,0 +1,70 @@
+use 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::<ffi::inotify_event>() + 257;
+
+pub fn read_into_buffer(fd: RawFd, buffer: &mut [u8]) -> isize {
+    unsafe {
+        ffi::read(
+            fd,
+            buffer.as_mut_ptr() as *mut c_void,
+            buffer.len() as size_t
+        )
+    }
+}
+
+/// 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>
+///
+/// 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>
+///
+/// Unfortunately, we can't just do the same with max path length itself.
+///
+/// See: <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<usize> {
+    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
+    }
+}
diff --git a/src/watches.rs b/src/watches.rs
new file mode 100644
index 0000000..2f0e66f
--- /dev/null
+++ b/src/watches.rs
@@ -0,0 +1,461 @@
+use std::{
+    cmp::Ordering,
+    ffi::CString,
+    hash::{
+        Hash,
+        Hasher,
+    },
+    io,
+    os::raw::c_int,
+    os::unix::ffi::OsStrExt,
+    path::Path,
+    sync::{
+        Arc,
+        Weak,
+    },
+};
+
+use inotify_sys as ffi;
+
+use crate::fd_guard::FdGuard;
+
+bitflags! {
+    /// Describes a file system watch
+    ///
+    /// Passed to [`Watches::add`], to describe what file system events
+    /// to watch for, and how to do that.
+    ///
+    /// # Examples
+    ///
+    /// `WatchMask` constants can be passed to [`Watches::add`] 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 `Watches::add` won't return an error.
+    /// # use std::fs::File;
+    /// # File::create("/tmp/inotify-rs-test-file")
+    /// #     .expect("Failed to create test file");
+    /// #
+    /// inotify.watches().add("/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.watches().add("/tmp/", WatchMask::CREATE | WatchMask::DELETE)
+    ///    .expect("Error adding watch");
+    /// ```
+    #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
+    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`].
+        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`].
+        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`].
+        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`].
+        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`].
+        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`].
+        const DELETE = ffi::IN_DELETE;
+
+        /// Watched file/directory was deleted
+        ///
+        /// See [`inotify_sys::IN_DELETE_SELF`].
+        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`].
+        const MODIFY = ffi::IN_MODIFY;
+
+        /// Watched file/directory was moved
+        ///
+        /// See [`inotify_sys::IN_MOVE_SELF`].
+        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`].
+        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`].
+        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`].
+        const OPEN = ffi::IN_OPEN;
+
+        /// Watch for all events
+        ///
+        /// This constant is simply a convenient combination of the following
+        /// other constants:
+        ///
+        /// - [`ACCESS`](Self::ACCESS)
+        /// - [`ATTRIB`](Self::ATTRIB)
+        /// - [`CLOSE_WRITE`](Self::CLOSE_WRITE)
+        /// - [`CLOSE_NOWRITE`](Self::CLOSE_NOWRITE)
+        /// - [`CREATE`](Self::CREATE)
+        /// - [`DELETE`](Self::DELETE)
+        /// - [`DELETE_SELF`](Self::DELETE_SELF)
+        /// - [`MODIFY`](Self::MODIFY)
+        /// - [`MOVE_SELF`](Self::MOVE_SELF)
+        /// - [`MOVED_FROM`](Self::MOVED_FROM)
+        /// - [`MOVED_TO`](Self::MOVED_TO)
+        /// - [`OPEN`](Self::OPEN)
+        ///
+        /// See [`inotify_sys::IN_ALL_EVENTS`].
+        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`](Self::MOVED_FROM)
+        /// - [`MOVED_TO`](Self::MOVED_TO)
+        ///
+        /// See [`inotify_sys::IN_MOVE`].
+        const MOVE = ffi::IN_MOVE;
+
+        /// Watch for all close events
+        ///
+        /// This constant is simply a convenient combination of the following
+        /// other constants:
+        ///
+        /// - [`CLOSE_WRITE`](Self::CLOSE_WRITE)
+        /// - [`CLOSE_NOWRITE`](Self::CLOSE_NOWRITE)
+        ///
+        /// See [`inotify_sys::IN_CLOSE`].
+        const CLOSE = ffi::IN_CLOSE;
+
+        /// Don't dereference the path if it is a symbolic link
+        ///
+        /// See [`inotify_sys::IN_DONT_FOLLOW`].
+        const DONT_FOLLOW = ffi::IN_DONT_FOLLOW;
+
+        /// Filter events for directory entries that have been unlinked
+        ///
+        /// See [`inotify_sys::IN_EXCL_UNLINK`].
+        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`].
+        const MASK_ADD = ffi::IN_MASK_ADD;
+
+        /// Only receive one event, then remove the watch
+        ///
+        /// See [`inotify_sys::IN_ONESHOT`].
+        const ONESHOT = ffi::IN_ONESHOT;
+
+        /// Only watch path, if it is a directory
+        ///
+        /// See [`inotify_sys::IN_ONLYDIR`].
+        const ONLYDIR = ffi::IN_ONLYDIR;
+    }
+}
+
+impl WatchMask {
+    /// Wrapper around [`Self::from_bits_retain`] for backwards compatibility
+    ///
+    /// # Safety
+    ///
+    /// This function is not actually unsafe. It is just a wrapper around the
+    /// safe [`Self::from_bits_retain`].
+    #[deprecated = "Use the safe `from_bits_retain` method instead"]
+    pub unsafe fn from_bits_unchecked(bits: u32) -> Self {
+        Self::from_bits_retain(bits)
+    }
+}
+
+impl WatchDescriptor {
+    /// Getter method for a watcher's id.
+    ///
+    /// Can be used to distinguish events for files with the same name.
+    pub fn get_watch_descriptor_id(&self) -> c_int {
+        self.id
+    }
+}
+
+/// Interface for adding and removing watches
+#[derive(Clone, Debug)]
+pub struct Watches {
+    pub(crate) fd: Arc<FdGuard>,
+}
+
+impl Watches {
+    /// Init watches with an inotify file descriptor
+    pub(crate) fn new(fd: Arc<FdGuard>) -> Self {
+        Watches {
+            fd,
+        }
+    }
+
+    /// 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 `Watches::add` won't return an error.
+    /// # use std::fs::File;
+    /// # File::create("/tmp/inotify-rs-test-file")
+    /// #     .expect("Failed to create test file");
+    /// #
+    /// inotify.watches().add("/tmp/inotify-rs-test-file", WatchMask::MODIFY)
+    ///     .expect("Failed to add file watch");
+    ///
+    /// // Handle events for the file here
+    /// ```
+    ///
+    /// [`inotify_add_watch`]: inotify_sys::inotify_add_watch
+    pub fn add<P>(&mut self, path: P, mask: WatchMask)
+                        -> io::Result<WatchDescriptor>
+        where P: AsRef<Path>
+    {
+        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
+    /// [`Watches::add`], 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 `Watches::add` 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.watches().add("/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");
+    /// let mut watches = inotify.watches();
+    ///
+    /// for event in events {
+    ///     watches.remove(event.wd);
+    /// }
+    /// ```
+    ///
+    /// [`inotify_rm_watch`]: inotify_sys::inotify_rm_watch
+    /// [`Event`]: crate::Event
+    /// [`Inotify`]: crate::Inotify
+    /// [`io::Error`]: std::io::Error
+    /// [`ErrorKind`]: std::io::ErrorKind
+    pub fn remove(&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)
+        }
+    }
+}
+
+
+/// Represents a watch on an inode
+///
+/// Can be obtained from [`Watches::add`] or from an [`Event`]. A watch
+/// descriptor can be used to get inotify to stop watching an inode by passing
+/// it to [`Watches::remove`].
+///
+/// [`Event`]: crate::Event
+#[derive(Clone, Debug)]
+pub struct WatchDescriptor{
+    pub(crate) id: c_int,
+    pub(crate) fd: Weak<FdGuard>,
+}
+
+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<Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl Hash for WatchDescriptor {
+    fn hash<H: Hasher>(&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);
+    }
+}
diff --git a/tests/main.rs b/tests/main.rs
new file mode 100644
index 0000000..4f9633c
--- /dev/null
+++ b/tests/main.rs
@@ -0,0 +1,475 @@
+#![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;
+
+#[cfg(feature = "stream")]
+use maplit::hashmap;
+#[cfg(feature = "stream")]
+use inotify::EventMask;
+#[cfg(feature = "stream")]
+use rand::{thread_rng, prelude::SliceRandom};
+#[cfg(feature = "stream")]
+use std::sync::{Mutex, Arc};
+#[cfg(feature = "stream")]
+use futures_util::StreamExt;
+
+
+#[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.watches().add(&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 inotify = Inotify::init().unwrap();
+
+    // Hold ownership of `watches` for this test, so that the underlying file descriptor has
+    // at least one reference to keep it alive, and we can inspect the WatchDescriptors below.
+    // Otherwise the `Weak<FdGuard>` contained in the WatchDescriptors will be invalidated
+    // when `inotify` is consumed by `into_event_stream()` and the EventStream is dropped
+    // during `await`.
+    let mut watches = inotify.watches();
+
+    let watch = watches.add(&path, WatchMask::MODIFY).unwrap();
+
+    write_to(&mut file);
+
+    let mut buffer = [0; 1024];
+
+    use futures_util::StreamExt;
+    let events = inotify
+        .into_event_stream(&mut buffer[..])
+        .unwrap()
+        .take(1)
+        .collect::<Vec<_>>()
+        .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);
+}
+
+#[cfg(feature = "stream")]
+#[tokio::test]
+async fn it_should_watch_a_file_from_eventstream_watches() {
+    let mut testdir = TestDir::new();
+    let (path, mut file) = testdir.new_file();
+
+    let inotify = Inotify::init().unwrap();
+
+    let mut buffer = [0; 1024];
+
+    use futures_util::StreamExt;
+    let stream = inotify.into_event_stream(&mut buffer[..]).unwrap();
+
+    // Hold ownership of `watches` for this test, so that the underlying file descriptor has
+    // at least one reference to keep it alive, and we can inspect the WatchDescriptors below.
+    // Otherwise the `Weak<FdGuard>` contained in the WatchDescriptors will be invalidated
+    // when `stream` is dropped during `await`.
+    let mut watches = stream.watches();
+
+    let watch = watches.add(&path, WatchMask::MODIFY).unwrap();
+    write_to(&mut file);
+
+    let events = stream
+        .take(1)
+        .collect::<Vec<_>>()
+        .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);
+}
+
+#[cfg(feature = "stream")]
+#[tokio::test]
+async fn it_should_watch_a_file_after_converting_back_from_eventstream() {
+    let mut testdir = TestDir::new();
+    let (path, mut file) = testdir.new_file();
+
+    let inotify = Inotify::init().unwrap();
+
+    let mut buffer = [0; 1024];
+    let stream = inotify.into_event_stream(&mut buffer[..]).unwrap();
+    let mut inotify = stream.into_inotify();
+
+    let watch = inotify.watches().add(&path, WatchMask::MODIFY).unwrap();
+
+    write_to(&mut file);
+
+    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);
+}
+
+#[test]
+fn it_should_return_immediately_if_no_events_are_available() {
+    let mut inotify = Inotify::init().unwrap();
+
+    let mut buffer = [0; 1024];
+    assert_eq!(inotify.read_events(&mut buffer).unwrap_err().kind(), ErrorKind::WouldBlock);
+}
+
+#[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.watches().add(&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.watches().add(&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 inotify = Inotify::init().unwrap();
+    let _ = inotify.watches().add(&path, WatchMask::ACCESS).unwrap();
+
+    let second_inotify = Inotify::init().unwrap();
+    let wd2 = second_inotify.watches().add(&path, WatchMask::ACCESS).unwrap();
+
+    assert_eq!(inotify.watches().remove(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 inotify_1 = Inotify::init()
+        .unwrap();
+    let inotify_2 = Inotify::init()
+        .unwrap();
+
+    let wd_1 = inotify_1
+        .watches()
+        .add(&path, WatchMask::ACCESS)
+        .unwrap();
+    let wd_2 = inotify_2
+        .watches()
+        .add(&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, inotify_2) = loop {
+        let inotify_1 = Inotify::init()
+            .unwrap();
+
+        let wd_1 = inotify_1
+            .watches()
+            .add(&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
+        .watches()
+        .add(&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 { <Inotify as FromRawFd>::from_raw_fd(fd) };
+
+    let mut buffer = [0; 1024];
+    if let Err(error) = inotify.read_events(&mut buffer) {
+        if error.kind() != ErrorKind::WouldBlock {
+            panic!("Failed to add watch: {}", error);
+        }
+    }
+}
+
+#[test]
+fn it_should_watch_correctly_with_a_watches_clone() {
+    let mut testdir = TestDir::new();
+    let (path, mut file) = testdir.new_file();
+
+    let mut inotify = Inotify::init().unwrap();
+    let mut watches1 = inotify.watches();
+    let mut watches2 = watches1.clone();
+    let watch1 = watches1.add(&path, WatchMask::MODIFY).unwrap();
+    let watch2 = watches2.add(&path, WatchMask::MODIFY).unwrap();
+
+    // same path and same Inotify should return same descriptor
+    assert_eq!(watch1, watch2);
+
+    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!(watch2, event.wd);
+        num_events += 1;
+    }
+    assert!(num_events > 0);
+}
+
+#[cfg(feature = "stream")]
+#[tokio::test]
+/// Testing if two files with the same name but different directories
+/// (e.g. "file_a" and "another_dir/file_a") are distinguished when _randomly_
+/// triggering a DELETE_SELF for the two files.
+async fn it_should_distinguish_event_for_files_with_same_name() {
+    let mut testdir = TestDir::new();
+    let testdir_path = testdir.dir.path().to_owned();
+    let file_order = Arc::new(Mutex::new(vec!["file_a", "another_dir/file_a"]));
+    file_order.lock().unwrap().shuffle(&mut thread_rng());
+    let file_order_clone = file_order.clone();
+
+    let inotify = Inotify::init().expect("Failed to initialize inotify instance");
+
+    // creating file_a inside `TestDir.dir`
+    let (path_1, _) = testdir.new_file_with_name("file_a");
+    // creating a directory inside `TestDir.dir`
+    testdir.new_directory_with_name("another_dir");
+    // creating a file inside `TestDir.dir/another_dir`
+    let (path_2, _) = testdir.new_file_in_directory_with_name("another_dir", "file_a");
+
+    // watching both files for `DELETE_SELF`
+    let wd_1 = inotify.watches().add(&path_1, WatchMask::DELETE_SELF).unwrap();
+    let wd_2 = inotify.watches().add(&path_2, WatchMask::DELETE_SELF).unwrap();
+
+    let expected_ids = hashmap! {
+        wd_1.get_watch_descriptor_id() => "file_a",
+        wd_2.get_watch_descriptor_id() => "another_dir/file_a"
+    };
+    let mut buffer = [0; 1024];
+
+    let file_removal_handler = tokio::spawn(async move {
+        for file in file_order.lock().unwrap().iter() {
+            testdir.delete_file(file);
+        }
+    });
+
+    let event_handle = tokio::spawn(async move {
+        let mut events = inotify.into_event_stream(&mut buffer).unwrap();
+        while let Some(Ok(event)) = events.next().await {
+            if event.mask == EventMask::DELETE_SELF {
+                let id = event.wd.get_watch_descriptor_id();
+                let file = expected_ids.get(&id).unwrap();
+                let full_path = testdir_path.join(*file);
+                println!("file {:?} was deleted", full_path);
+                file_order_clone.lock().unwrap().retain(|&x| x != *file);
+
+                if file_order_clone.lock().unwrap().is_empty() {
+                    break;
+                }
+            }
+        }
+    });
+
+    let () = event_handle.await.unwrap();
+    let () = file_removal_handler.await.unwrap();
+}
+
+struct TestDir {
+    dir: TempDir,
+    counter: u32,
+}
+
+impl TestDir {
+    fn new() -> TestDir {
+        TestDir {
+            dir: TempDir::new().unwrap(),
+            counter: 0,
+        }
+    }
+
+    #[cfg(feature = "stream")]
+    fn new_file_with_name(&mut self, file_name: &str) -> (PathBuf, File) {
+        self.counter += 1;
+
+        let path = self.dir.path().join(file_name);
+        let file = File::create(&path)
+            .unwrap_or_else(|error| panic!("Failed to create temporary file: {}", error));
+
+        (path, file)
+    }
+
+    #[cfg(feature = "stream")]
+    fn delete_file(&mut self, relative_path_to_file: &str) {
+        let path = &self.dir.path().join(relative_path_to_file);
+        std::fs::remove_file(path).unwrap();
+    }
+
+    #[cfg(feature = "stream")]
+    fn new_file_in_directory_with_name(
+        &mut self,
+        dir_name: &str,
+        file_name: &str,
+    ) -> (PathBuf, File) {
+        self.counter += 1;
+
+        let path = self.dir.path().join(dir_name).join(file_name);
+        let file = File::create(&path)
+            .unwrap_or_else(|error| panic!("Failed to create temporary file: {}", error));
+
+        (path, file)
+    }
+
+    #[cfg(feature = "stream")]
+    fn new_directory_with_name(&mut self, dir_name: &str) -> PathBuf {
+        let path = self.dir.path().join(dir_name);
+        let () = std::fs::create_dir(&path).unwrap();
+        path.to_path_buf()
+    }
+
+    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)
+        );
+}