Migrate 3 crates to monorepo.
inotify
inotify-sys
maplit
Bug: http://b/339424309
Test: treehugger
Change-Id: I5f92bbc807b4c5c3edf202a00158208c40f7282b
diff --git a/crates/inotify-sys/.android-checksum.json b/crates/inotify-sys/.android-checksum.json
new file mode 100644
index 0000000..7dfc6c8
--- /dev/null
+++ b/crates/inotify-sys/.android-checksum.json
@@ -0,0 +1 @@
+{"package":null,"files":{"LICENSE":"1121c9e8d60da948a7c73ecd1025dd17405332f37930c9d9be2350f0607a9638","cargo_embargo.json":"aa45a963da01d3f018be316cd5b7646a5b413ce2611c5218f2914d2e8a9efd0e","METADATA":"d78dc661a71552ce051ec327b0ecf04bb71774e7aef108f514f183fdc8efb195","CHANGELOG.md":"e900936b8c012332c421258b19bde86f323b789ef0799e97bdf42ace6f20d84b","CONTRIBUTING.md":"abde60a6650f212daa004ce5b5adf5c0b52077cf2c02c08812dc4a77ed134ee6",".cargo-checksum.json":"627dda4352daeb2fe2b02704ead96d775abc90e9840483928febb7556604f93e","inotify-sys.sublime-project":"5498a9ebcf1b0c6c64c712236f3a773701bd562ef7613262243d5e741090066a","Android.bp":"1e5a7d4f682b43a30c1cf224ba7cc0b204affc6b5bb88d509da700586687933a","src/lib.rs":"11a4c2c5787edf75eb2b91a3469d773cc22d371be73c2ea141609f1cca42cfe8","MODULE_LICENSE_ISC":"0d6f8afa3940b7f06bebee651376d43bc8b0d5b437337be2696d30377451e93a","Cargo.toml":"8ac94d7cc7431de5e968c9d67df060099e7067199ea588ab85c20be85def11c2","patches/license.patch":"7a28e361244a08c12bc9ad98f50e53eb25e66e1d736df49effd9ee796d28735a","README.md":"717c95de89105f0ced98121e6c2491889587241535b616532b4d37658d25474f"}}
\ No newline at end of file
diff --git a/crates/inotify-sys/.cargo-checksum.json b/crates/inotify-sys/.cargo-checksum.json
new file mode 100644
index 0000000..c7af574
--- /dev/null
+++ b/crates/inotify-sys/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"CHANGELOG.md":"fba948453a22944162900dd8e7641d30d5d13c63a67e1e0e0df59244eac8aeea","CONTRIBUTING.md":"5fcd431870e4baf184e1bcf10938e99b5444ecdc330a84a9fc2637d17126a704","Cargo.toml":"fa9d376c9ef55d626c847a41252009d41d42fcf5d2f8e384de13ae21e9c4f56a","README.md":"34d1f8bec64c143fc3a550b23f95dfaca20a907af9a04025c820ee8ebb36f53c","inotify-sys.sublime-project":"398b9b44c345cb381d036ce0aa7cb71fc87cc330f0d54452dff47a8ed7054b38","src/lib.rs":"5177fc90713e9351dc205b0a430baf3cdd639d0c2d996cfc02b6027d4c87dd80"},"package":"e74a1aa87c59aeff6ef2cc2fa62d41bc43f54952f55652656b18a02fd5e356c0"}
\ No newline at end of file
diff --git a/crates/inotify-sys/Android.bp b/crates/inotify-sys/Android.bp
new file mode 100644
index 0000000..de988b5
--- /dev/null
+++ b/crates/inotify-sys/Android.bp
@@ -0,0 +1,31 @@
+// 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-sys_license"],
+ default_team: "trendy_team_android_rust",
+}
+
+license {
+ name: "external_rust_crates_inotify-sys_license",
+ visibility: [":__subpackages__"],
+ license_kinds: ["SPDX-license-identifier-ISC"],
+ license_text: ["LICENSE"],
+}
+
+rust_library {
+ name: "libinotify_sys",
+ host_supported: true,
+ crate_name: "inotify_sys",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.3",
+ crate_root: "src/lib.rs",
+ edition: "2015",
+ rustlibs: ["liblibc"],
+ apex_available: [
+ "//apex_available:platform",
+ "//apex_available:anyapex",
+ ],
+ product_available: true,
+ vendor_available: true,
+}
diff --git a/crates/inotify-sys/CHANGELOG.md b/crates/inotify-sys/CHANGELOG.md
new file mode 100644
index 0000000..bdcd2c5
--- /dev/null
+++ b/crates/inotify-sys/CHANGELOG.md
@@ -0,0 +1,7 @@
+<a name="v0.1.3"></a>
+### v0.1.3 (2018-07-26)
+
+
+#### Bug Fixes
+
+* Use platform-specific constants from libc ([b363dff1](b363dff1))
diff --git a/crates/inotify-sys/CONTRIBUTING.md b/crates/inotify-sys/CONTRIBUTING.md
new file mode 100644
index 0000000..2c2e949
--- /dev/null
+++ b/crates/inotify-sys/CONTRIBUTING.md
@@ -0,0 +1,69 @@
+# 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.
+
+## Commit guidelines
+
+We use [clog] to generate a changelog for each release. This is done automatically, using the commit messages as a data source. Therefore it is very imporant to write clear commit messages and tag them in a way that the tool can understand.
+
+The rest of this section explains the rules for commit messages. Please don't be put off, if this seems overwhelming. As always, if you're unsure about anything, just send a pull request. [GitCop] and the reviewer will happily point out any problems.
+
+Before we go into the rules, here's an example of a commit message:
+```
+feat: Implement a feature
+
+This is the commit message body. It is optional and might consist of
+multiple paragraphs.
+
+Here's the message body's second paragraph. The next paragraph is going
+to automatically close an issue, once the commit is merged into the
+repository.
+
+Closes #123456.
+```
+
+First, let's start with the first line, the header. It's the most important part of the commit, as it's used by [clog] to generate the changelog. For that reason, it's the most heavily regulated part:
+- The header's purpose is to concisely summarize the changes made.
+- It must be **at most 50 characters** long.
+- It should be written in the **imperative voice**, as if you're commanding someone. So, "Add something", as opposed to "Adding something" or "Added something".
+- It must begin with the type of commit, followed by a colon (e.g. "feat:" or "fix:"). The following types can be used:
+ - **feat**: New functionality, or changes (not bug fixes) to existing functionality.
+ - **fix**: Bug fixes
+ - **docs**: Improvements to documentation
+ - **style**: Code formatting, indentation, etc.
+ - **refactor**: Changes to code that don't change what it does. Cleaning up, moving stuff around, etc.
+ - **perf**: Performance improvements
+ - **test**: Changes to test code
+ - **chore**: Custodial work that isn't directly related to the code. Changes to the build system, etc.
+
+These rules apply to the message body:
+- The messages body is optional, but should be added if the header and the commit diff by themselves don't explain why the commit is necessary.
+- It should **provide context** for the commit and **explain its reasoning**. It doesn't need to restate things that are already obvious from the commit diff.
+- Please be mindful of explanations of how the code works. Often, it makes more sense to add such explanations to the code itself, as comments.
+- The length limit for lines in the commit body is **72 characters**.
+- If any issues should be closed once the commit is merged, this can be done automatically by adding something like "Closes #123456" to the commit. Be careful about not doing this accidentally.
+
+That's it! If anything about this document is unclear, feel free to open an issue. If you have questions regarding a pull request that you're working on, just open the pull request and ask your questions there.
+
+[inotify]: https://github.com/inotify-rs/inotify
+[inotify-sys]: https://github.com/inotify-rs/inotify-sys
+[clog]: https://crates.io/crates/clog-cli
+[GitCop]: https://gitcop.com/
\ No newline at end of file
diff --git a/crates/inotify-sys/Cargo.toml b/crates/inotify-sys/Cargo.toml
new file mode 100644
index 0000000..47b87d8
--- /dev/null
+++ b/crates/inotify-sys/Cargo.toml
@@ -0,0 +1,36 @@
+# 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 believe there's an error in this file please file an
+# issue against the rust-lang/cargo repository. If you're
+# editing this file be aware that the upstream Cargo.toml
+# will likely look very different (and much more reasonable)
+
+[package]
+name = "inotify-sys"
+version = "0.1.3"
+authors = ["Hanno Braun <hb@hannobraun.de>"]
+description = "inotify bindings for the Rust programming language"
+documentation = "https://docs.rs/inotify-sys"
+readme = "README.md"
+keywords = ["inotify", "linux"]
+categories = ["external-ffi-bindings", "filesystem"]
+license = "ISC"
+repository = "https://github.com/inotify-rs/inotify-sys"
+[package.metadata.release]
+dev-version-ext = "dev"
+pre-release-commit-message = "chore: Release version {{version}}"
+pro-release-commit-message = "chore: Bump version to {{version}}"
+tag-message = "Version {{version}}"
+tag-prefix = "v"
+[dependencies.libc]
+version = "0.2"
+[badges.maintenance]
+status = "actively-developed"
+
+[badges.travis-ci]
+repository = "inotify-rs/inotify-sys"
diff --git a/crates/inotify-sys/LICENSE b/crates/inotify-sys/LICENSE
new file mode 100644
index 0000000..855ee51
--- /dev/null
+++ b/crates/inotify-sys/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/crates/inotify-sys/METADATA b/crates/inotify-sys/METADATA
new file mode 100644
index 0000000..f59ae29
--- /dev/null
+++ b/crates/inotify-sys/METADATA
@@ -0,0 +1,19 @@
+name: "inotify-sys"
+description:
+ "Low-level inotify bindings for the Rust programming language. This crate's "
+ "main purpose is to serve as the base for inotify-rs, the idiomatic Rust "
+ "wrapper around inotify. Unless you're sure you specifically need this "
+ "crate, please use inotify-rs instead."
+
+third_party {
+homepage: "https://crates.io/crates/inotify-sys"
+ identifier {
+ type: "Archive"
+ value: "https://static.crates.io/crates/inotify-sys/inotify-sys-0.1.3.crate"
+ primary_source: true
+ version: "0.1.3"
+ }
+ version: "0.1.3"
+ last_upgrade_date { year: 2024 month: 11 day: 12 }
+ license_type: NOTICE
+}
diff --git a/crates/inotify-sys/MODULE_LICENSE_ISC b/crates/inotify-sys/MODULE_LICENSE_ISC
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/crates/inotify-sys/MODULE_LICENSE_ISC
diff --git a/crates/inotify-sys/README.md b/crates/inotify-sys/README.md
new file mode 100644
index 0000000..97b466b
--- /dev/null
+++ b/crates/inotify-sys/README.md
@@ -0,0 +1,25 @@
+# inotify-sys [](https://crates.io/crates/inotify-sys) [](https://docs.rs/inotify-sys) [](https://travis-ci.org/inotify-rs/inotify-sys)
+
+Low-level [inotify] bindings for the [Rust programming language]. This crate's main purpose is to serve as the base for [inotify-rs], the idiomatic Rust wrapper around inotify. Unless you're sure you specifically need this crate, please use [inotify-rs] instead.
+
+
+## License
+
+Copyright (c) 2014-2017, 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/
+[inotify-rs]: https://crates.io/crates/inotify
diff --git a/crates/inotify-sys/cargo_embargo.json b/crates/inotify-sys/cargo_embargo.json
new file mode 100644
index 0000000..cb908d7
--- /dev/null
+++ b/crates/inotify-sys/cargo_embargo.json
@@ -0,0 +1,3 @@
+{
+ "run_cargo": false
+}
diff --git a/crates/inotify-sys/inotify-sys.sublime-project b/crates/inotify-sys/inotify-sys.sublime-project
new file mode 100644
index 0000000..077b3dd
--- /dev/null
+++ b/crates/inotify-sys/inotify-sys.sublime-project
@@ -0,0 +1,9 @@
+{
+ "folders":
+ [
+ {
+ "path": ".",
+ "file_exclude_patterns": [ "target/*" ]
+ }
+ ]
+}
diff --git a/crates/inotify-sys/patches/license.patch b/crates/inotify-sys/patches/license.patch
new file mode 100644
index 0000000..9ac55d0
--- /dev/null
+++ b/crates/inotify-sys/patches/license.patch
@@ -0,0 +1,19 @@
+diff --git b/LICENSE a/LICENSE
+new file mode 100644
+index 0000000..855ee51
+--- /dev/null
++++ a/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/crates/inotify-sys/src/lib.rs b/crates/inotify-sys/src/lib.rs
new file mode 100644
index 0000000..84924e6
--- /dev/null
+++ b/crates/inotify-sys/src/lib.rs
@@ -0,0 +1,713 @@
+#![deny(missing_docs)]
+#![deny(warnings)]
+
+
+//! # inotify bindings for the Rust programming language
+//!
+//! Please note that these are direct, low-level bindings to C functions that
+//! form the inotify C API. Unless you have a specific reason to use this crate,
+//! [inotify-rs], which is an idiomatic wrapper, is a much better choice.
+//!
+//! ## Usage
+//!
+//! In general, inotify usage follows the following pattern:
+//!
+//! 1. Create an inotify instance using [`inotify_init`] or [`inotify_init1`].
+//! 2. Manage watches with [`inotify_add_watch`] and [`inotify_rm_watch`].
+//! 3. Read event using [`read`].
+//! 4. Close the inotify instance using [`close`], once you're done.
+//!
+//! Please refer to the [inotify man page] and the rest of this documentation
+//! for full details.
+//!
+//! [inotify-rs]: https://crates.io/crates/inotify
+//! [`inotify_init`]: fn.inotify_init.html
+//! [`inotify_init1`]: fn.inotify_init1.html
+//! [`inotify_add_watch`]: fn.inotify_add_watch.html
+//! [`inotify_rm_watch`]: fn.inotify_rm_watch.html
+//! [`read`]: fn.read.html
+//! [`close`]: fn.close.html
+//! [inotify man page]: http://man7.org/linux/man-pages/man7/inotify.7.html
+
+
+extern crate libc;
+
+
+use libc::{
+ c_char,
+ c_int,
+ uint32_t };
+
+
+/// Set the `FD_CLOEXEC` flag for an inotify instance
+///
+/// Can be passed to [`inotify_init1`] to set the `FD_CLOEXEC` flag for the
+/// inotify instance. This changes the behavior of file descriptor when
+/// [execve(2)]'d. From [fcntl(2)]:
+///
+/// > If the FD_CLOEXEC bit is 0, the file descriptor will
+/// > remain open across an [execve(2)], otherwise it will be
+/// > closed.
+///
+/// See [open(2)] and [fcntl(2)] for details.
+///
+/// [`inotify_init1`]: fn.inotify_init1.html
+/// [execve(2)]: http://man7.org/linux/man-pages/man2/execve.2.html
+/// [open(2)]: http://man7.org/linux/man-pages/man2/open.2.html
+/// [fcntl(2)]: http://man7.org/linux/man-pages/man2/fcntl.2.html
+pub const IN_CLOEXEC: c_int = libc::O_CLOEXEC;
+
+/// Set an inotify instance to non-blocking mode
+///
+/// Can be passed to [`inotify_init1`] to set the `O_NONBLOCK` flag for the
+/// inotify instance.
+///
+/// See [open(2)] for details.
+///
+/// [`inotify_init1`]: fn.inotify_init1.html
+/// [open(2)]: http://man7.org/linux/man-pages/man2/open.2.html
+pub const IN_NONBLOCK: c_int = libc::O_NONBLOCK;
+
+/// Event: File was accessed
+///
+/// This constant can be passed to [`inotify_add_watch`], to register interest
+/// in this type of event, or it can be used to check (via [`inotify_event`]'s
+/// [`mask`] field) whether an event is of this type.
+///
+/// When monitoring a directory, this event will be triggered only for files
+/// within the directory.
+///
+/// See [man page] for additional details.
+///
+/// [`inotify_add_watch`]: fn.inotify_add_watch.html
+/// [`inotify_event`]: struct.inotify_event.html
+/// [`mask`]: struct.inotify_event.html#structfield.mask
+/// [man page]: http://man7.org/linux/man-pages/man7/inotify.7.html
+pub const IN_ACCESS: uint32_t = 0x00000001;
+
+/// Event: File was modified
+///
+/// This constant can be passed to [`inotify_add_watch`], to register interest
+/// in this type of event, or it can be used to check (via [`inotify_event`]'s
+/// [`mask`] field) whether an event is of this type.
+///
+/// When monitoring a directory, this event will be triggered only for files
+/// within the directory.
+///
+/// See [man page] for additional details.
+///
+/// [`inotify_add_watch`]: fn.inotify_add_watch.html
+/// [`inotify_event`]: struct.inotify_event.html
+/// [`mask`]: struct.inotify_event.html#structfield.mask
+/// [man page]: http://man7.org/linux/man-pages/man7/inotify.7.html
+pub const IN_MODIFY: uint32_t = 0x00000002;
+
+/// Event: Metadata was changed
+///
+/// This can include e.g.
+///
+/// - permissions, see [chmod(2)];
+/// - timestamps, see [utimensat(2)];
+/// - extended attributes, see [setxattr(2)];
+/// - link count, see [link(2)] and [unlink(2)];
+/// - user/group, see [chown(2)].
+///
+/// This constant can be passed to [`inotify_add_watch`], to register interest
+/// in this type of event, or it can be used to check (via [`inotify_event`]'s
+/// [`mask`] field) whether an event is of this type.
+///
+/// When monitoring a directory, this event can be triggered for both for the
+/// directory itself and the files within.
+///
+/// See [man page] for additional details.
+///
+/// [chmod(2)]: http://man7.org/linux/man-pages/man2/chmod.2.html
+/// [utimensat(2)]: http://man7.org/linux/man-pages/man2/utimensat.2.html
+/// [setxattr(2)]: http://man7.org/linux/man-pages/man2/fsetxattr.2.html
+/// [link(2)]: http://man7.org/linux/man-pages/man2/link.2.html
+/// [unlink(2)]: http://man7.org/linux/man-pages/man2/unlink.2.html
+/// [chown(2)]: http://man7.org/linux/man-pages/man2/chown.2.html
+/// [`inotify_add_watch`]: fn.inotify_add_watch.html
+/// [`inotify_event`]: struct.inotify_event.html
+/// [`mask`]: struct.inotify_event.html#structfield.mask
+/// [man page]: http://man7.org/linux/man-pages/man7/inotify.7.html
+pub const IN_ATTRIB: uint32_t = 0x00000004;
+
+/// Event: Writable file was closed
+///
+/// This constant can be passed to [`inotify_add_watch`], to register interest
+/// in this type of event, or it can be used to check (via [`inotify_event`]'s
+/// [`mask`] field) whether an event is of this type.
+///
+/// When monitoring a directory, this event will be triggered only for files
+/// within the directory.
+///
+/// See [man page] for additional details.
+///
+/// [`inotify_add_watch`]: fn.inotify_add_watch.html
+/// [`inotify_event`]: struct.inotify_event.html
+/// [`mask`]: struct.inotify_event.html#structfield.mask
+/// [man page]: http://man7.org/linux/man-pages/man7/inotify.7.html
+pub const IN_CLOSE_WRITE: uint32_t = 0x00000008;
+
+/// Event: Non-writable file or directory was closed
+///
+/// This constant can be passed to [`inotify_add_watch`], to register interest
+/// in this type of event, or it can be used to check (via [`inotify_event`]'s
+/// [`mask`] field) whether an event is of this type.
+///
+/// When monitoring a directory, this event can be triggered for both for the
+/// directory itself and the files within.
+///
+/// See [man page] for additional details.
+///
+/// [`inotify_add_watch`]: fn.inotify_add_watch.html
+/// [`inotify_event`]: struct.inotify_event.html
+/// [`mask`]: struct.inotify_event.html#structfield.mask
+/// [man page]: http://man7.org/linux/man-pages/man7/inotify.7.html
+pub const IN_CLOSE_NOWRITE: uint32_t = 0x00000010;
+
+/// Event: File or directory was opened
+///
+/// This constant can be passed to [`inotify_add_watch`], to register interest
+/// in this type of event, or it can be used to check (via [`inotify_event`]'s
+/// [`mask`] field) whether an event is of this type.
+///
+/// When monitoring a directory, this event can be triggered for both for the
+/// directory itself and the files within.
+///
+/// See [man page] for additional details.
+///
+/// [`inotify_add_watch`]: fn.inotify_add_watch.html
+/// [`inotify_event`]: struct.inotify_event.html
+/// [`mask`]: struct.inotify_event.html#structfield.mask
+/// [man page]: http://man7.org/linux/man-pages/man7/inotify.7.html
+pub const IN_OPEN: uint32_t = 0x00000020;
+
+/// Event: File or directory was moved out of watched directory
+///
+/// This constant can be passed to [`inotify_add_watch`], to register interest
+/// in this type of event, or it can be used to check (via [`inotify_event`]'s
+/// [`mask`] field) whether an event is of this type.
+///
+/// When monitoring a directory, this event will be triggered only for files
+/// within the directory.
+///
+/// [`inotify_add_watch`]: fn.inotify_add_watch.html
+/// [`inotify_event`]: struct.inotify_event.html
+/// [`mask`]: struct.inotify_event.html#structfield.mask
+/// [man page]: http://man7.org/linux/man-pages/man7/inotify.7.html
+pub const IN_MOVED_FROM: uint32_t = 0x00000040;
+
+/// Event: File or directory was moved into watched directory
+///
+/// This constant can be passed to [`inotify_add_watch`], to register interest
+/// in this type of event, or it can be used to check (via [`inotify_event`]'s
+/// [`mask`] field) whether an event is of this type.
+///
+/// When monitoring a directory, this event will be triggered only for files
+/// within the directory.
+///
+/// See [man page] for additional details.
+///
+/// [`inotify_add_watch`]: fn.inotify_add_watch.html
+/// [`inotify_event`]: struct.inotify_event.html
+/// [`mask`]: struct.inotify_event.html#structfield.mask
+/// [man page]: http://man7.org/linux/man-pages/man7/inotify.7.html
+pub const IN_MOVED_TO: uint32_t = 0x00000080;
+
+/// Event: File or directory was created in watched directory
+///
+/// This may also include hard links, symlinks, and UNIX sockets.
+///
+/// This constant can be passed to [`inotify_add_watch`], to register interest
+/// in this type of event, or it can be used to check (via [`inotify_event`]'s
+/// [`mask`] field) whether an event is of this type.
+///
+/// When monitoring a directory, this event will be triggered only for files
+/// within the directory.
+///
+/// See [man page] for additional details.
+///
+/// [`inotify_add_watch`]: fn.inotify_add_watch.html
+/// [`inotify_event`]: struct.inotify_event.html
+/// [`mask`]: struct.inotify_event.html#structfield.mask
+/// [man page]: http://man7.org/linux/man-pages/man7/inotify.7.html
+pub const IN_CREATE: uint32_t = 0x00000100;
+
+/// Event: File or directory in watched directory was deleted
+///
+/// This may also include hard links, symlinks, and UNIX sockets.
+///
+/// This constant can be passed to [`inotify_add_watch`], to register interest
+/// in this type of event, or it can be used to check (via [`inotify_event`]'s
+/// [`mask`] field) whether an event is of this type.
+///
+/// When monitoring a directory, this event will be triggered only for files
+/// within the directory.
+///
+/// See [man page] for additional details.
+///
+/// [`inotify_add_watch`]: fn.inotify_add_watch.html
+/// [`inotify_event`]: struct.inotify_event.html
+/// [`mask`]: struct.inotify_event.html#structfield.mask
+/// [man page]: http://man7.org/linux/man-pages/man7/inotify.7.html
+pub const IN_DELETE: uint32_t = 0x00000200;
+
+/// Event: Watched file or directory was deleted
+///
+/// This may also occur if the object is moved to another filesystem, since
+/// [mv(1)] in effect copies the file to the other filesystem and then deletes
+/// it from the original.
+///
+/// An IN_IGNORED event will subsequently be generated.
+///
+/// This constant can be passed to [`inotify_add_watch`], to register interest
+/// in this type of event, or it can be used to check (via [`inotify_event`]'s
+/// [`mask`] field) whether an event is of this type.
+///
+/// See [man page] for additional details.
+///
+/// [mv(1)]: http://man7.org/linux/man-pages/man1/mv.1.html
+/// [`inotify_add_watch`]: fn.inotify_add_watch.html
+/// [`inotify_event`]: struct.inotify_event.html
+/// [`mask`]: struct.inotify_event.html#structfield.mask
+/// [man page]: http://man7.org/linux/man-pages/man7/inotify.7.html
+pub const IN_DELETE_SELF: uint32_t = 0x00000400;
+
+/// Event: Watched file or directory was moved
+///
+/// This constant can be passed to [`inotify_add_watch`], to register interest
+/// in this type of event, or it can be used to check (via [`inotify_event`]'s
+/// [`mask`] field) whether an event is of this type.
+///
+/// See [man page] for additional details.
+///
+/// [`inotify_add_watch`]: fn.inotify_add_watch.html
+/// [`inotify_event`]: struct.inotify_event.html
+/// [`mask`]: struct.inotify_event.html#structfield.mask
+/// [man page]: http://man7.org/linux/man-pages/man7/inotify.7.html
+pub const IN_MOVE_SELF: uint32_t = 0x00000800;
+
+/// Event: File or directory within watched directory was moved
+///
+/// This is a combination of [`IN_MOVED_FROM`] and [`IN_MOVED_TO`].
+///
+/// This constant can be passed to [`inotify_add_watch`], to register interest
+/// in this type of event, or it can be used to check (via [`inotify_event`]'s
+/// [`mask`] field) whether an event is of this type.
+///
+/// See [man page] for additional details.
+///
+/// [`IN_MOVED_FROM`]: constant.IN_MOVED_FROM.html
+/// [`IN_MOVED_TO`]: constant.IN_MOVED_TO.html
+/// [`inotify_add_watch`]: fn.inotify_add_watch.html
+/// [`inotify_event`]: struct.inotify_event.html
+/// [`mask`]: struct.inotify_event.html#structfield.mask
+/// [man page]: http://man7.org/linux/man-pages/man7/inotify.7.html
+pub const IN_MOVE: uint32_t = (IN_MOVED_FROM | IN_MOVED_TO);
+
+/// Event: File was closed
+///
+/// This is a combination of [`IN_CLOSE_WRITE`] and [`IN_CLOSE_NOWRITE`].
+///
+/// This constant can be passed to [`inotify_add_watch`], to register interest
+/// in this type of event, or it can be used to check (via [`inotify_event`]'s
+/// [`mask`] field) whether an event is of this type.
+///
+/// See [man page] for additional details.
+///
+/// [`IN_CLOSE_WRITE`]: constant.IN_CLOSE_WRITE.html
+/// [`IN_CLOSE_NOWRITE`]: constant.IN_CLOSE_NOWRITE.html
+/// [`inotify_add_watch`]: fn.inotify_add_watch.html
+/// [`inotify_event`]: struct.inotify_event.html
+/// [`mask`]: struct.inotify_event.html#structfield.mask
+/// [man page]: http://man7.org/linux/man-pages/man7/inotify.7.html
+pub const IN_CLOSE: uint32_t = (IN_CLOSE_WRITE | IN_CLOSE_NOWRITE);
+
+/// Event: Any event occured
+///
+/// This is a combination of all the other event constants:
+///
+/// - [`IN_ACCESS`]
+/// - [`IN_ATTRIB`]
+/// - [`IN_CLOSE_WRITE`]
+/// - [`IN_CLOSE_NOWRITE`]
+/// - [`IN_MODIFY`]
+/// - [`IN_CREATE`]
+/// - [`IN_DELETE`]
+/// - [`IN_DELETE_SELF`]
+/// - [`IN_MODIFY`]
+/// - [`IN_MOVE_SELF`]
+/// - [`IN_MOVED_FROM`]
+/// - [`IN_MOVED_TO`]
+/// - [`IN_OPEN`]
+///
+/// This constant can be passed to [`inotify_add_watch`], to register interest
+/// in any type of event.
+///
+/// See [man page] for additional details.
+///
+/// [`IN_ACCESS`]: constant.IN_ACCESS.html
+/// [`IN_ATTRIB`]: constant.IN_ATTRIB.html
+/// [`IN_CLOSE_WRITE`]: constant.IN_CLOSE_WRITE.html
+/// [`IN_CLOSE_NOWRITE`]: constant.IN_CLOSE_NOWRITE.html
+/// [`IN_MODIFY`]: constant.IN_MODIFY.html
+/// [`IN_CREATE`]: constant.IN_CREATE.html
+/// [`IN_DELETE`]: constant.IN_DELETE.html
+/// [`IN_DELETE_SELF`]: constant.IN_DELETE_SELF.html
+/// [`IN_MODIFY`]: constant.IN_MODIFY.html
+/// [`IN_MOVE_SELF`]: constant.IN_MOVE_SELF.html
+/// [`IN_MOVED_FROM`]: constant.IN_MOVED_FROM.html
+/// [`IN_MOVED_TO`]: constant.IN_MOVED_TO.html
+/// [`IN_OPEN`]: constant.IN_OPEN.html
+/// [`inotify_add_watch`]: fn.inotify_add_watch.html
+/// [man page]: http://man7.org/linux/man-pages/man7/inotify.7.html
+pub const IN_ALL_EVENTS: uint32_t = (
+ IN_ACCESS | IN_MODIFY | IN_ATTRIB | IN_CLOSE_WRITE | IN_CLOSE_NOWRITE
+ | IN_OPEN | IN_MOVED_FROM | IN_MOVED_TO | IN_CREATE | IN_DELETE
+ | IN_DELETE_SELF | IN_MOVE_SELF);
+
+/// Only watch path, if it is a directory
+///
+/// This bit can be set in [`inotify_add_watch`]'s `mask` parameter, to
+/// configure the watch.
+///
+/// See [man page] for additional details.
+///
+/// [`inotify_add_watch`]: fn.inotify_add_watch.html
+/// [man page]: http://man7.org/linux/man-pages/man7/inotify.7.html
+pub const IN_ONLYDIR: uint32_t = 0x01000000;
+
+/// Don't dereference path, if it is a symbolic link
+///
+/// This bit can be set in [`inotify_add_watch`]'s `mask` parameter, to
+/// configure the watch.
+///
+/// See [man page] for additional details.
+///
+/// [`inotify_add_watch`]: fn.inotify_add_watch.html
+/// [man page]: http://man7.org/linux/man-pages/man7/inotify.7.html
+pub const IN_DONT_FOLLOW: uint32_t = 0x02000000;
+
+/// Ignore events for children, that have been unlinked from watched directory
+///
+/// This bit can be set in [`inotify_add_watch`]'s `mask` parameter, to
+/// configure the watch.
+///
+/// See [man page] for additional details.
+///
+/// [`inotify_add_watch`]: fn.inotify_add_watch.html
+/// [man page]: http://man7.org/linux/man-pages/man7/inotify.7.html
+pub const IN_EXCL_UNLINK: uint32_t = 0x04000000;
+
+/// Update existing watch mask, instead of replacing it
+///
+/// This bit can be set in [`inotify_add_watch`]'s `mask` parameter, to
+/// configure the watch.
+///
+/// See [man page] for additional details.
+///
+/// [`inotify_add_watch`]: fn.inotify_add_watch.html
+/// [man page]: http://man7.org/linux/man-pages/man7/inotify.7.html
+pub const IN_MASK_ADD: uint32_t = 0x20000000;
+
+/// Remove watch after one event
+///
+/// This bit can be set in [`inotify_add_watch`]'s `mask` parameter, to
+/// configure the watch.
+///
+/// See [man page] for additional details.
+///
+/// [`inotify_add_watch`]: fn.inotify_add_watch.html
+/// [man page]: http://man7.org/linux/man-pages/man7/inotify.7.html
+pub const IN_ONESHOT: uint32_t = 0x80000000;
+
+/// Indicates that the subject of an event is a directory
+///
+/// This constant can be used to check against the [`mask`] field in
+/// [`inotify_event`].
+///
+/// See [man page] for additional details.
+///
+/// [`mask`]: struct.inotify_event.html#structfield.mask
+/// [`inotify_event`]: struct.inotify_event.html
+/// [man page]: http://man7.org/linux/man-pages/man7/inotify.7.html
+pub const IN_ISDIR: uint32_t = 0x40000000;
+
+/// Indicates that file system containing a watched object has been unmounted
+///
+/// An [`IN_IGNORED`] event will be generated subsequently.
+///
+/// This constant can be used to check against the [`mask`] field in
+/// [`inotify_event`].
+///
+/// See [man page] for additional details.
+///
+/// [`IN_IGNORED`]: constant.IN_IGNORED.html
+/// [`mask`]: struct.inotify_event.html#structfield.mask
+/// [`inotify_event`]: struct.inotify_event.html
+/// [man page]: http://man7.org/linux/man-pages/man7/inotify.7.html
+pub const IN_UNMOUNT: uint32_t = 0x00002000;
+
+/// Indicates that the event queue has overflowed
+///
+/// This constant can be used to check against the [`mask`] field in
+/// [`inotify_event`].
+///
+/// See [man page] for additional details.
+///
+/// [`mask`]: struct.inotify_event.html#structfield.mask
+/// [`inotify_event`]: struct.inotify_event.html
+/// [man page]: http://man7.org/linux/man-pages/man7/inotify.7.html
+pub const IN_Q_OVERFLOW: uint32_t = 0x00004000;
+
+/// Indicates that a file system watch was removed
+///
+/// This can occur as a result of [`inotify_rm_watch`], because a watched item
+/// was deleted, the containing filesystem was unmounted, or after a
+/// [`IN_ONESHOT`] watch is complete.
+///
+/// This constant can be used to check against the [`mask`] field in
+/// [`inotify_event`].
+///
+/// See [man page] for additional details.
+///
+/// [`inotify_rm_watch`]: fn.inotify_rm_watch.html
+/// [`IN_ONESHOT`]: constant.IN_ONESHOT.html
+/// [`mask`]: struct.inotify_event.html#structfield.mask
+/// [`inotify_event`]: struct.inotify_event.html
+/// [man page]: http://man7.org/linux/man-pages/man7/inotify.7.html
+pub const IN_IGNORED: uint32_t = 0x00008000;
+
+
+/// Describes a file system event
+///
+/// From [inotify(7)]:
+///
+/// > To determine what events have occurred, an application [read(2)]s
+/// > from the inotify file descriptor. If no events have so far occurred,
+/// > then, assuming a blocking file descriptor, [read(2)] will block until
+/// > at least one event occurs (unless interrupted by a signal, in which
+/// > case the call fails with the error EINTR; see [signal(7)]).
+/// >
+/// > Each successful [read(2)] returns a buffer containing one or more of
+/// > this structure.
+///
+/// [inotify(7)]: http://man7.org/linux/man-pages/man7/inotify.7.html
+/// [read(2)]: http://man7.org/linux/man-pages/man2/read.2.html
+/// [signal(7)]: http://man7.org/linux/man-pages/man7/signal.7.html
+#[allow(non_camel_case_types)]
+#[derive(Clone, Copy, Debug)]
+#[repr(C)]
+pub struct inotify_event {
+ /// Identifies the watch for which this event occurs
+ ///
+ /// This is one of the watch descriptors returned by a previous call to
+ /// [`inotify_add_watch()`].
+ ///
+ /// [`inotify_add_watch()`]: fn.inotify_add_watch.html
+ pub wd: c_int,
+
+ /// Describes the type file system event
+ ///
+ /// One of the following bits will be set, to identify the type of event:
+ ///
+ /// - [`IN_ACCESS`]
+ /// - [`IN_ATTRIB`]
+ /// - [`IN_CLOSE_NOWRITE`]
+ /// - [`IN_CLOSE_WRITE`]
+ /// - [`IN_CREATE`]
+ /// - [`IN_DELETE`]
+ /// - [`IN_DELETE_SELF`]
+ /// - [`IN_IGNORED`]
+ /// - [`IN_MODIFY`]
+ /// - [`IN_MOVED_FROM`]
+ /// - [`IN_MOVED_TO`]
+ /// - [`IN_MOVE_SELF`]
+ /// - [`IN_OPEN`]
+ /// - [`IN_Q_OVERFLOW`]
+ /// - [`IN_UNMOUNT`]
+ ///
+ /// Some constants cover multiple bits, and can be used for a less precise
+ /// check of the event type:
+ ///
+ /// - [`IN_CLOSE`]
+ /// - [`IN_MOVE`]
+ ///
+ /// In addition, the [`IN_ISDIR`] bit can be set.
+ ///
+ /// [`IN_ACCESS`]: constant.IN_ACCESS.html
+ /// [`IN_ATTRIB`]: constant.IN_ATTRIB.html
+ /// [`IN_CLOSE`]: constant.IN_CLOSE.html
+ /// [`IN_CLOSE_NOWRITE`]: constant.IN_CLOSE_NOWRITE.html
+ /// [`IN_CLOSE_WRITE`]: constant.IN_CLOSE_WRITE.html
+ /// [`IN_CREATE`]: constant.IN_CREATE.html
+ /// [`IN_DELETE`]: constant.IN_DELETE.html
+ /// [`IN_DELETE_SELF`]: constant.IN_DELETE_SELF.html
+ /// [`IN_IGNORED`]: constant.IN_IGNORED.html
+ /// [`IN_ISDIR`]: constant.IN_ISDIR.html
+ /// [`IN_MODIFY`]: constant.IN_MODIFY.html
+ /// [`IN_MOVE`]: constant.IN_MOVE.html
+ /// [`IN_MOVED_FROM`]: constant.IN_MOVED_FROM.html
+ /// [`IN_MOVED_TO`]: constant.IN_MOVED_TO.html
+ /// [`IN_MOVE_SELF`]: constant.IN_MOVE_SELF.html
+ /// [`IN_OPEN`]: constant.IN_OPEN.html
+ /// [`IN_Q_OVERFLOW`]: constant.IN_Q_OVERFLOW.html
+ /// [`IN_UNMOUNT`]: constant.IN_UNMOUNT.html
+ pub mask: uint32_t,
+
+ /// A number that connects related events
+ ///
+ /// Currently used only for rename events. A related pair of
+ /// [`IN_MOVED_FROM`] and [`IN_MOVED_TO`] events will have the same,
+ /// non-zero, cookie. For all other events, cookie is 0.
+ ///
+ /// [`IN_MOVED_FROM`]: constant.IN_MOVED_FROM.html
+ /// [`IN_MOVED_TO`]: constant.IN_MOVED_TO.html
+ pub cookie: uint32_t,
+
+ /// The length of `name`
+ ///
+ /// Used to determine the size of this structure. When `name`
+ /// isn't present (`name` is only present when an event occurs
+ /// for a file inside a watched directory), it is 0. When `name`
+ /// *is* present, it counts all of `name`'s bytes, including `\0`.
+ ///
+ /// > The `name` field is present only when an event is returned for
+ /// > a file inside a watched directory; it identifies the file
+ /// > pathname relative to the watched directory. This pathname is
+ /// > null-terminated, and may include further null bytes ('\0') to
+ /// > align subsequent reads to a suitable address boundary.
+ ///
+ /// The `name` field has been ommited in this struct's definition.
+ pub len: uint32_t,
+}
+
+
+extern {
+ /// Creates an inotify instance
+ ///
+ /// If you need more flexibility, consider using [`inotify_init1`] instead.
+ ///
+ /// Returns `-1`, if an error occured, or an inotify file descriptor
+ /// otherwise.
+ ///
+ /// Please refer to the [man page] for additional details.
+ ///
+ /// [`inotify_init1`]: fn.inotify_init1.html
+ /// [man page]: http://man7.org/linux/man-pages/man2/inotify_init.2.html
+ pub fn inotify_init() -> c_int;
+
+ /// Creates an inotify instance
+ ///
+ /// Takes an argument to configure the new inotify instance. The following
+ /// flags can be set:
+ ///
+ /// - [`IN_CLOEXEC`]
+ /// - [`IN_NONBLOCK`]
+ ///
+ /// Returns `-1`, if an error occured, or an inotify file descriptor
+ /// otherwise.
+ ///
+ /// Please refer to the [man page] for additional details.
+ ///
+ /// [`IN_CLOEXEC`]: constant.IN_CLOEXEC.html
+ /// [`IN_NONBLOCK`]: constant.IN_NONBLOCK.html
+ /// [man page]: http://man7.org/linux/man-pages/man2/inotify_init1.2.html
+ pub fn inotify_init1(flags: c_int) -> c_int;
+
+ /// Adds or updates an inotify watch
+ ///
+ /// Adds an item to the watch list of an inotify instance, or modifies an
+ /// item on that list. This function takes the following arguments:
+ ///
+ /// - `fd` is the file descriptor of the inotify instance (created by
+ /// [`inotify_init`] or [`inotify_init1`])
+ /// - `pathname` is the path of the file or directory watch
+ /// - `mask` defines the behavior of this function and configures the watch
+ ///
+ /// The following flags in `mask` control the type of events to watch for:
+ ///
+ /// - [`IN_ACCESS`]
+ /// - [`IN_ATTRIB`]
+ /// - [`IN_CLOSE_NOWRITE`]
+ /// - [`IN_CLOSE_WRITE`]
+ /// - [`IN_CREATE`]
+ /// - [`IN_DELETE`]
+ /// - [`IN_DELETE_SELF`]
+ /// - [`IN_MODIFY`]
+ /// - [`IN_MOVED_FROM`]
+ /// - [`IN_MOVED_TO`]
+ /// - [`IN_MOVE_SELF`]
+ /// - [`IN_OPEN`]
+ ///
+ /// The following constants can be used as shortcuts to set multiple event
+ /// flags:
+ ///
+ /// - [`IN_ALL_EVENTS`]
+ /// - [`IN_CLOSE`]
+ /// - [`IN_MOVE`]
+ ///
+ /// In addition, the following flags can be set to control the behaviors of
+ /// the watch and this function:
+ ///
+ /// - [`IN_DONT_FOLLOW`]
+ /// - [`IN_EXCL_UNLINK`]
+ /// - [`IN_MASK_ADD`]
+ /// - [`IN_ONESHOT`]
+ /// - [`IN_ONLYDIR`]
+ ///
+ /// The function returns `-1` if an error occured. Otherwise, it returns a
+ /// watch descriptor that can be used to remove the watch using
+ /// [`inotify_rm_watch`] or identify the watch via [`inotify_event`]'s [wd`]
+ /// field.
+ ///
+ /// Please refer to the [man page] for additional details.
+ ///
+ /// [`inotify_init`]: fn.inotify_init.html
+ /// [`inotify_init1`]: fn.inotify_init1.html
+ /// [`IN_ACCESS`]: constant.IN_ACCESS.html
+ /// [`IN_ATTRIB`]: constant.IN_ATTRIB.html
+ /// [`IN_CLOSE_NOWRITE`]: constant.IN_CLOSE_NOWRITE.html
+ /// [`IN_CLOSE_WRITE`]: constant.IN_CLOSE_WRITE.html
+ /// [`IN_CREATE`]: constant.IN_CREATE.html
+ /// [`IN_DELETE`]: constant.IN_DELETE.html
+ /// [`IN_DELETE_SELF`]: constant.IN_DELETE_SELF.html
+ /// [`IN_MODIFY`]: constant.IN_MODIFY.html
+ /// [`IN_MOVED_FROM`]: constant.IN_MOVED_FROM.html
+ /// [`IN_MOVED_TO`]: constant.IN_MOVED_TO.html
+ /// [`IN_MOVE_SELF`]: constant.IN_MOVE_SELF.html
+ /// [`IN_OPEN`]: constant.IN_OPEN.html
+ /// [`IN_ALL_EVENTS`]: constant.IN_ALL_EVENTS.html
+ /// [`IN_CLOSE`]: constant.IN_CLOSE.html
+ /// [`IN_MOVE`]: constant.IN_MOVE.html
+ /// [`IN_DONT_FOLLOW`]: constant.IN_DONT_FOLLOW.html
+ /// [`IN_EXCL_UNLINK`]: constant.IN_EXCL_UNLINK.html
+ /// [`IN_MASK_ADD`]: constant.IN_MASK_ADD.html
+ /// [`IN_ONESHOT`]: constant.IN_ONESHOT.html
+ /// [`IN_ONLYDIR`]: constant.IN_ONLYDIR.html
+ /// [`inotify_rm_watch`]: fn.inotify_rm_watch.html
+ /// [`inotify_event`]: struct.inotify_event.html
+ /// [`wd`]: struct.inotify_event.html#structfield.wd
+ /// [man page]: http://man7.org/linux/man-pages/man2/inotify_add_watch.2.html
+ pub fn inotify_add_watch(fd: c_int, pathname: *const c_char, mask: uint32_t) -> c_int;
+
+ /// Removes an inotify watch
+ ///
+ /// Removes an item from the watch list of an inotify instance. The inotify
+ /// instance is identified by the `fd` argument. The watch is identified by
+ /// the `wd` argument.
+ ///
+ /// Returns `0` on success, `-1` on failure.
+ ///
+ /// Please refer to the [man page] for additional details.
+ ///
+ /// [man page]: http://man7.org/linux/man-pages/man2/inotify_rm_watch.2.html
+ pub fn inotify_rm_watch(fd: c_int, wd: c_int) -> c_int;
+}
+
+pub use libc::{
+ close,
+ read,
+};
diff --git a/crates/inotify/.android-checksum.json b/crates/inotify/.android-checksum.json
new file mode 100644
index 0000000..7060da1
--- /dev/null
+++ b/crates/inotify/.android-checksum.json
@@ -0,0 +1 @@
+{"package":null,"files":{"src/fd_guard.rs":"1332a0d16207474a81e8e294f2a76e6ff6939f55f6111f6a6ee7e9385d3cc2e1","src/lib.rs":"6cb02efc6fcfbb91bf3029f8a03e685ab66139f6fade7837f49eab7411cdb04a","src/watches.rs":"8033907cbc312ba322e2936dd91876249c9e4749936007b90b505da3130b176c","src/inotify.rs":"a0adfee86e9a7d8f00d1f245ce47c37e4df0e465a7683dfce16983e409273e88","src/util.rs":"a64770266cffdad1ec275b331677e3da18ac206820a7682c2b140e0fbab412bc","Cargo.toml":"1cf43ba6b2af411a443e6993a852f59ab6398b67937e880c85b18418ae003850","Android.bp":"791845197a6ab656064a71eb1ca5a1eea1c160885d348b45fa07c84b0def118a",".cargo-checksum.json":"0d9706ef5d373938da2aa7d4614d1f4dcf9ff6e2371ead9c58d28441687f8618","README.md":"125060d459975be258596f1df8386a9f4b4fbcbecbddaebeadbf634e2090de51","LICENSE":"827af61e671db65655482be1fe90882b836403fdec06746b03bdd935c869c71a","tests/main.rs":"268d6edfe319dab75b0bc54908995f22f3ebe2becd63d9ba076972b501c24df1","METADATA":"e226bcee9f6cdc3891cb4f8840430823d1e55e5f65b2003f4746c60a35dc34a8","examples/watch.rs":"c833b8f7951536c4e5d5643494487cc6b547472faa8d9851a5fc56b905301adc","src/stream.rs":"b1e929261395ab0b81efe7a7e70881333b32c4e26872a9090a6c8743bf63da92","cargo_embargo.json":"84459a3d28b2de537000617d709682791207b299a562af849851183686bc30fe","examples/stream.rs":"57246cc24a83cc6f57bd3be52eaeab2fa799d5006970f1312ebb937ceabc810d","CHANGELOG.md":"6741d951a7b741c56d109f35cd5268006984aa429ff173b278368a8f223b3603","src/events.rs":"04d951288983fd669fb0a564c80e4c9020d6597ffadf61e85a185057aaf495ce","CONTRIBUTING.md":"903615a28d3cd291969fd64f862320104a0d3b9a195232b842ac43b36fd4db3e","Cargo.lock":"66cc74d61ad532389ddd4d973d81aeb058f7d14aaafd3b79b4ef5a4c0bfe4e68"}}
\ No newline at end of file
diff --git a/crates/inotify/.cargo-checksum.json b/crates/inotify/.cargo-checksum.json
new file mode 100644
index 0000000..211570e
--- /dev/null
+++ b/crates/inotify/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"CHANGELOG.md":"f8c7001007e651c773a6a46a03c4430ffc55306034590454f004d55655e5d318","CONTRIBUTING.md":"63ec0b71cb5acafb22a3edbd1232d8886ed531544b241d956ca284e4163a05fc","Cargo.lock":"41a7032f0819fd33fbfaad1a69f5d0d1c4505a0c0f3fe5e5bcae46b3bd1ba780","Cargo.toml":"f18445d4de18f79b526547c85b31d2684cf9d24fb1ab7b0bc8446a9469a9fddd","LICENSE":"7f78b31de4e7a3e2ac8de40e2b409eda313f03637aa62b8416fbde9bdc5ce4c9","README.md":"c1737860060c52b18aec28dedccb38b6f00f798387bc69c2137859a0d77e001e","examples/stream.rs":"3d81838f02e16ceef93a5529dcd37b90dbe83ea5195f6d373972cc5fd9bba770","examples/watch.rs":"3b437c8f8c3813167d41d11183198bb26a21d15485a6abf1d3fab86faeb35cd6","src/events.rs":"62fea3423632462d30f7164c4c2238e26d3989b0e4d7e8cba16391ff8aee48cf","src/fd_guard.rs":"3d9dd97d24e4da26ebbc2a467007ae663bc662037ec5e674368e3dd914a2d084","src/inotify.rs":"c7f2f57cbad8c7c091cd8f85e6dda12bac8d05efb86c5e3ae0cd8f001b727ea7","src/lib.rs":"5a9be80193fc384285a18aab9a5de4650a001bda84a314b97cacbe8dcaf4375b","src/stream.rs":"0a507fa68413078f4223a77a04d1cfdf6d4c1af0133b7a87675d0a9f999595c8","src/util.rs":"5cd2db91ba6a406995dace2b55fb7cafed4131ee8e2998943aef8e78144d3f1f","src/watches.rs":"04b1ffcdaa7d782afdc8faa413aff6bd1a824d76182c8552f758a2f40c3dd009","tests/main.rs":"6d70e9c278823db5c5a293bc973c9fb04de0605966c23be03aaf1d97db8e45e4"},"package":"f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3"}
\ No newline at end of file
diff --git a/crates/inotify/Android.bp b/crates/inotify/Android.bp
new file mode 100644
index 0000000..61f9b03
--- /dev/null
+++ b/crates/inotify/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/crates/inotify/CHANGELOG.md b/crates/inotify/CHANGELOG.md
new file mode 100644
index 0000000..710d1d9
--- /dev/null
+++ b/crates/inotify/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/crates/inotify/CONTRIBUTING.md b/crates/inotify/CONTRIBUTING.md
new file mode 100644
index 0000000..73da607
--- /dev/null
+++ b/crates/inotify/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/crates/inotify/Cargo.lock b/crates/inotify/Cargo.lock
new file mode 100644
index 0000000..69ded48
--- /dev/null
+++ b/crates/inotify/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/crates/inotify/Cargo.toml b/crates/inotify/Cargo.toml
new file mode 100644
index 0000000..551b475
--- /dev/null
+++ b/crates/inotify/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 <mail@hannobraun.de>",
+ "Félix Saparelli <me@passcod.name>",
+ "Cristian Kubis <cristian.kubis@tsunix.de>",
+ "Frank Denis <github@pureftpd.org>",
+]
+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/crates/inotify/LICENSE b/crates/inotify/LICENSE
new file mode 100644
index 0000000..f201411
--- /dev/null
+++ b/crates/inotify/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/crates/inotify/METADATA b/crates/inotify/METADATA
new file mode 100644
index 0000000..a9386db
--- /dev/null
+++ b/crates/inotify/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/crates/inotify/README.md b/crates/inotify/README.md
new file mode 100644
index 0000000..e5ecd52
--- /dev/null
+++ b/crates/inotify/README.md
@@ -0,0 +1,116 @@
+# inotify-rs [](https://crates.io/crates/inotify) [](https://docs.rs/inotify) [](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/crates/inotify/cargo_embargo.json b/crates/inotify/cargo_embargo.json
new file mode 100644
index 0000000..c8842d1
--- /dev/null
+++ b/crates/inotify/cargo_embargo.json
@@ -0,0 +1,4 @@
+{
+ "run_cargo": false,
+ "tests": true
+}
diff --git a/crates/inotify/examples/stream.rs b/crates/inotify/examples/stream.rs
new file mode 100644
index 0000000..ca2282f
--- /dev/null
+++ b/crates/inotify/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/crates/inotify/examples/watch.rs b/crates/inotify/examples/watch.rs
new file mode 100644
index 0000000..8bdeb7b
--- /dev/null
+++ b/crates/inotify/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/crates/inotify/src/events.rs b/crates/inotify/src/events.rs
new file mode 100644
index 0000000..88473f3
--- /dev/null
+++ b/crates/inotify/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/crates/inotify/src/fd_guard.rs b/crates/inotify/src/fd_guard.rs
new file mode 100644
index 0000000..dfadfcd
--- /dev/null
+++ b/crates/inotify/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/crates/inotify/src/inotify.rs b/crates/inotify/src/inotify.rs
new file mode 100644
index 0000000..0b1080f
--- /dev/null
+++ b/crates/inotify/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/crates/inotify/src/lib.rs b/crates/inotify/src/lib.rs
new file mode 100644
index 0000000..e26e339
--- /dev/null
+++ b/crates/inotify/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/crates/inotify/src/stream.rs b/crates/inotify/src/stream.rs
new file mode 100644
index 0000000..7bbf434
--- /dev/null
+++ b/crates/inotify/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/crates/inotify/src/util.rs b/crates/inotify/src/util.rs
new file mode 100644
index 0000000..28376bc
--- /dev/null
+++ b/crates/inotify/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/crates/inotify/src/watches.rs b/crates/inotify/src/watches.rs
new file mode 100644
index 0000000..2f0e66f
--- /dev/null
+++ b/crates/inotify/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/crates/inotify/tests/main.rs b/crates/inotify/tests/main.rs
new file mode 100644
index 0000000..4f9633c
--- /dev/null
+++ b/crates/inotify/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)
+ );
+}
diff --git a/crates/maplit/.android-checksum.json b/crates/maplit/.android-checksum.json
new file mode 100644
index 0000000..0f54e86
--- /dev/null
+++ b/crates/maplit/.android-checksum.json
@@ -0,0 +1 @@
+{"package":null,"files":{"LICENSE-APACHE":"3c7cd2396b5b772507febd2615d3d5a55b80103845037df77c87ba6e64872f2c","Cargo.toml":"9ead68a86ae6a94a698d67d83ee7c1161be6cbc031d5f8489e25816e6493337b","LICENSE":"3c7cd2396b5b772507febd2615d3d5a55b80103845037df77c87ba6e64872f2c","LICENSE-MIT":"25045647f8514b43237cf0efdc4ba7dc153f8793e19c9e4b4a65af7e1b78cb5b","cargo_embargo.json":"d87209d4c367ff47dd3b7c46cebe3557f0596daaaae987d18ea162cb31e92b6c","README.rst":"67555a05bc2fdfcf303ca098d68dd0f12cee4582f35a6ddcfd4ff10632759b4a","MODULE_LICENSE_APACHE2":"0d6f8afa3940b7f06bebee651376d43bc8b0d5b437337be2696d30377451e93a","METADATA":"842f972a407701f4a9c09f52915c0de3d51b1b6fc8fe1831f136186d15130878","tests/tests.rs":"d8716b250cae0d9869c4d979f532dfc59850fc9e8ef26661ac18b7c8980116f6",".cargo-checksum.json":"57d75b1f8b4d6f04ae6bbfe06c9dca420b7767c7ddfc12baa9909f6828aa6c6d","src/lib.rs":"d05a41c93137cd8128cfe5e6eb97b0395be8898a7733f78eed36cb17e0f236ce","Android.bp":"6b7d1d4e48627450dfc0b6cfae1bb62cbedb6ad9edd97113fadbe69f5d584325"}}
\ No newline at end of file
diff --git a/crates/maplit/.cargo-checksum.json b/crates/maplit/.cargo-checksum.json
new file mode 100644
index 0000000..bf057d1
--- /dev/null
+++ b/crates/maplit/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"4281e55a6ade6ea883936f2325599d7050c823118a641bb549cf0d47bf4d861e","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"7576269ea71f767b99297934c0b2367532690f8c4badc695edf8e04ab6a1e545","README.rst":"a3896d941360244037c0e1bf82e6f8def2ddaea13381e7165b26cbf9502a311c","src/lib.rs":"20592121c8ee5365ba967e1c09c46d7f09c247b0a7f7d7ad7066e262b70f1af2","tests/tests.rs":"f7fc8b9ed33f3b14eee34cacbb99d1f435666b52445eb7b41e5bde8dffae759d"},"package":"3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"}
\ No newline at end of file
diff --git a/crates/maplit/Android.bp b/crates/maplit/Android.bp
new file mode 100644
index 0000000..288e377
--- /dev/null
+++ b/crates/maplit/Android.bp
@@ -0,0 +1,61 @@
+// 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_maplit_license"],
+ default_team: "trendy_team_android_rust",
+}
+
+license {
+ name: "external_rust_crates_maplit_license",
+ visibility: [":__subpackages__"],
+ license_kinds: ["SPDX-license-identifier-Apache-2.0"],
+ license_text: ["LICENSE"],
+}
+
+rust_library {
+ name: "libmaplit",
+ host_supported: true,
+ crate_name: "maplit",
+ cargo_env_compat: true,
+ cargo_pkg_version: "1.0.2",
+ crate_root: "src/lib.rs",
+ edition: "2015",
+ apex_available: [
+ "//apex_available:platform",
+ "//apex_available:anyapex",
+ ],
+ product_available: true,
+ vendor_available: true,
+}
+
+rust_test {
+ name: "maplit_test_src_lib",
+ host_supported: true,
+ crate_name: "maplit",
+ cargo_env_compat: true,
+ cargo_pkg_version: "1.0.2",
+ crate_root: "src/lib.rs",
+ test_suites: ["general-tests"],
+ auto_gen_config: true,
+ test_options: {
+ unit_test: true,
+ },
+ edition: "2015",
+}
+
+rust_test {
+ name: "maplit_test_tests_tests",
+ host_supported: true,
+ crate_name: "tests",
+ cargo_env_compat: true,
+ cargo_pkg_version: "1.0.2",
+ crate_root: "tests/tests.rs",
+ test_suites: ["general-tests"],
+ auto_gen_config: true,
+ test_options: {
+ unit_test: true,
+ },
+ edition: "2015",
+ rustlibs: ["libmaplit"],
+}
diff --git a/crates/maplit/Cargo.toml b/crates/maplit/Cargo.toml
new file mode 100644
index 0000000..30bf906
--- /dev/null
+++ b/crates/maplit/Cargo.toml
@@ -0,0 +1,24 @@
+# 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 believe there's an error in this file please file an
+# issue against the rust-lang/cargo repository. If you're
+# editing this file be aware that the upstream Cargo.toml
+# will likely look very different (and much more reasonable)
+
+[package]
+name = "maplit"
+version = "1.0.2"
+authors = ["bluss"]
+description = "Collection “literal” macros for HashMap, HashSet, BTreeMap, and BTreeSet."
+documentation = "https://docs.rs/maplit/"
+keywords = ["literal", "data-structure", "hashmap", "macro"]
+categories = ["rust-patterns"]
+license = "MIT/Apache-2.0"
+repository = "https://github.com/bluss/maplit"
+[package.metadata.release]
+no-dev-version = true
diff --git a/crates/maplit/LICENSE b/crates/maplit/LICENSE
new file mode 120000
index 0000000..6b579aa
--- /dev/null
+++ b/crates/maplit/LICENSE
@@ -0,0 +1 @@
+LICENSE-APACHE
\ No newline at end of file
diff --git a/crates/maplit/LICENSE-APACHE b/crates/maplit/LICENSE-APACHE
new file mode 100644
index 0000000..16fe87b
--- /dev/null
+++ b/crates/maplit/LICENSE-APACHE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/crates/maplit/LICENSE-MIT b/crates/maplit/LICENSE-MIT
new file mode 100644
index 0000000..9203baa
--- /dev/null
+++ b/crates/maplit/LICENSE-MIT
@@ -0,0 +1,25 @@
+Copyright (c) 2015
+
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without
+limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions
+of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/crates/maplit/METADATA b/crates/maplit/METADATA
new file mode 100644
index 0000000..7ef42cd
--- /dev/null
+++ b/crates/maplit/METADATA
@@ -0,0 +1,16 @@
+name: "maplit"
+description:
+ "Collection “literal” macros for HashMap, HashSet, BTreeMap, and BTreeSet."
+
+third_party {
+homepage: "https://crates.io/crates/maplit"
+ identifier {
+ type: "Archive"
+ value: "https://static.crates.io/crates/maplit/maplit-1.0.2.crate"
+ primary_source: true
+ version: "1.0.2"
+ }
+ version: "1.0.2"
+ license_type: NOTICE
+ last_upgrade_date { year: 2024 month: 11 day: 12 }
+}
diff --git a/crates/maplit/MODULE_LICENSE_APACHE2 b/crates/maplit/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/crates/maplit/MODULE_LICENSE_APACHE2
diff --git a/crates/maplit/README.rst b/crates/maplit/README.rst
new file mode 100644
index 0000000..1be1840
--- /dev/null
+++ b/crates/maplit/README.rst
@@ -0,0 +1,80 @@
+maplit
+======
+
+Container / collection literal macros for `HashMap <https://doc.rust-lang.org/beta/std/collections/struct.HashMap.html>`_, `HashSet <https://doc.rust-lang.org/beta/std/collections/struct.HashSet.html>`_, `BTreeMap <https://doc.rust-lang.org/beta/std/collections/struct.BTreeMap.html>`_, `BTreeSet <https://doc.rust-lang.org/beta/std/collections/struct.BTreeSet.html>`_.
+
+You can use these for convenience. Using them has no other implications.
+
+Please read the `API documentation here`__
+
+__ https://docs.rs/maplit/
+
+|build_status|_ |crates|_
+
+.. |build_status| image:: https://travis-ci.org/bluss/maplit.svg?branch=master
+.. _build_status: https://travis-ci.org/bluss/maplit
+
+.. |crates| image:: http://meritbadge.herokuapp.com/maplit
+.. _crates: https://crates.io/crates/maplit
+
+
+Recent Changes
+--------------
+
+- 1.0.2
+
+ - Fix usage of the macros through full paths, like `maplit::hashmap!(..)` (#27)
+
+- 1.0.1
+
+ - Fix ``unused_results`` lint in the macros by @povilasb
+
+- 1.0.0
+
+ - maplit 1.0!
+ - Only documentation changes since the last version
+
+- 0.1.6
+
+ - Add macro ``convert_args!`` for composable opt-in conversion of the
+ expressions being used for the maplit macros.
+
+- 0.1.5
+
+ - Add license files correctly
+ - Add crates.io category
+ - Small doc improvements by @seeekr and @sanmai-NL
+
+- 0.1.4
+
+ - Update docs to point to docs.rs
+
+- 0.1.2
+
+ - Now supports more arguments in hashset!{} and hashmap!{}
+
+- 0.1.0
+
+ - Initial release
+
+FAQ
+---
+
+**Question:** Very large maps take a long time to compile?
+
+**Answer:** Rustc is very slow to compile big expressions with many literals
+(including integers and float literals). Work around this by either
+using explicitly typed literals, or explicitly typed conversions.
+See https://github.com/bluss/maplit/issues/14 for more information.
+
+
+License
+-------
+
+Dual-licensed to be compatible with the Rust project.
+
+Licensed under the Apache License, Version 2.0
+http://www.apache.org/licenses/LICENSE-2.0 or the MIT license
+http://opensource.org/licenses/MIT, at your
+option. This file may not be copied, modified, or distributed
+except according to those terms.
diff --git a/crates/maplit/cargo_embargo.json b/crates/maplit/cargo_embargo.json
new file mode 100644
index 0000000..9a0a579
--- /dev/null
+++ b/crates/maplit/cargo_embargo.json
@@ -0,0 +1,3 @@
+{
+ "tests": true
+}
diff --git a/crates/maplit/src/lib.rs b/crates/maplit/src/lib.rs
new file mode 100644
index 0000000..4993f1b
--- /dev/null
+++ b/crates/maplit/src/lib.rs
@@ -0,0 +1,324 @@
+#![warn(missing_docs)]
+#![warn(unused_results)]
+#![doc(html_root_url="https://docs.rs/maplit/1/")]
+
+//! Macros for container literals with specific type.
+//!
+//! ```
+//! #[macro_use] extern crate maplit;
+//!
+//! # fn main() {
+//! let map = hashmap!{
+//! "a" => 1,
+//! "b" => 2,
+//! };
+//! # }
+//! ```
+//!
+//! The **maplit** crate uses `=>` syntax to separate the key and value for the
+//! mapping macros. (It was not possible to use `:` as separator due to syntactic
+//! restrictions in regular `macro_rules!` macros.)
+//!
+//! Note that rust macros are flexible in which brackets you use for the invocation.
+//! You can use them as `hashmap!{}` or `hashmap![]` or `hashmap!()`.
+//!
+//! Generic container macros already exist elsewhere, so those are not provided
+//! here at the moment.
+
+#[macro_export(local_inner_macros)]
+/// Create a **HashMap** from a list of key-value pairs
+///
+/// ## Example
+///
+/// ```
+/// #[macro_use] extern crate maplit;
+/// # fn main() {
+///
+/// let map = hashmap!{
+/// "a" => 1,
+/// "b" => 2,
+/// };
+/// assert_eq!(map["a"], 1);
+/// assert_eq!(map["b"], 2);
+/// assert_eq!(map.get("c"), None);
+/// # }
+/// ```
+macro_rules! hashmap {
+ (@single $($x:tt)*) => (());
+ (@count $($rest:expr),*) => (<[()]>::len(&[$(hashmap!(@single $rest)),*]));
+
+ ($($key:expr => $value:expr,)+) => { hashmap!($($key => $value),+) };
+ ($($key:expr => $value:expr),*) => {
+ {
+ let _cap = hashmap!(@count $($key),*);
+ let mut _map = ::std::collections::HashMap::with_capacity(_cap);
+ $(
+ let _ = _map.insert($key, $value);
+ )*
+ _map
+ }
+ };
+}
+
+/// Create a **HashSet** from a list of elements.
+///
+/// ## Example
+///
+/// ```
+/// #[macro_use] extern crate maplit;
+/// # fn main() {
+///
+/// let set = hashset!{"a", "b"};
+/// assert!(set.contains("a"));
+/// assert!(set.contains("b"));
+/// assert!(!set.contains("c"));
+/// # }
+/// ```
+#[macro_export(local_inner_macros)]
+macro_rules! hashset {
+ (@single $($x:tt)*) => (());
+ (@count $($rest:expr),*) => (<[()]>::len(&[$(hashset!(@single $rest)),*]));
+
+ ($($key:expr,)+) => { hashset!($($key),+) };
+ ($($key:expr),*) => {
+ {
+ let _cap = hashset!(@count $($key),*);
+ let mut _set = ::std::collections::HashSet::with_capacity(_cap);
+ $(
+ let _ = _set.insert($key);
+ )*
+ _set
+ }
+ };
+}
+
+#[macro_export(local_inner_macros)]
+/// Create a **BTreeMap** from a list of key-value pairs
+///
+/// ## Example
+///
+/// ```
+/// #[macro_use] extern crate maplit;
+/// # fn main() {
+///
+/// let map = btreemap!{
+/// "a" => 1,
+/// "b" => 2,
+/// };
+/// assert_eq!(map["a"], 1);
+/// assert_eq!(map["b"], 2);
+/// assert_eq!(map.get("c"), None);
+/// # }
+/// ```
+macro_rules! btreemap {
+ // trailing comma case
+ ($($key:expr => $value:expr,)+) => (btreemap!($($key => $value),+));
+
+ ( $($key:expr => $value:expr),* ) => {
+ {
+ let mut _map = ::std::collections::BTreeMap::new();
+ $(
+ let _ = _map.insert($key, $value);
+ )*
+ _map
+ }
+ };
+}
+
+#[macro_export(local_inner_macros)]
+/// Create a **BTreeSet** from a list of elements.
+///
+/// ## Example
+///
+/// ```
+/// #[macro_use] extern crate maplit;
+/// # fn main() {
+///
+/// let set = btreeset!{"a", "b"};
+/// assert!(set.contains("a"));
+/// assert!(set.contains("b"));
+/// assert!(!set.contains("c"));
+/// # }
+/// ```
+macro_rules! btreeset {
+ ($($key:expr,)+) => (btreeset!($($key),+));
+
+ ( $($key:expr),* ) => {
+ {
+ let mut _set = ::std::collections::BTreeSet::new();
+ $(
+ _set.insert($key);
+ )*
+ _set
+ }
+ };
+}
+
+/// Identity function. Used as the fallback for conversion.
+#[doc(hidden)]
+pub fn __id<T>(t: T) -> T { t }
+
+/// Macro that converts the keys or key-value pairs passed to another maplit
+/// macro. The default conversion is to use the [`Into`] trait, if no
+/// custom conversion is passed.
+///
+/// The syntax is:
+///
+/// `convert_args!(` `keys=` *function* `,` `values=` *function* `,`
+/// *macro_name* `!(` [ *key* => *value* [, *key* => *value* ... ] ] `))`
+///
+/// Here *macro_name* is any other maplit macro and either or both of the
+/// explicit `keys=` and `values=` parameters can be omitted.
+///
+/// [`Into`]: https://doc.rust-lang.org/std/convert/trait.Into.html
+///
+/// **Note** To use `convert_args`, the macro that is being wrapped
+/// must itself be brought into the current scope with `#[macro_use]` or `use`.
+///
+/// # Examples
+///
+/// ```
+/// #[macro_use] extern crate maplit;
+/// # fn main() {
+///
+/// use std::collections::HashMap;
+/// use std::collections::BTreeSet;
+///
+/// // a. Use the default conversion with the Into trait.
+/// // Here this converts both the key and value string literals to `String`,
+/// // but we need to specify the map type exactly!
+///
+/// let map1: HashMap<String, String> = convert_args!(hashmap!(
+/// "a" => "b",
+/// "c" => "d",
+/// ));
+///
+/// // b. Specify an explicit custom conversion for the keys. If we don't specify
+/// // a conversion for the values, they are not converted at all.
+///
+/// let map2 = convert_args!(keys=String::from, hashmap!(
+/// "a" => 1,
+/// "c" => 2,
+/// ));
+///
+/// // Note: map2 is a HashMap<String, i32>, but we didn't need to specify the type
+/// let _: HashMap<String, i32> = map2;
+///
+/// // c. convert_args! works with all the maplit macros -- and macros from other
+/// // crates that have the same "signature".
+/// // For example, btreeset and conversion from &str to Vec<u8>.
+///
+/// let set: BTreeSet<Vec<u8>> = convert_args!(btreeset!(
+/// "a", "b", "c", "d", "a", "e", "f",
+/// ));
+/// assert_eq!(set.len(), 6);
+///
+///
+/// # }
+/// ```
+#[macro_export(local_inner_macros)]
+macro_rules! convert_args {
+ (keys=$kf:expr, $macro_name:ident !($($k:expr),* $(,)*)) => {
+ $macro_name! { $(($kf)($k)),* }
+ };
+ (keys=$kf:expr, values=$vf:expr, $macro_name:ident !($($k:expr),* $(,)*)) => {
+ $macro_name! { $(($kf)($k)),* }
+ };
+ (keys=$kf:expr, values=$vf:expr, $macro_name:ident !( $($k:expr => $v:expr),* $(,)*)) => {
+ $macro_name! { $(($kf)($k) => ($vf)($v)),* }
+ };
+ (keys=$kf:expr, $macro_name:ident !($($rest:tt)*)) => {
+ convert_args! {
+ keys=$kf, values=$crate::__id,
+ $macro_name !(
+ $($rest)*
+ )
+ }
+ };
+ (values=$vf:expr, $macro_name:ident !($($rest:tt)*)) => {
+ convert_args! {
+ keys=$crate::__id, values=$vf,
+ $macro_name !(
+ $($rest)*
+ )
+ }
+ };
+ ($macro_name:ident ! $($rest:tt)*) => {
+ convert_args! {
+ keys=::std::convert::Into::into, values=::std::convert::Into::into,
+ $macro_name !
+ $($rest)*
+ }
+ };
+}
+
+#[test]
+fn test_hashmap() {
+ use std::collections::HashMap;
+ use std::collections::HashSet;
+ let names = hashmap!{
+ 1 => "one",
+ 2 => "two",
+ };
+ assert_eq!(names.len(), 2);
+ assert_eq!(names[&1], "one");
+ assert_eq!(names[&2], "two");
+ assert_eq!(names.get(&3), None);
+
+ let empty: HashMap<i32, i32> = hashmap!{};
+ assert_eq!(empty.len(), 0);
+
+ let _nested_compiles = hashmap!{
+ 1 => hashmap!{0 => 1 + 2,},
+ 2 => hashmap!{1 => 1,},
+ };
+
+ let _: HashMap<String, i32> = convert_args!(keys=String::from, hashmap!(
+ "one" => 1,
+ "two" => 2,
+ ));
+
+ let _: HashMap<String, i32> = convert_args!(keys=String::from, values=__id, hashmap!(
+ "one" => 1,
+ "two" => 2,
+ ));
+
+ let names: HashSet<String> = convert_args!(hashset!(
+ "one",
+ "two",
+ ));
+ assert!(names.contains("one"));
+ assert!(names.contains("two"));
+
+ let lengths: HashSet<usize> = convert_args!(keys=str::len, hashset!(
+ "one",
+ "two",
+ ));
+ assert_eq!(lengths.len(), 1);
+
+ let _no_trailing: HashSet<usize> = convert_args!(keys=str::len, hashset!(
+ "one",
+ "two"
+ ));
+}
+
+#[test]
+fn test_btreemap() {
+ use std::collections::BTreeMap;
+ let names = btreemap!{
+ 1 => "one",
+ 2 => "two",
+ };
+ assert_eq!(names.len(), 2);
+ assert_eq!(names[&1], "one");
+ assert_eq!(names[&2], "two");
+ assert_eq!(names.get(&3), None);
+
+ let empty: BTreeMap<i32, i32> = btreemap!{};
+ assert_eq!(empty.len(), 0);
+
+ let _nested_compiles = btreemap!{
+ 1 => btreemap!{0 => 1 + 2,},
+ 2 => btreemap!{1 => 1,},
+ };
+}
diff --git a/crates/maplit/tests/tests.rs b/crates/maplit/tests/tests.rs
new file mode 100644
index 0000000..b7b8968
--- /dev/null
+++ b/crates/maplit/tests/tests.rs
@@ -0,0 +1,65 @@
+
+#[macro_use] extern crate maplit;
+
+#[test]
+#[allow(unused_parens)]
+fn test_parse() {
+ let mut m = hashmap!{};
+ m.insert(1, 1);
+ hashmap!{1 => 1};
+ hashmap!{1 => 1,};
+ hashmap!{1 + 1 => 1, 2 + 1 => 2};
+ hashmap!{1 + 1 => 1, 2 + 1 => 2,};
+ hashmap!{{1 + 2} => 1, (1 + 3) => {0 + 2}};
+ let m = hashmap!{"a".to_string() => 1 + 2, "b".to_string() => 1 + 3};
+ assert_eq!(m["a"], 3);
+ assert_eq!(m["b"], 4);
+ let m = hashmap!{"a".to_string() => 1 + 2, "b".to_string() => 1 + 3, };
+ assert_eq!(m["a"], 3);
+ assert_eq!(m["b"], 4);
+
+ let mut s = hashset!{};
+ s.insert(1);
+ hashset!{1};
+ hashset!{1,};
+ hashset!{1, 2};
+ hashset!{1, 2,};
+ hashset!{1 + 1, 2 + 1};
+ hashset!{1 + 1, 2 + 1,};
+ hashset!{{1 + 1}, (2 + 1)};
+}
+
+#[test]
+fn hashset() {
+ let mut set = hashset!{};
+ assert!(set.is_empty());
+ set.insert(2);
+ let set = hashset!{1};
+ assert_eq!(set.len(), 1);
+ let set = hashset!{2, 3};
+ assert_eq!(set.len(), 2);
+ // Test that we can use many elements without hitting the macro recursion limit
+ let set = hashset!{1,
+ 2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,
+ 2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,
+ 2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,
+ 2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,
+ 2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,
+ 2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,
+ 2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,
+ 2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,
+ 2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,
+ 2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,
+ 2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,
+ 2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,
+ 2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,
+ 2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,
+ 2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,
+ 2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,
+ 2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,
+ 2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,
+ 2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,
+ 2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,
+ };
+ assert_eq!(set.len(), 10);
+}
diff --git a/pseudo_crate/Cargo.lock b/pseudo_crate/Cargo.lock
index 64f550c..7fce79f 100644
--- a/pseudo_crate/Cargo.lock
+++ b/pseudo_crate/Cargo.lock
@@ -236,6 +236,8 @@
"ident_case",
"idna 0.5.0",
"indexmap 1.9.2",
+ "inotify",
+ "inotify-sys",
"instant",
"intrusive-collections",
"itertools 0.13.0",
@@ -264,6 +266,7 @@
"lz4_flex",
"macaddr",
"managed",
+ "maplit",
"matches",
"matchit",
"maybe-async",
@@ -2654,6 +2657,28 @@
]
[[package]]
+name = "inotify"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3"
+dependencies = [
+ "bitflags 2.6.0",
+ "futures-core",
+ "inotify-sys",
+ "libc",
+ "tokio",
+]
+
+[[package]]
+name = "inotify-sys"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e74a1aa87c59aeff6ef2cc2fa62d41bc43f54952f55652656b18a02fd5e356c0"
+dependencies = [
+ "libc",
+]
+
+[[package]]
name = "inout"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3070,6 +3095,12 @@
checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d"
[[package]]
+name = "maplit"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
+
+[[package]]
name = "matches"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/pseudo_crate/Cargo.toml b/pseudo_crate/Cargo.toml
index f977e56..c1d5487 100644
--- a/pseudo_crate/Cargo.toml
+++ b/pseudo_crate/Cargo.toml
@@ -152,6 +152,8 @@
ident_case = "=1.0.1"
idna = "=0.5.0"
indexmap = "=1.9.2"
+inotify = "=0.11.0"
+inotify-sys = "=0.1.3"
instant = "=0.1.12"
intrusive-collections = "=0.9.6"
itertools = "=0.13.0"
@@ -180,6 +182,7 @@
lz4_flex = "=0.11.2"
macaddr = "=1.0.1"
managed = "=0.8.0"
+maplit = "=1.0.2"
matches = "=0.1.9"
matchit = "=0.7.3"
maybe-async = "=0.2.10"
diff --git a/pseudo_crate/crate-list.txt b/pseudo_crate/crate-list.txt
index 921b458..1504e67 100644
--- a/pseudo_crate/crate-list.txt
+++ b/pseudo_crate/crate-list.txt
@@ -144,6 +144,8 @@
ident_case
idna
indexmap
+inotify
+inotify-sys
instant
intrusive-collections
itertools
@@ -172,6 +174,7 @@
lz4_flex
macaddr
managed
+maplit
matches
matchit
maybe-async