commit 37f645bea088a3b59b2c0d19f9964fad0299b23b Author: nm17 Date: Sat Mar 15 19:28:47 2025 +0400 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..131f951 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,501 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +dependencies = [ + "anstyle", + "once_cell", + "windows-sys 0.59.0", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" + +[[package]] +name = "clap" +version = "4.5.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6088f3ae8c3608d19260cd7445411865a485688711b78b5be70d78cd96136f83" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap-verbosity-flag" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2678fade3b77aa3a8ff3aae87e9c008d3fb00473a41c71fbf74e91c8c7b37e84" +dependencies = [ + "clap", + "log", +] + +[[package]] +name = "clap_builder" +version = "4.5.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "io-kit-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617ee6cf8e3f66f3b4ea67a4058564628cde41901316e19f559e14c7c72c5e7b" +dependencies = [ + "core-foundation-sys", + "mach2", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "libc" +version = "0.2.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "log" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" + +[[package]] +name = "mach2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" +dependencies = [ + "libc", +] + +[[package]] +name = "nusb" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99a726776e551f3ee9b467fe47202f26e64b9bbf715df5443b0904df6f2dcc41" +dependencies = [ + "atomic-waker", + "core-foundation", + "core-foundation-sys", + "futures-core", + "io-kit-sys", + "libc", + "log", + "once_cell", + "rustix", + "slab", + "windows-sys 0.48.0", +] + +[[package]] +name = "once_cell" +version = "1.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde51589ab56b20a6f686b2c68f7a0bd6add753d697abf720d63f8db3ab7b1ad" + +[[package]] +name = "proc-macro2" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rust-dms" +version = "0.1.0" +dependencies = [ + "clap", + "clap-verbosity-flag", + "hex", + "libc", + "log", + "nusb", + "signal-hook", + "snafu", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "snafu" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "223891c85e2a29c3fe8fb900c1fae5e69c2e42415e3177752e8718475efa5019" +dependencies = [ + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c3c6b7927ffe7ecaa769ee0e3994da3b8cafc8f444578982c83ecb161af917" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..970eb4b --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "rust-dms" +version = "0.1.0" +edition = "2024" +description = """ +A tiny utility to execute certain actions on upon a disconnection of any given USB device. +""" +authors = ["nm17"] +license = "MIT OR Apache-2.0" +readme = "README.md" +repository = "https://git.dc09.ru/nm17/rust-dms" +homepage = "https://git.dc09.ru/nm17/rust-dms" + +[dependencies] +clap = { version = "4.5.32", features = ["derive"] } +clap-verbosity-flag = "3.0.2" +hex = { version = "0.4.3" } +libc = { version = "0.2.171", default-features = false } +log = "0.4.26" +nusb = "0.1.13" +signal-hook = "0.3.17" +snafu = "0.8.5" diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..78173fa --- /dev/null +++ b/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/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..22e49e2 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2025 Даниил Николаев (nm17) + +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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..ea5b31d --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +# Rust-DMS (Dead man switch) + +A tiny utility to execute certain actions on upon a disconnection of any given USB device. + +This project is meant to be as simple and reliable as possible. It shouldn't need continious updates or testing. +It doesn't have a web or terminal UI, all options are supplied from CLI arguments only. +This makes it easy to understand and configure, compared to configuring from config files or .env files/variables. + +## How to install + +``` +$ cargo install rust-dms +``` + +## Similar projects +### dead-man-switch + +A bit too complex for what I need, but if you really need a TUI and Web UI for your deadman switch - use it. + +## License + +The crate is licensed under either of: + + - Apache License, Version 2.0 + - MIT license + +at your option. + +## Contribution +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. \ No newline at end of file diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..7bdae7b --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,43 @@ +use clap_verbosity_flag::Verbosity; + +#[derive(clap::Parser)] +/// A simple dead man switch based on USB devices. +pub struct Cli { + #[command(flatten)] + pub verbosity: Verbosity, + + /// The amount of time in miliseconds to sleep between checks. + #[clap(short, long, default_value_t = 100.0, value_name = "ms")] + pub sleep_time: f64, + + /// Ignore the following signals from being handled by the usual means. + /// + /// The signals SIGKILL and SIGSTOP cannot be caught, blocked, or ignored. + #[cfg(target_os = "linux")] + #[clap(short, long = "prevent-signal", value_name = "SIGNAL", default_values = ["SIGQUIT", "SIGABRT"])] + pub prevent_signals: Vec, + + /// Do not actually do any changes, only print what would be executed. + #[clap(short, long, default_value_t = false)] + pub dry_run: bool, + + /// Determines if we exit after executing all the main actions or not. + /// + /// Not useful if your actions shutdown the system. + #[clap(short = 'c', long, default_value_t = false)] + pub continue_on_unplug: bool, + + /// Actions to take if the device is not plugged in to begin with. + #[clap(short = 'N', long, value_name = "ACTION")] + pub not_plugged_on_boot_actions: Vec, + + /// The USB Vendor ID of the dead man switch device. + pub vid: String, + + /// The USB Product ID of the dead man switch device. + pub pid: String, + + /// List of shell commands to execute on device being unplugged. + #[clap(required = true, value_name = "ACTION")] + pub actions: Vec, +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..dce971b --- /dev/null +++ b/src/error.rs @@ -0,0 +1,40 @@ +use std::array::TryFromSliceError; + +use hex::FromHexError; +use snafu::Snafu; + +type AnyError = Box; + +#[derive(Debug, Snafu)] +#[snafu(visibility(pub))] +pub enum MyError { + #[snafu(display("Error while parsing hex values: {message}"))] + FromHex { + source: FromHexError, + message: &'static str, + }, + #[snafu(display("Invalid slice length, expected = {expected_len}, got = {got}"))] + TryFromSlice { + source: TryFromSliceError, + expected_len: usize, + got: usize, + }, + #[snafu(display("Error while getting the USB list: {message}"))] + USB { + source: nusb::Error, + message: &'static str, + }, + #[snafu(display("Device not found on first run"))] + DeviceNotFound {}, + #[snafu(display("Device not found on first run"))] + StdIO { + source: std::io::Error, + message: &'static str, + }, + #[snafu(whatever, display("{message}"))] + Whatever { + message: String, + #[snafu(source(from(AnyError, Some)))] + source: Option, + }, +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..66e9a5f --- /dev/null +++ b/src/main.rs @@ -0,0 +1,125 @@ +#![doc = include_str!("../README.md")] + +pub mod cli; +pub mod error; + +#[cfg(target_os = "linux")] +pub mod signal_handler; + +use std::{ + process::{Command, exit}, + thread::sleep, + time::Duration, +}; + +use clap::Parser; +use cli::Cli; +use error::*; +use log::{info, trace}; +use snafu::ResultExt; + +pub fn check_if_plugged(pid: u16, vid: u16, first_run: bool) -> Result { + let device_iter = nusb::list_devices(); + + if device_iter.is_err() { + if first_run { + let _ = device_iter.context(USBSnafu { + message: "Getting the device list for the first time", + })?; + unreachable!(); + } else { + dbg!(&device_iter.err()); + return Ok(false); + } + } + + Ok(device_iter + .unwrap() + .any(|dev| dev.vendor_id() == vid && dev.product_id() == pid)) +} + +pub fn execute_actions(cli: &Cli, actions: impl Iterator>) { + for action in actions { + let action = action.as_ref(); + if cli.dry_run { + info!("[Dry run] {}", action); + continue; + } + let mut cmd = Command::new( + std::env::var_os("SHELL") + .map(|el| el.into_string().unwrap()) + .unwrap_or_else(|| "/bin/sh".to_string()), + ); + cmd.args(["-c", action]); + let mut handle = cmd.spawn().expect("Can't start the shell"); + let _ = handle.wait().expect("Error waiting for the action"); + } +} + +#[snafu::report] +fn main() -> Result<(), MyError> { + let cli = Cli::parse(); + + if cfg!(target_os = "linux") { + use signal_handler::handle_signals; + let _signals_handler = handle_signals(&cli)?; + } + + let (pid, vid) = ( + hex::decode(cli.pid.clone()).context(FromHexSnafu { + message: "Invalid PID", + })?, + hex::decode(cli.vid.clone()).context(FromHexSnafu { + message: "Invalid VID", + })?, + ); + + let (pid, vid) = ( + u16::from_be_bytes( + pid.as_slice() + .try_into() + .with_context(|_| TryFromSliceSnafu { + expected_len: 2usize, + got: pid.len(), + })?, + ), + u16::from_be_bytes( + vid.as_slice() + .try_into() + .with_context(|_| TryFromSliceSnafu { + expected_len: 2usize, + got: vid.len(), + })?, + ), + ); + + let first_check = check_if_plugged(pid, vid, true); + if let Err(ref _first_check_err) = first_check { + if !cli.not_plugged_on_boot_actions.is_empty() { + execute_actions(&cli, cli.not_plugged_on_boot_actions.iter()); + + exit(1); + } else { + let _ = first_check?; + unreachable!(); + } + } + + info!("Successfully got through the first iteration of the loop"); + + loop { + sleep(Duration::from_micros( + (cli.sleep_time * 1000.).round() as u64 + )); + + if !(check_if_plugged(pid, vid, false)?) { + execute_actions(&cli, cli.actions.iter()); + + if !cli.continue_on_unplug { + exit(1); + } + } else { + trace!("Device plugged in") + } + } +} diff --git a/src/signal_handler/consts.rs b/src/signal_handler/consts.rs new file mode 100644 index 0000000..4d927eb --- /dev/null +++ b/src/signal_handler/consts.rs @@ -0,0 +1,35 @@ +pub const STDIN_FILENO: i32 = 0; +pub const STDOUT_FILENO: i32 = 1; +pub const STDERR_FILENO: i32 = 2; +pub const SIGHUP: i32 = 1; +pub const SIGINT: i32 = 2; +pub const SIGQUIT: i32 = 3; +pub const SIGILL: i32 = 4; +pub const SIGTRAP: i32 = 5; +pub const SIGABRT: i32 = 6; +pub const SIGFPE: i32 = 8; +pub const SIGKILL: i32 = 9; +pub const SIGSEGV: i32 = 11; +pub const SIGPIPE: i32 = 13; +pub const SIGALRM: i32 = 14; +pub const SIGTERM: i32 = 15; +pub const SIGTTIN: i32 = 21; +pub const SIGTTOU: i32 = 22; +pub const SIGXCPU: i32 = 24; +pub const SIGXFSZ: i32 = 25; +pub const SIGVTALRM: i32 = 26; +pub const SIGPROF: i32 = 27; +pub const SIGWINCH: i32 = 28; +pub const SIGCHLD: i32 = 17; +pub const SIGBUS: i32 = 7; +pub const SIGUSR1: i32 = 10; +pub const SIGUSR2: i32 = 12; +pub const SIGCONT: i32 = 18; +pub const SIGSTOP: i32 = 19; +pub const SIGTSTP: i32 = 20; +pub const SIGURG: i32 = 23; +pub const SIGIO: i32 = 29; +pub const SIGSYS: i32 = 31; +pub const SIGSTKFLT: i32 = 16; +pub const SIGPOLL: i32 = 29; +pub const SIGPWR: i32 = 30; \ No newline at end of file diff --git a/src/signal_handler/mod.rs b/src/signal_handler/mod.rs new file mode 100644 index 0000000..987ae06 --- /dev/null +++ b/src/signal_handler/mod.rs @@ -0,0 +1,58 @@ +pub mod consts; + +use std::{ + collections::HashMap, + thread::{self, JoinHandle}, +}; + +use libc::*; +use log::warn; +use signal_hook::iterator::Signals; +use snafu::ResultExt; + +use crate::{ + cli::Cli, + error::{MyError, StdIOSnafu}, +}; + +pub fn handle_signals(cli: &Cli) -> Result, MyError> { + let signals: HashMap<&str, i32> = [ + ("SIGHUP", SIGHUP), + ("SIGINT", SIGINT), + ("SIGQUIT", SIGQUIT), + ("SIGILL", SIGILL), + ("SIGTRAP", SIGTRAP), + ("SIGABRT", SIGABRT), + ("SIGBUS", SIGBUS), + ("SIGFPE", SIGFPE), + ("SIGKILL", SIGKILL), + ("SIGUSR1", SIGUSR1), + ("SIGSEGV", SIGSEGV), + ("SIGUSR2", SIGUSR2), + ("SIGPIPE", SIGPIPE), + ("SIGALRM", SIGALRM), + ("SIGTERM", SIGTERM), + ("SIGCHLD", SIGCHLD), + ("SIGCONT", SIGCONT), + ("SIGSTOP", SIGSTOP), + ("SIGTSTP", SIGTSTP), + ("SIGTTIN", SIGTTIN), + ("SIGTTOU", SIGTTOU), + ] + .into(); + + let mut signals = Signals::new( + cli.prevent_signals + .iter() + .map(|el| signals.get(el.as_str()).unwrap()), + ) + .context(StdIOSnafu { + message: "Could not setup the signal catcher", + })?; + + Ok(thread::spawn(move || { + for sig in signals.forever() { + warn!("Received signal {:?}", sig); + } + })) +}