diff options
Diffstat (limited to 'third_party/rust/devd-rs')
-rw-r--r-- | third_party/rust/devd-rs/.cargo-checksum.json | 1 | ||||
-rw-r--r-- | third_party/rust/devd-rs/CODE_OF_CONDUCT.md | 74 | ||||
-rw-r--r-- | third_party/rust/devd-rs/Cargo.lock | 39 | ||||
-rw-r--r-- | third_party/rust/devd-rs/Cargo.toml | 37 | ||||
-rw-r--r-- | third_party/rust/devd-rs/README.md | 27 | ||||
-rw-r--r-- | third_party/rust/devd-rs/UNLICENSE | 24 | ||||
-rw-r--r-- | third_party/rust/devd-rs/examples/main.rs | 12 | ||||
-rw-r--r-- | third_party/rust/devd-rs/src/data.rs | 9 | ||||
-rw-r--r-- | third_party/rust/devd-rs/src/lib.rs | 93 | ||||
-rw-r--r-- | third_party/rust/devd-rs/src/parser.rs | 153 | ||||
-rw-r--r-- | third_party/rust/devd-rs/src/result.rs | 26 |
11 files changed, 495 insertions, 0 deletions
diff --git a/third_party/rust/devd-rs/.cargo-checksum.json b/third_party/rust/devd-rs/.cargo-checksum.json new file mode 100644 index 0000000000..213a5bf8ea --- /dev/null +++ b/third_party/rust/devd-rs/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"CODE_OF_CONDUCT.md":"22729f3c955ab53089f989d6ca8c84e575fa7fb68a8c86d2f7bbe9deef56cd2f","Cargo.lock":"9b04639cee0f4f014700a16aecb4b2a168e5d1261baf89747730cc4384dabe36","Cargo.toml":"1a56f2eed01dfef37968a3826b8b0c2cd3f77ab91977588001dc92419613ebdf","README.md":"edc8122b28f1948ca3e52b259363ec599ff97e2b29b32c328893605124203f22","UNLICENSE":"7e12e5df4bae12cb21581ba157ced20e1986a0508dd10d0e8a4ab9a4cf94e85c","examples/main.rs":"734a87846b61d09d2aaca444c69dc61765f66df34602f3a4acf1255f95404226","src/data.rs":"677b52a636deb1f0ffc623dbdc5ed7acd78d915117825ced7031c6fa6f0c861e","src/lib.rs":"ef2f27d9caf4ba1183364cd58a6fd8d0d5400cdb0de6350bcc7cf655eb557c2f","src/parser.rs":"495a155de17c4422cf11ff97026de681707156e211ed89693fd0546d8628127a","src/result.rs":"0e9064e4c912c7eae170a329277ff3811b0016a8f38d1f8f6f57a269f605b489"},"package":"9313f104b590510b46fc01c0a324fc76505c13871454d3c48490468d04c8d395"}
\ No newline at end of file diff --git a/third_party/rust/devd-rs/CODE_OF_CONDUCT.md b/third_party/rust/devd-rs/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..fae73b6736 --- /dev/null +++ b/third_party/rust/devd-rs/CODE_OF_CONDUCT.md @@ -0,0 +1,74 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project owner at hello@unrelenting.technology. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project owner is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/third_party/rust/devd-rs/Cargo.lock b/third_party/rust/devd-rs/Cargo.lock new file mode 100644 index 0000000000..73f68ee90e --- /dev/null +++ b/third_party/rust/devd-rs/Cargo.lock @@ -0,0 +1,39 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "devd-rs" +version = "0.3.6" +dependencies = [ + "libc", + "nom", +] + +[[package]] +name = "libc" +version = "0.2.134" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +dependencies = [ + "memchr", + "minimal-lexical", +] diff --git a/third_party/rust/devd-rs/Cargo.toml b/third_party/rust/devd-rs/Cargo.toml new file mode 100644 index 0000000000..14a87f213a --- /dev/null +++ b/third_party/rust/devd-rs/Cargo.toml @@ -0,0 +1,37 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2018" +name = "devd-rs" +version = "0.3.6" +authors = ["Val Packett <val@packett.cool>"] +description = "An interface to devd, the device hotplug daemon on FreeBSD and DragonFlyBSD" +homepage = "https://codeberg.org/valpackett/devd-rs" +readme = "README.md" +keywords = [ + "System", + "FreeBSD", + "DragonFlyBSD", + "devd", + "hotplug", +] +categories = ["os::unix-apis"] +license = "Unlicense/MIT" +repository = "https://codeberg.org/valpackett/devd-rs" + +[dependencies.libc] +version = "0" + +[dependencies.nom] +version = "7" +features = ["std"] +default-features = false diff --git a/third_party/rust/devd-rs/README.md b/third_party/rust/devd-rs/README.md new file mode 100644 index 0000000000..9313848029 --- /dev/null +++ b/third_party/rust/devd-rs/README.md @@ -0,0 +1,27 @@ +[![crates.io](https://img.shields.io/crates/v/devd-rs.svg)](https://crates.io/crates/devd-rs) +[![unlicense](https://img.shields.io/badge/un-license-green.svg?style=flat)](https://unlicense.org) + +# devd-rs + +A Rust library for listening to FreeBSD (also DragonFlyBSD) [devd](https://www.freebsd.org/cgi/man.cgi?devd)'s device attach-detach notifications. + +Listens on `/var/run/devd.seqpacket.pipe` and parses messages using [nom](https://github.com/Geal/nom). + +## Usage + +See [examples/main.rs](https://github.com/unrelentingtech/devd-rs/blob/master/examples/main.rs). + +## Contributing + +Please feel free to submit pull requests! + +By participating in this project you agree to follow the [Contributor Code of Conduct](https://www.contributor-covenant.org/version/1/4/code-of-conduct/) and to release your contributions under the Unlicense and the MIT License. + +[The list of contributors is available on GitHub](https://github.com/unrelentingtech/devd-rs/graphs/contributors). + +## License + +This is free and unencumbered software released into the public domain. +For more information, please refer to the `UNLICENSE` file or [unlicense.org](https://unlicense.org). + +It is also available under the MIT License. diff --git a/third_party/rust/devd-rs/UNLICENSE b/third_party/rust/devd-rs/UNLICENSE new file mode 100644 index 0000000000..68a49daad8 --- /dev/null +++ b/third_party/rust/devd-rs/UNLICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +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 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. + +For more information, please refer to <http://unlicense.org/> diff --git a/third_party/rust/devd-rs/examples/main.rs b/third_party/rust/devd-rs/examples/main.rs new file mode 100644 index 0000000000..fee8612f1e --- /dev/null +++ b/third_party/rust/devd-rs/examples/main.rs @@ -0,0 +1,12 @@ +extern crate devd_rs; + +use devd_rs::*; + +fn main() { + let mut ctx = Context::new().unwrap(); + loop { + if let Ok(ev) = ctx.wait_for_event(1000) { + println!("{:?}", ev); + } + } +} diff --git a/third_party/rust/devd-rs/src/data.rs b/third_party/rust/devd-rs/src/data.rs new file mode 100644 index 0000000000..52084362e5 --- /dev/null +++ b/third_party/rust/devd-rs/src/data.rs @@ -0,0 +1,9 @@ +pub use std::collections::BTreeMap; + +#[derive(Debug, Clone, PartialEq)] +pub enum Event { + Notify { system: String, subsystem: String, kind: String, data: BTreeMap<String, String> }, + Attach { dev: String, parent: BTreeMap<String, String>, location: String }, + Detach { dev: String, parent: BTreeMap<String, String>, location: String }, + Nomatch { parent: BTreeMap<String, String>, location: String }, +} diff --git a/third_party/rust/devd-rs/src/lib.rs b/third_party/rust/devd-rs/src/lib.rs new file mode 100644 index 0000000000..8c863e856e --- /dev/null +++ b/third_party/rust/devd-rs/src/lib.rs @@ -0,0 +1,93 @@ +pub mod data; +pub mod parser; +pub mod result; + +use io::{BufRead, BufReader}; +use libc::{c_int, connect, nfds_t, poll, pollfd, sockaddr_un, socket, AF_UNIX, POLLIN, SOCK_SEQPACKET}; +use std::cmp::Ordering; +use std::os::unix::io::{FromRawFd, RawFd}; +use std::os::unix::net::UnixStream; +use std::{io, mem, ptr}; + +pub use data::*; +pub use result::*; + +const SOCKET_PATH: &str = "/var/run/devd.seqpacket.pipe"; + +pub fn parse_devd_event(event: &str) -> Result<Event> { + match parser::event(event) { + Ok((_, x)) => Ok(x), + _ => Err(Error::Parse), + } +} + +#[derive(Debug)] +pub struct Context { + sock: BufReader<UnixStream>, + sockfd: RawFd, + buffer: String, +} + +impl Context { + pub fn new() -> Result<Context> { + unsafe { + let sockfd = socket(AF_UNIX, SOCK_SEQPACKET, 0); + if sockfd < 0 { + return Err(io::Error::last_os_error().into()); + } + let mut sockaddr = sockaddr_un { sun_family: AF_UNIX as _, ..mem::zeroed() }; + ptr::copy_nonoverlapping(SOCKET_PATH.as_ptr(), sockaddr.sun_path.as_mut_ptr() as *mut u8, SOCKET_PATH.len()); + if connect( + sockfd, + &sockaddr as *const sockaddr_un as *const _, + (mem::size_of_val(&AF_UNIX) + SOCKET_PATH.len()) as _, + ) < 0 + { + return Err(io::Error::last_os_error().into()); + } + Ok(Context { + sock: BufReader::new(UnixStream::from_raw_fd(sockfd)), + sockfd, + buffer: String::new(), + }) + } + } + + pub fn wait_for_event_raw_internal(&mut self, timeout_ms: usize) -> Result<&str> { + let mut fds = [pollfd { fd: self.sockfd, events: POLLIN, revents: 0 }]; + let x = unsafe { poll((&mut fds).as_mut_ptr(), fds.len() as nfds_t, timeout_ms as c_int) }; + + match x.cmp(&0) { + Ordering::Less => Err(io::Error::last_os_error().into()), + Ordering::Equal => Err(Error::Timeout), + Ordering::Greater => { + self.buffer.clear(); + self.sock.read_line(&mut self.buffer)?; + Ok(&self.buffer) + } + } + } + + /// Waits for an event using poll(), reads it but does not parse + pub fn wait_for_event_raw(&mut self, timeout_ms: usize) -> Result<String> { + self.wait_for_event_raw_internal(timeout_ms).map(ToOwned::to_owned) + } + + /// Waits for an event using poll(), reads and parses it + pub fn wait_for_event(&mut self, timeout_ms: usize) -> Result<Event> { + self.wait_for_event_raw_internal(timeout_ms).and_then(parse_devd_event) + } + + /// Returns the devd socket file descriptor in case you want to select/poll on it together with + /// other file descriptors + pub fn fd(&self) -> RawFd { + self.sockfd + } + + /// Reads an event and parses it. Use when polling on the raw fd by yourself + pub fn read_event(&mut self) -> Result<Event> { + self.buffer.clear(); + self.sock.read_line(&mut self.buffer)?; + parse_devd_event(&self.buffer) + } +} diff --git a/third_party/rust/devd-rs/src/parser.rs b/third_party/rust/devd-rs/src/parser.rs new file mode 100644 index 0000000000..ee3c0511e9 --- /dev/null +++ b/third_party/rust/devd-rs/src/parser.rs @@ -0,0 +1,153 @@ +use crate::data::*; + +use nom::{ + branch::alt, + bytes::complete::{tag, take_while}, + character::complete::{alphanumeric1, char, multispace0, multispace1}, + combinator::success, + multi::fold_many0, + sequence::{delimited, preceded, terminated, tuple}, + IResult, Parser, +}; + +/// Parse a single value, which is either a quoted string, or a word without whitespace +fn val(input: &str) -> IResult<&str, &str, ()> { + alt((delimited(char('"'), take_while(|c| c != '"'), char('"')), take_while(|c| c != '\n' && c != ' '))).parse(input) +} + +/// Parse a key followed by a value, separated by = +fn keyval(input: &str) -> IResult<&str, (&str, &str), ()> { + terminated(alphanumeric1, char('=')).and(val).parse(input) +} + +/// Parser any number of key-value pairs, separated by 0 or more whitespace +fn keyvals(input: &str) -> IResult<&str, BTreeMap<String, String>, ()> { + fold_many0(terminated(keyval, multispace0), BTreeMap::new, |mut map, (key, value)| { + map.insert(key.to_owned(), value.to_owned()); + map + }) + .parse(input) +} + +/// Parse a key-value pair, where the key is a specific tag, separated by =, +/// terminated by whitespace +fn keyed_val<'i>(key: &'static str) -> impl Parser<&'i str, &'i str, ()> { + terminated(preceded(terminated(tag(key), char('=')), val), multispace1) +} + +fn notify(input: &str) -> IResult<&str, Event, ()> { + preceded(char('!'), tuple((keyed_val("system"), keyed_val("subsystem"), keyed_val("type"), keyvals))) + .map(|(sys, subsys, kind, data)| Event::Notify { + system: sys.to_owned(), + subsystem: subsys.to_owned(), + kind: kind.to_owned(), + data, + }) + .parse(input) +} + +/// Parse a key-value pair, where the key is a specific tag, separated by +/// whitespace +fn event_param<'i, T>(key: &'static str, value: impl Parser<&'i str, T, ()>) -> impl Parser<&'i str, T, ()> { + preceded(terminated(tag(key), multispace1), value) +} + +fn generic_event<'i, T>(prefix: char, dev: impl Parser<&'i str, T, ()>) -> impl Parser<&'i str, (T, BTreeMap<String, String>, &'i str), ()> { + tuple(( + terminated(preceded(char(prefix), dev), multispace1), + event_param("at", keyvals), + event_param("on", val), + )) +} + +fn attach(input: &str) -> IResult<&str, Event, ()> { + generic_event('+', alphanumeric1).map(|(dev, parent, loc)| Event::Attach { dev: dev.to_owned(), parent, location: loc.to_owned() }).parse(input) +} + +fn detach(input: &str) -> IResult<&str, Event, ()> { + generic_event('-', alphanumeric1).map(|(dev, parent, loc)| Event::Detach { dev: dev.to_owned(), parent, location: loc.to_owned() }).parse(input) +} + +fn nomatch(input: &str) -> IResult<&str, Event, ()> { + generic_event('?', success(())).map(|((), parent, loc)| Event::Nomatch { parent, location: loc.to_owned() }).parse(input) +} + +pub fn event(input: &str) -> IResult<&str, Event, ()> { + alt((notify, attach, detach, nomatch)).parse(input) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_notify() { + let txt = "!system=USB subsystem=INTERFACE type=ATTACH ugen=ugen0.2 vendor=0x1050 sernum=\"\" mode=host\n"; + let res = event(txt); + let mut data = BTreeMap::new(); + data.insert("ugen".to_owned(), "ugen0.2".to_owned()); + data.insert("vendor".to_owned(), "0x1050".to_owned()); + data.insert("sernum".to_owned(), "".to_owned()); + data.insert("mode".to_owned(), "host".to_owned()); + assert_eq!( + res, + Ok(( + "", + Event::Notify { + system: "USB".to_owned(), + subsystem: "INTERFACE".to_owned(), + kind: "ATTACH".to_owned(), + data, + } + )) + ) + } + + #[test] + fn test_attach() { + let txt = "+uhid1 at bus=0 sernum=\"\" on uhub1"; + let res = event(txt); + let mut data = BTreeMap::new(); + data.insert("bus".to_owned(), "0".to_owned()); + data.insert("sernum".to_owned(), "".to_owned()); + assert_eq!( + res, + Ok(( + "", + Event::Attach { + dev: "uhid1".to_owned(), + parent: data, + location: "uhub1".to_owned(), + } + )) + ) + } + + #[test] + fn test_detach() { + let txt = "-uhid1 at on uhub1"; + let res = event(txt); + let data = BTreeMap::new(); + assert_eq!( + res, + Ok(( + "", + Event::Detach { + dev: "uhid1".to_owned(), + parent: data, + location: "uhub1".to_owned(), + } + )) + ) + } + + #[test] + fn test_nomatch() { + let txt = "? at bus=0 on uhub1"; + let res = event(txt); + let mut data = BTreeMap::new(); + data.insert("bus".to_owned(), "0".to_owned()); + + assert_eq!(res, Ok(("", Event::Nomatch { parent: data, location: "uhub1".to_owned() }))) + } +} diff --git a/third_party/rust/devd-rs/src/result.rs b/third_party/rust/devd-rs/src/result.rs new file mode 100644 index 0000000000..2b47b8012c --- /dev/null +++ b/third_party/rust/devd-rs/src/result.rs @@ -0,0 +1,26 @@ +use std::{io, result}; + +#[derive(Debug)] +pub enum Error { + IoError(io::Error), + Timeout, + Parse, +} + +impl From<Error> for io::Error { + fn from(val: Error) -> Self { + match val { + Error::IoError(e) => e, + Error::Timeout => io::Error::new(io::ErrorKind::Other, "devd poll timeout"), + Error::Parse => io::Error::new(io::ErrorKind::Other, "devd parse error"), + } + } +} + +impl From<io::Error> for Error { + fn from(err: io::Error) -> Error { + Error::IoError(err) + } +} + +pub type Result<T> = result::Result<T, Error>; |