init
This commit is contained in:
commit
b09fe33dd8
739
Cargo.lock
generated
Normal file
739
Cargo.lock
generated
Normal file
|
@ -0,0 +1,739 @@
|
|||
# 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 = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391"
|
||||
dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "assert_matches"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9"
|
||||
|
||||
[[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.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.105"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5208975e568d83b6b05cc0a063c8e7e9acc2b43bee6da15616a5b73e109d7437"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84b3edb18336f4df585bc9aa31dd99c036dfa5dc5e9a2939a722a188f3a8970d"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1c09dd5ada6c6c78075d6fd0da3f90d8080651e2d6cc8eb2f1aaa4034ced708"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
|
||||
|
||||
[[package]]
|
||||
name = "env_filter"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea"
|
||||
dependencies = [
|
||||
"log",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"env_filter",
|
||||
"humantime",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
|
||||
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-io"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
|
||||
|
||||
[[package]]
|
||||
name = "futures-lite"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"parking",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800"
|
||||
|
||||
[[package]]
|
||||
name = "iusb-spoof"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"assert_matches",
|
||||
"bytes",
|
||||
"clap",
|
||||
"env_logger",
|
||||
"futures-lite",
|
||||
"log",
|
||||
"num_enum",
|
||||
"pretty-hex",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
]
|
||||
|
||||
[[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 = "log"
|
||||
version = "0.4.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
||||
|
||||
[[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 = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845"
|
||||
dependencies = [
|
||||
"num_enum_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum_derive"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
|
||||
|
||||
[[package]]
|
||||
name = "pretty-hex"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbc83ee4a840062f368f9096d80077a9841ec117e17e7f700df81958f1451254"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24"
|
||||
dependencies = [
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[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 = "regex"
|
||||
version = "1.10.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
|
||||
|
||||
[[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 = "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 = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89456b690ff72fddcecf231caedbe615c59480c93358a93dfae7fc29e3ebbf0e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
"rustix",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.38.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
"libc",
|
||||
"mio",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[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.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.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
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"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.5.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
29
Cargo.toml
Normal file
29
Cargo.toml
Normal file
|
@ -0,0 +1,29 @@
|
|||
[package]
|
||||
name = "iusb-spoof"
|
||||
version = "0.1.0"
|
||||
authors = ["Gaelan Steele <gbs@canishe.com>", "Erik Schilling <erik.schilling@linaro.org>", "Yureka <yuka@yuka.dev>"]
|
||||
description = "vhost scsi backend device"
|
||||
repository = "https://git.lix.systems/the-distro/iusb-spoof"
|
||||
readme = "README.md"
|
||||
keywords = ["scsi", "s2600kp", "iusb", "jviewer"]
|
||||
license = "Apache-2.0 OR BSD-3-Clause"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
futures-lite = "2.3"
|
||||
bytes = "1.6.1"
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
env_logger = "0.11"
|
||||
log = "0.4"
|
||||
num_enum = "0.7"
|
||||
pretty-hex = "0.4.1"
|
||||
thiserror = "1.0"
|
||||
tokio = { version = "1.38.0", features = ["rt", "net", "io-util"] }
|
||||
tokio-util = { version = "0.7.11", features = ["codec"] }
|
||||
|
||||
[dev-dependencies]
|
||||
assert_matches = "1.5"
|
||||
tempfile = "3.10.1"
|
||||
|
202
LICENSE-APACHE
Normal file
202
LICENSE-APACHE
Normal file
|
@ -0,0 +1,202 @@
|
|||
|
||||
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.
|
26
LICENSE-BSD-3-Clause
Normal file
26
LICENSE-BSD-3-Clause
Normal file
|
@ -0,0 +1,26 @@
|
|||
Copyright 2022 The rust-vmm authors.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
5
README.md
Normal file
5
README.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# iusb-spoof
|
||||
|
||||
Based off [iusb_spoof.py](https://github.com/samozy/iusb/blob/master/iusb_spoof.py)
|
||||
|
||||
ISCSI Emulated Target code from [vhost-device-scsi](https://github.com/rust-vmm/vhost-device/tree/main/vhost-device-scsi)
|
34
make-token.py
Executable file
34
make-token.py
Executable file
|
@ -0,0 +1,34 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import re
|
||||
import requests
|
||||
import sys
|
||||
|
||||
|
||||
hostname = sys.argv[1]
|
||||
|
||||
|
||||
s = requests.Session()
|
||||
r = s.post('http://'+hostname+':80/rpc/WEBSES/create.asp',data="WEBVAR_USERNAME=root&WEBVAR_PASSWORD=root")
|
||||
session_cookie_regex = re.compile(r"'?(\w*(?:session)|(?:SESSION)\w*)'?\s*[:=]\s*'(\w+)'")
|
||||
for line in r.text.split("\n"):
|
||||
match_obj = session_cookie_regex.search(line)
|
||||
if match_obj is not None:
|
||||
session_cookie_value = match_obj.group(2)
|
||||
s.cookies.set("SessionCookie", session_cookie_value)
|
||||
break
|
||||
if r.status_code != 200 or not s.cookies:
|
||||
raise "Login was not successful."
|
||||
|
||||
r = s.get('http://'+hostname+':80/Java/jviewer.jnlp')
|
||||
token_regex = re.compile("<argument>(\\w{16})</argument>")
|
||||
token = None
|
||||
for line in r.text.split("\n"):
|
||||
match_obj = token_regex.search(line)
|
||||
if match_obj is not None:
|
||||
token = match_obj.group(1)
|
||||
break
|
||||
if r.status_code != 200 or token is None:
|
||||
raise "Obtaining token was not successful."
|
||||
|
||||
print(token)
|
236
src/main.rs
Normal file
236
src/main.rs
Normal file
|
@ -0,0 +1,236 @@
|
|||
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
|
||||
|
||||
mod scsi;
|
||||
|
||||
use std::{
|
||||
fs::File,
|
||||
path::PathBuf,
|
||||
process::exit,
|
||||
};
|
||||
|
||||
use futures_lite::StreamExt;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio_util::codec::LengthDelimitedCodec;
|
||||
use bytes::BufMut;
|
||||
use clap::Parser;
|
||||
use log::{error, warn};
|
||||
use thiserror::Error as ThisError;
|
||||
|
||||
use crate::scsi::emulation::{
|
||||
block_device::{BlockDevice, FileBackend, MediumRotationRate},
|
||||
target::EmulatedTarget,
|
||||
};
|
||||
use crate::scsi::{Target, Request, TaskAttr};
|
||||
|
||||
#[derive(Debug, ThisError)]
|
||||
enum Error {
|
||||
#[error("More than 256 LUNs aren't currently supported")]
|
||||
TooManyLUNs,
|
||||
#[error("IO Error: {0}")]
|
||||
IO(#[from] std::io::Error),
|
||||
}
|
||||
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Parser)]
|
||||
struct ScsiArgs {
|
||||
/// Make the images read-only.
|
||||
///
|
||||
/// Currently, we don't actually support writes, but sometimes we want to
|
||||
/// pretend the disk is writable to work around issues with some tools that
|
||||
/// use the Linux SCSI generic API.
|
||||
#[arg(long = "read-only", short = 'r')]
|
||||
read_only: bool,
|
||||
/// Tell the guest this disk is non-rotational.
|
||||
///
|
||||
/// Affects some heuristics in Linux around, for example, scheduling.
|
||||
#[arg(long = "solid-state")]
|
||||
solid_state: bool,
|
||||
|
||||
host: String,
|
||||
|
||||
//login_port: u16,
|
||||
|
||||
iusb_port: u16,
|
||||
|
||||
auth_token: String,
|
||||
|
||||
/// Images against which the SCSI actions are emulated.
|
||||
images: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
async fn run() -> Result<()> {
|
||||
env_logger::init();
|
||||
let args = ScsiArgs::parse();
|
||||
|
||||
|
||||
let mut target = EmulatedTarget::new();
|
||||
|
||||
if args.images.len() > 256 {
|
||||
// This is fairly simple to add; it's just a matter of supporting the right LUN
|
||||
// encoding formats.
|
||||
error!("Currently only up to 256 targets are supported");
|
||||
return Err(Error::TooManyLUNs);
|
||||
}
|
||||
|
||||
if !args.read_only {
|
||||
warn!("Currently, only read-only images are supported. Unless you know what you're doing, you want to pass -r");
|
||||
}
|
||||
|
||||
for image in &args.images {
|
||||
println!("{:?}", image);
|
||||
let mut dev = BlockDevice::new(FileBackend::new(
|
||||
File::options()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(image)
|
||||
.expect("Opening image"),
|
||||
));
|
||||
dev.set_write_protected(args.read_only);
|
||||
dev.set_solid_state(if args.solid_state {
|
||||
MediumRotationRate::NonRotating
|
||||
} else {
|
||||
MediumRotationRate::Unreported
|
||||
});
|
||||
target.add_lun(Box::new(dev));
|
||||
}
|
||||
|
||||
let sock = tokio::net::TcpStream::connect(&(args.host.as_str(), args.iusb_port)).await?;
|
||||
sock.set_nodelay(true)?;
|
||||
let (read, mut write) = tokio::io::split(sock);
|
||||
|
||||
assert_eq!(args.auth_token.len(), 16);
|
||||
|
||||
let mut data = vec![];
|
||||
data.put_bytes(0x00, 9);
|
||||
data.put_u8(0xf2);
|
||||
data.put_bytes(0x00, 21);
|
||||
data.put(args.auth_token.as_bytes());
|
||||
data.put_bytes(0x00, 81);
|
||||
let initial_packet = make_iusb_packet(0x05, 0x01, 0x0000, 0x00, &data);
|
||||
|
||||
//println!("SENT:\n{}", pretty_hex(&initial_packet));
|
||||
write.write_all(&initial_packet).await?;
|
||||
write.flush().await?;
|
||||
|
||||
let mut stream = LengthDelimitedCodec::builder()
|
||||
.length_field_offset(12)
|
||||
.length_field_type::<u16>()
|
||||
.length_adjustment(0x20)
|
||||
.little_endian()
|
||||
.num_skip(0) // Do not strip frame header
|
||||
.new_read(read);
|
||||
|
||||
stream.next().await.transpose()?;
|
||||
|
||||
while let Some(mut packet) = stream.next().await.transpose()? {
|
||||
//println!("{}", pretty_hex(&packet));
|
||||
|
||||
let seq_num = packet[24];
|
||||
|
||||
let packet = &mut packet[32..];
|
||||
let id = packet[4];
|
||||
|
||||
let _sniffed_command = packet[9];
|
||||
let sniffed_len = u16::from_le_bytes(packet[17..19].try_into().unwrap());
|
||||
if sniffed_len > 0x100 {
|
||||
let resp = vec![
|
||||
packet[0], 0x00, 0x00, 0x00, id, 0x00, 0x00, 0x00,
|
||||
0x01, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x30,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00
|
||||
];
|
||||
let packet = make_iusb_packet(0x80, 0x01, 0x0001, seq_num, resp.as_slice());
|
||||
//println!("SENT:\n{}", pretty_hex(&packet));
|
||||
write.write_all(&packet).await?;
|
||||
write.flush().await?;
|
||||
continue;
|
||||
}
|
||||
//println!("sniffed command: {}", sniffed_command);
|
||||
//println!("sniffed len: {}", sniffed_len);
|
||||
|
||||
let req = Request {
|
||||
id: id.into(),
|
||||
cdb: &packet[9..],
|
||||
prio: 0,
|
||||
crn: 0,
|
||||
task_attr: TaskAttr::Simple,
|
||||
};
|
||||
let mut cursor = std::io::Cursor::new(&[]);
|
||||
let mut resp_data = vec![];
|
||||
let cmd_output = target.execute_command(0, &mut cursor, &mut resp_data, req).unwrap();
|
||||
//println!("{:?} {:?}", cmd_output, resp_data.len());
|
||||
|
||||
if cmd_output.status != 0 {
|
||||
|
||||
//[2024-07-14T10:45:16Z ERROR vhost_device_scsi::scsi::emulation::target] Rejecting CDB for unknown command: [241, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
//CmdOutput { status: 2, status_qualifier: 0, sense: [112, 0, 5, 0, 0, 0, 0, 10, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0] } 0
|
||||
let resp = vec![
|
||||
packet[0], 0x00, 0x00, 0x00, id, 0x00, 0x00, 0x00,
|
||||
0x01, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, cmd_output.sense[2], cmd_output.sense[12],
|
||||
0x00, 0x00, 0x00, 0x00, 0x00
|
||||
];
|
||||
let packet = make_iusb_packet(0x80, 0x01, 0x0001, seq_num, resp.as_slice());
|
||||
//println!("SENT:\n{}", pretty_hex(&packet));
|
||||
write.write_all(&packet).await?;
|
||||
write.flush().await?;
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut resp = vec![];
|
||||
resp.put(&packet[..25]);
|
||||
|
||||
//resp.put_u8(packet[8]);
|
||||
//resp.put_u8(packet[9]);
|
||||
//resp.put_bytes(0x00, 2);
|
||||
resp.put_u32_le(resp_data.len().try_into().unwrap());
|
||||
resp.put(&resp_data[..]);
|
||||
|
||||
//println!("responding\n{}", pretty_hex(&resp_data));
|
||||
let packet = make_iusb_packet(0x80, 0x01, 0x0001, seq_num, resp.as_slice());
|
||||
//println!("SENT:\n{}", pretty_hex(&packet));
|
||||
write.write_all(&packet).await?;
|
||||
write.flush().await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn make_iusb_packet(
|
||||
device_number: u8,
|
||||
interface_number: u8,
|
||||
client_data: u32,
|
||||
seq_num: u8,
|
||||
data: &[u8],
|
||||
) -> Vec<u8> {
|
||||
let data_len: u32 = data.len().try_into().unwrap();
|
||||
|
||||
let mut packet = vec![];
|
||||
packet.put(&b"IUSB "[..]);
|
||||
packet.put_u8(0x01); // major ver
|
||||
packet.put_u8(0x00); // minor ver
|
||||
packet.put_u8(0x20); // headers length
|
||||
packet.put_u8(0x00); // checksum (unused)
|
||||
packet.put_u32_le(data_len); // data length
|
||||
packet.put_bytes(0x0, 1); // unknown
|
||||
packet.put_u8(device_number);
|
||||
packet.put_u8(interface_number);
|
||||
packet.put_u8(0x80); // direction (TX)
|
||||
packet.put_u32_le(client_data);
|
||||
packet.put_u8(seq_num);
|
||||
packet.put_bytes(0x00, 7); // reserved
|
||||
|
||||
packet.put(data);
|
||||
|
||||
packet
|
||||
}
|
||||
|
||||
|
||||
fn main() {
|
||||
let rt = tokio::runtime::Builder::new_current_thread().enable_io().build().unwrap();
|
||||
if let Err(e) = rt.block_on(run()) {
|
||||
error!("{e}");
|
||||
exit(1);
|
||||
}
|
||||
}
|
778
src/scsi/emulation/block_device.rs
Normal file
778
src/scsi/emulation/block_device.rs
Normal file
|
@ -0,0 +1,778 @@
|
|||
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
|
||||
|
||||
use std::{
|
||||
convert::{TryFrom, TryInto},
|
||||
fs::File,
|
||||
io::{self, Read, Write},
|
||||
num::{NonZeroU32, NonZeroU64, TryFromIntError},
|
||||
ops::{Add, Div, Mul, Sub},
|
||||
os::unix::prelude::*,
|
||||
};
|
||||
|
||||
use log::{debug, error, warn};
|
||||
|
||||
use super::{
|
||||
command::{
|
||||
parse_opcode, CommandType, LunSpecificCommand, ModePageSelection, ModeSensePageControl,
|
||||
ParseOpcodeResult, ReportSupportedOpCodesMode, SenseFormat, VpdPage, OPCODES,
|
||||
},
|
||||
mode_page::ModePage,
|
||||
response_data::{respond_standard_inquiry_data, SilentlyTruncate},
|
||||
target::{LogicalUnit, LunRequest},
|
||||
};
|
||||
use crate::scsi::{sense, CmdError, CmdOutput, TaskAttr};
|
||||
|
||||
pub(crate) enum MediumRotationRate {
|
||||
Unreported,
|
||||
NonRotating,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, PartialOrd)]
|
||||
pub(crate) struct ByteOffset(u64);
|
||||
impl From<u64> for ByteOffset {
|
||||
fn from(value: u64) -> Self {
|
||||
ByteOffset(value)
|
||||
}
|
||||
}
|
||||
impl From<ByteOffset> for u64 {
|
||||
fn from(value: ByteOffset) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
impl Div<BlockSize> for ByteOffset {
|
||||
type Output = BlockOffset;
|
||||
|
||||
fn div(self, rhs: BlockSize) -> Self::Output {
|
||||
BlockOffset(self.0 / NonZeroU64::from(rhs.0))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, PartialOrd)]
|
||||
pub(crate) struct BlockSize(NonZeroU32);
|
||||
impl From<BlockSize> for u32 {
|
||||
fn from(value: BlockSize) -> Self {
|
||||
u32::from(value.0)
|
||||
}
|
||||
}
|
||||
impl TryFrom<u32> for BlockSize {
|
||||
type Error = TryFromIntError;
|
||||
|
||||
fn try_from(value: u32) -> Result<Self, Self::Error> {
|
||||
Ok(BlockSize(NonZeroU32::try_from(value)?))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, PartialOrd)]
|
||||
pub(crate) struct BlockOffset(u64);
|
||||
impl From<BlockOffset> for u64 {
|
||||
fn from(value: BlockOffset) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
impl From<u64> for BlockOffset {
|
||||
fn from(value: u64) -> Self {
|
||||
BlockOffset(value)
|
||||
}
|
||||
}
|
||||
impl Add<BlockOffset> for BlockOffset {
|
||||
type Output = BlockOffset;
|
||||
|
||||
fn add(self, rhs: BlockOffset) -> Self::Output {
|
||||
BlockOffset(self.0 + rhs.0)
|
||||
}
|
||||
}
|
||||
impl Sub<BlockOffset> for BlockOffset {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, rhs: BlockOffset) -> Self::Output {
|
||||
BlockOffset(self.0 - rhs.0)
|
||||
}
|
||||
}
|
||||
impl Mul<BlockSize> for BlockOffset {
|
||||
type Output = ByteOffset;
|
||||
|
||||
fn mul(self, rhs: BlockSize) -> Self::Output {
|
||||
ByteOffset(self.0 * u64::from(NonZeroU64::from(rhs.0)))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait BlockDeviceBackend: Send + Sync {
|
||||
fn read_exact_at(&mut self, buf: &mut [u8], offset: ByteOffset) -> io::Result<()>;
|
||||
fn write_exact_at(&mut self, buf: &[u8], offset: ByteOffset) -> io::Result<()>;
|
||||
fn size_in_blocks(&mut self) -> io::Result<BlockOffset>;
|
||||
fn block_size(&self) -> BlockSize;
|
||||
fn sync(&mut self) -> io::Result<()>;
|
||||
}
|
||||
|
||||
pub(crate) struct FileBackend {
|
||||
file: File,
|
||||
block_size: BlockSize,
|
||||
}
|
||||
|
||||
impl FileBackend {
|
||||
pub fn new(file: File) -> Self {
|
||||
Self {
|
||||
file,
|
||||
block_size: BlockSize::try_from(512).expect("512 is valid BlockSize"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockDeviceBackend for FileBackend {
|
||||
fn read_exact_at(&mut self, buf: &mut [u8], offset: ByteOffset) -> io::Result<()> {
|
||||
self.file.read_exact_at(buf, u64::from(offset))
|
||||
}
|
||||
|
||||
fn write_exact_at(&mut self, buf: &[u8], offset: ByteOffset) -> io::Result<()> {
|
||||
self.file.write_all_at(buf, u64::from(offset))
|
||||
}
|
||||
|
||||
fn size_in_blocks(&mut self) -> io::Result<BlockOffset> {
|
||||
let len = ByteOffset::from(self.file.metadata()?.len());
|
||||
assert!(u64::from(len) % NonZeroU64::from(self.block_size.0) == 0);
|
||||
Ok(len / self.block_size)
|
||||
}
|
||||
|
||||
fn block_size(&self) -> BlockSize {
|
||||
self.block_size
|
||||
}
|
||||
|
||||
fn sync(&mut self) -> io::Result<()> {
|
||||
self.file.sync_data()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct BlockDevice<T: BlockDeviceBackend> {
|
||||
backend: T,
|
||||
write_protected: bool,
|
||||
rotation_rate: MediumRotationRate,
|
||||
}
|
||||
|
||||
impl<T: BlockDeviceBackend> BlockDevice<T> {
|
||||
pub(crate) const fn new(backend: T) -> Self {
|
||||
Self {
|
||||
backend,
|
||||
write_protected: false,
|
||||
rotation_rate: MediumRotationRate::Unreported,
|
||||
}
|
||||
}
|
||||
|
||||
fn read_blocks(&mut self, lba: BlockOffset, blocks: BlockOffset) -> io::Result<Vec<u8>> {
|
||||
// TODO: Ideally, this would be a read_vectored directly into guest
|
||||
// address space. Instead, we have an allocation and several copies.
|
||||
|
||||
let mut ret = vec![
|
||||
0;
|
||||
usize::try_from(u64::from(blocks * self.backend.block_size()))
|
||||
.expect("block length in bytes should fit usize")
|
||||
];
|
||||
|
||||
self.backend
|
||||
.read_exact_at(&mut ret[..], lba * self.backend.block_size())?;
|
||||
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
fn write_blocks(
|
||||
&mut self,
|
||||
lba: BlockOffset,
|
||||
blocks: BlockOffset,
|
||||
reader: &mut dyn Read,
|
||||
) -> io::Result<()> {
|
||||
// TODO: Avoid the copies here.
|
||||
let mut buf = vec![
|
||||
0;
|
||||
usize::try_from(u64::from(blocks * self.backend.block_size()))
|
||||
.expect("block length in bytes should fit usize")
|
||||
];
|
||||
reader.read_exact(&mut buf)?;
|
||||
self.backend
|
||||
.write_exact_at(&buf, lba * self.backend.block_size())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_same_block(
|
||||
&mut self,
|
||||
lba_start: BlockOffset,
|
||||
block_count: BlockOffset,
|
||||
buf: &[u8],
|
||||
) -> io::Result<()> {
|
||||
let block_size = self.backend.block_size();
|
||||
for lba in u64::from(lba_start)..u64::from(lba_start + block_count) {
|
||||
let lba = BlockOffset(lba);
|
||||
self.backend.write_exact_at(buf, lba * block_size)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_write_protected(&mut self, wp: bool) {
|
||||
self.write_protected = wp;
|
||||
}
|
||||
|
||||
pub fn set_solid_state(&mut self, rotation_rate: MediumRotationRate) {
|
||||
self.rotation_rate = rotation_rate;
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: BlockDeviceBackend> LogicalUnit for BlockDevice<T> {
|
||||
fn execute_command(
|
||||
&mut self,
|
||||
data_in: &mut SilentlyTruncate<&mut dyn Write>,
|
||||
data_out: &mut dyn Read,
|
||||
req: LunRequest,
|
||||
command: LunSpecificCommand,
|
||||
) -> Result<CmdOutput, CmdError> {
|
||||
if req.crn != 0 {
|
||||
// CRN is a weird bit of the protocol we wouldn't ever expect to be used over
|
||||
// virtio-scsi; but it's allowed to set it non-zero
|
||||
warn!("Received non-zero CRN: {}", req.crn);
|
||||
}
|
||||
|
||||
if req.task_attr != TaskAttr::Simple {
|
||||
// virtio-scsi spec allows us to treat all task attrs as SIMPLE.
|
||||
warn!("Ignoring non-simple task attr of {:?}", req.task_attr);
|
||||
}
|
||||
|
||||
if req.prio != 0 {
|
||||
// My reading of SAM-6 is that priority is purely advisory, so it's fine to
|
||||
// ignore it.
|
||||
warn!("Ignoring non-zero priority of {}.", req.prio);
|
||||
}
|
||||
|
||||
if req.naca {
|
||||
// We don't support NACA, and say as much in our INQUIRY data, so if
|
||||
// we get it that's an error.
|
||||
warn!("Driver set NACA bit, which is unsupported.");
|
||||
return Ok(CmdOutput::check_condition(sense::INVALID_FIELD_IN_CDB));
|
||||
}
|
||||
|
||||
debug!("Incoming command: {:?}", command);
|
||||
|
||||
match command {
|
||||
LunSpecificCommand::TestUnitReady => Ok(CmdOutput::ok()),
|
||||
LunSpecificCommand::ReadCapacity10 => {
|
||||
match self.backend.size_in_blocks() {
|
||||
Ok(size) => {
|
||||
// READ CAPACITY (10) returns a 32-bit LBA, which may not be enough. If it
|
||||
// isn't, we're supposed to return 0xffff_ffff and hope the driver gets the
|
||||
// memo and uses the newer READ CAPACITY (16).
|
||||
|
||||
// n.b. this is the last block, ie (length-1), not length
|
||||
let final_block: u32 = u64::from(size - BlockOffset(1))
|
||||
.try_into()
|
||||
.unwrap_or(0xffff_ffff);
|
||||
let block_size: u32 = u32::from(self.backend.block_size());
|
||||
|
||||
data_in
|
||||
.write_all(&u32::to_be_bytes(final_block))
|
||||
.map_err(CmdError::DataIn)?;
|
||||
data_in
|
||||
.write_all(&u32::to_be_bytes(block_size))
|
||||
.map_err(CmdError::DataIn)?;
|
||||
|
||||
Ok(CmdOutput::ok())
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Error getting image size: {}", e);
|
||||
// TODO: Is this a reasonable sense code to send?
|
||||
Ok(CmdOutput::check_condition(sense::UNRECOVERED_READ_ERROR))
|
||||
}
|
||||
}
|
||||
}
|
||||
LunSpecificCommand::ReadCapacity16 => {
|
||||
match self.backend.size_in_blocks() {
|
||||
Ok(size) => {
|
||||
// n.b. this is the last block, ie (length-1), not length
|
||||
let final_block = u64::from(size - BlockOffset(1));
|
||||
let block_size = u32::from(self.backend.block_size());
|
||||
|
||||
data_in
|
||||
.write_all(&u64::to_be_bytes(final_block))
|
||||
.map_err(CmdError::DataIn)?;
|
||||
data_in
|
||||
.write_all(&u32::to_be_bytes(block_size))
|
||||
.map_err(CmdError::DataIn)?;
|
||||
|
||||
// no protection stuff; 1-to-1 logical/physical blocks
|
||||
data_in.write_all(&[0, 0]).map_err(CmdError::DataIn)?;
|
||||
|
||||
// top 2 bits: thin provisioning stuff; other 14 bits are lowest
|
||||
// aligned LBA, which is zero
|
||||
data_in
|
||||
.write_all(&[0b1100_0000, 0])
|
||||
.map_err(CmdError::DataIn)?;
|
||||
|
||||
// reserved
|
||||
data_in.write_all(&[0; 16]).map_err(CmdError::DataIn)?;
|
||||
|
||||
Ok(CmdOutput::ok())
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Error getting image size: {}", e);
|
||||
// TODO: Is this a reasonable sense code to send?
|
||||
Ok(CmdOutput::check_condition(sense::UNRECOVERED_READ_ERROR))
|
||||
}
|
||||
}
|
||||
}
|
||||
LunSpecificCommand::ModeSense6 { mode_page, pc, dbd } => {
|
||||
// we use this for the pages array if we only need a single element; lifetime
|
||||
// rules mean it has to be declared here
|
||||
let single_page_array: [ModePage; 1];
|
||||
|
||||
let pages = match mode_page {
|
||||
ModePageSelection::Single(x) => {
|
||||
single_page_array = [x];
|
||||
&single_page_array
|
||||
}
|
||||
ModePageSelection::AllPageZeros => ModePage::ALL_ZERO,
|
||||
};
|
||||
|
||||
let pages_len: u32 = pages.iter().map(|x| u32::from(x.page_length() + 2)).sum();
|
||||
// SPC-6r05, 7.5.6: "Logical units that support more than 256 bytes of block
|
||||
// descriptors and mode pages should implement ten-byte mode commands. The MODE
|
||||
// DATA LENGTH field in the six-byte CDB header limits the transferred data to
|
||||
// 256 bytes."
|
||||
// Unclear what exactly we're supposed to do if we have more than 256 bytes of
|
||||
// mode pages and get sent a MODE SENSE (6). In any case, we don't at the
|
||||
// moment; if we ever get that much, this unwrap() will start
|
||||
// crashing us and we can figure out what to do.
|
||||
let pages_len = u8::try_from(pages_len).unwrap();
|
||||
|
||||
// mode parameter header
|
||||
data_in
|
||||
.write_all(&[
|
||||
pages_len + 3, // size in bytes after this one
|
||||
0, // medium type - 0 for SBC
|
||||
if self.write_protected {
|
||||
0b1001_0000 // WP, support DPOFUA
|
||||
} else {
|
||||
0b0001_0000 // support DPOFUA
|
||||
},
|
||||
0, // block desc length
|
||||
])
|
||||
.map_err(CmdError::DataIn)?;
|
||||
|
||||
if !dbd {
|
||||
// TODO: Block descriptors are optional, so we currently
|
||||
// don't provide them. Does any driver
|
||||
// actually use them?
|
||||
}
|
||||
|
||||
for page in pages {
|
||||
match pc {
|
||||
ModeSensePageControl::Current | ModeSensePageControl::Default => {
|
||||
page.write(data_in).map_err(CmdError::DataIn)?;
|
||||
}
|
||||
ModeSensePageControl::Changeable => {
|
||||
// SPC-6 6.14.3: "If the logical unit does not
|
||||
// implement changeable parameters mode pages and
|
||||
// the device server receives a MODE SENSE command
|
||||
// with 01b in the PC field, then the device server
|
||||
// shall terminate the command with CHECK CONDITION
|
||||
// status, with the sense key set to ILLEGAL
|
||||
// REQUEST, and the additional sense code set to
|
||||
// INVALID FIELD IN CDB."
|
||||
return Ok(CmdOutput::check_condition(sense::INVALID_FIELD_IN_CDB));
|
||||
}
|
||||
ModeSensePageControl::Saved => {
|
||||
return Ok(CmdOutput::check_condition(
|
||||
sense::SAVING_PARAMETERS_NOT_SUPPORTED,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(CmdOutput::ok())
|
||||
}
|
||||
LunSpecificCommand::Read10 {
|
||||
dpo,
|
||||
fua,
|
||||
lba,
|
||||
transfer_length,
|
||||
} => {
|
||||
if dpo {
|
||||
// DPO is just a hint that the guest probably won't access
|
||||
// this any time soon, so we can ignore it
|
||||
debug!("Silently ignoring DPO flag");
|
||||
}
|
||||
|
||||
if fua {
|
||||
// Somewhat weirdly, SCSI supports FUA on reads. Here's the
|
||||
// key bit: "A force unit access (FUA) bit set to one
|
||||
// specifies that the device server shall read the logical
|
||||
// blocks from… the medium. If the FUA bit is set to one
|
||||
// and a volatile cache contains a more recent version of a
|
||||
// logical block than… the medium, then, before reading the
|
||||
// logical block, the device server shall write the logical
|
||||
// block to… the medium."
|
||||
|
||||
// I guess the idea is that you can read something back, and
|
||||
// be absolutely sure what you just read will persist.
|
||||
|
||||
// So for our purposes, we need to make sure whatever we
|
||||
// return has been saved to disk. fsync()ing the whole image
|
||||
// is a bit blunt, but does the trick.
|
||||
|
||||
if let Err(e) = self.backend.sync() {
|
||||
error!("Error syncing file: {}", e);
|
||||
return Ok(CmdOutput::check_condition(sense::TARGET_FAILURE));
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore group number: AFAICT, it's for separating reads from different
|
||||
// workloads in performance metrics, and we don't report anything like that
|
||||
|
||||
let size = match self.backend.size_in_blocks() {
|
||||
Ok(size) => size,
|
||||
Err(e) => {
|
||||
error!("Error getting image size for read: {}", e);
|
||||
return Ok(CmdOutput::check_condition(sense::UNRECOVERED_READ_ERROR));
|
||||
}
|
||||
};
|
||||
|
||||
let lba = BlockOffset(lba.into());
|
||||
let transfer_length = BlockOffset(transfer_length.into());
|
||||
|
||||
if lba + transfer_length > size {
|
||||
return Ok(CmdOutput::check_condition(
|
||||
sense::LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE,
|
||||
));
|
||||
}
|
||||
|
||||
let read_result = self.read_blocks(lba, transfer_length);
|
||||
|
||||
match read_result {
|
||||
Ok(bytes) => {
|
||||
data_in.write_all(&bytes[..]).map_err(CmdError::DataIn)?;
|
||||
Ok(CmdOutput::ok())
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Error reading image: {}", e);
|
||||
Ok(CmdOutput::check_condition(sense::UNRECOVERED_READ_ERROR))
|
||||
}
|
||||
}
|
||||
}
|
||||
LunSpecificCommand::Write10 {
|
||||
dpo,
|
||||
fua,
|
||||
lba,
|
||||
transfer_length,
|
||||
} => {
|
||||
if dpo {
|
||||
// DPO is just a hint that the guest probably won't access
|
||||
// this any time soon, so we can ignore it
|
||||
debug!("Silently ignoring DPO flag");
|
||||
}
|
||||
|
||||
let size = match self.backend.size_in_blocks() {
|
||||
Ok(size) => size,
|
||||
Err(e) => {
|
||||
error!("Error getting image size for read: {}", e);
|
||||
return Ok(CmdOutput::check_condition(sense::TARGET_FAILURE));
|
||||
}
|
||||
};
|
||||
|
||||
let lba = BlockOffset(lba.into());
|
||||
let transfer_length = BlockOffset(transfer_length.into());
|
||||
|
||||
if lba + transfer_length > size {
|
||||
return Ok(CmdOutput::check_condition(
|
||||
sense::LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE,
|
||||
));
|
||||
}
|
||||
|
||||
let write_result = self.write_blocks(lba, transfer_length, data_out);
|
||||
|
||||
if fua {
|
||||
if let Err(e) = self.backend.sync() {
|
||||
error!("Error syncing file: {}", e);
|
||||
return Ok(CmdOutput::check_condition(sense::TARGET_FAILURE));
|
||||
}
|
||||
}
|
||||
|
||||
match write_result {
|
||||
Ok(()) => Ok(CmdOutput::ok()),
|
||||
Err(e) => {
|
||||
error!("Error writing to block device: {}", e);
|
||||
Ok(CmdOutput::check_condition(sense::TARGET_FAILURE))
|
||||
}
|
||||
}
|
||||
}
|
||||
LunSpecificCommand::WriteSame16 {
|
||||
lba,
|
||||
number_of_logical_blocks,
|
||||
anchor,
|
||||
} => {
|
||||
// We do not support block provisioning
|
||||
if anchor {
|
||||
return Ok(CmdOutput::check_condition(sense::INVALID_FIELD_IN_CDB));
|
||||
}
|
||||
|
||||
// This command can be used to unmap/discard a region of blocks...
|
||||
// TODO: Do something smarter and punch holes into the backend,
|
||||
// for now we will just write A LOT of zeros in a very inefficient way.
|
||||
|
||||
let size = match self.backend.size_in_blocks() {
|
||||
Ok(size) => size,
|
||||
Err(e) => {
|
||||
error!("Error getting image size for read: {}", e);
|
||||
return Ok(CmdOutput::check_condition(sense::UNRECOVERED_READ_ERROR));
|
||||
}
|
||||
};
|
||||
|
||||
let lba = BlockOffset(lba);
|
||||
let number_of_logical_blocks = BlockOffset(number_of_logical_blocks.into());
|
||||
|
||||
if lba + number_of_logical_blocks > size {
|
||||
return Ok(CmdOutput::check_condition(
|
||||
sense::LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE,
|
||||
));
|
||||
}
|
||||
|
||||
let mut buf = vec![
|
||||
0;
|
||||
usize::try_from(u32::from(self.backend.block_size()))
|
||||
.expect("block_size should fit usize")
|
||||
];
|
||||
let read_result = data_out.read_exact(&mut buf);
|
||||
if let Err(e) = read_result {
|
||||
error!("Error reading from data_out: {}", e);
|
||||
return Ok(CmdOutput::check_condition(sense::TARGET_FAILURE));
|
||||
}
|
||||
|
||||
let write_result = self.write_same_block(lba, number_of_logical_blocks, &buf);
|
||||
|
||||
match write_result {
|
||||
Ok(()) => Ok(CmdOutput::ok()),
|
||||
Err(e) => {
|
||||
error!("Error writing to block device: {}", e);
|
||||
Ok(CmdOutput::check_condition(sense::TARGET_FAILURE))
|
||||
}
|
||||
}
|
||||
}
|
||||
LunSpecificCommand::Inquiry(page_code) => {
|
||||
// top 3 bits 0: peripheral device code = exists and ready
|
||||
// bottom 5 bits 0: device type = block device
|
||||
data_in.write_all(&[0]).map_err(CmdError::DataIn)?;
|
||||
|
||||
if let Some(code) = page_code {
|
||||
let mut out = vec![];
|
||||
match code {
|
||||
VpdPage::SupportedVpdPages => {
|
||||
out.push(VpdPage::SupportedVpdPages.into());
|
||||
out.push(VpdPage::BlockDeviceCharacteristics.into());
|
||||
out.push(VpdPage::LogicalBlockProvisioning.into());
|
||||
}
|
||||
VpdPage::BlockDeviceCharacteristics => {
|
||||
let rotation_rate: u16 = match self.rotation_rate {
|
||||
MediumRotationRate::Unreported => 0,
|
||||
MediumRotationRate::NonRotating => 1,
|
||||
};
|
||||
out.extend_from_slice(&rotation_rate.to_be_bytes());
|
||||
// nothing worth setting in the rest
|
||||
out.extend_from_slice(&[0; 58]);
|
||||
}
|
||||
VpdPage::LogicalBlockProvisioning => {
|
||||
out.push(0); // don't support threshold sets
|
||||
out.push(0b1110_0100); // support unmapping w/ UNMAP
|
||||
// and WRITE SAME (10 & 16),
|
||||
// don't support anchored
|
||||
// LBAs or group descriptors
|
||||
out.push(0b0000_0010); // thin provisioned
|
||||
out.push(0); // no threshold % support
|
||||
}
|
||||
_ => return Ok(CmdOutput::check_condition(sense::INVALID_FIELD_IN_CDB)),
|
||||
}
|
||||
|
||||
data_in
|
||||
.write_all(&[code.into()])
|
||||
.map_err(CmdError::DataIn)?;
|
||||
data_in
|
||||
.write_all(
|
||||
&u16::try_from(out.len())
|
||||
.expect("VPD page < 2^16 bits")
|
||||
.to_be_bytes(),
|
||||
)
|
||||
.map_err(CmdError::DataIn)?;
|
||||
data_in.write_all(&out).map_err(CmdError::DataIn)?;
|
||||
} else {
|
||||
respond_standard_inquiry_data(data_in).map_err(CmdError::DataIn)?;
|
||||
}
|
||||
|
||||
Ok(CmdOutput::ok())
|
||||
}
|
||||
LunSpecificCommand::ReportSupportedOperationCodes { rctd, mode } => {
|
||||
// helpers for output data format
|
||||
fn one_command_supported(
|
||||
data_in: &mut impl Write,
|
||||
ty: CommandType,
|
||||
) -> io::Result<()> {
|
||||
data_in.write_all(&[0])?; // unused flags
|
||||
data_in.write_all(&[0b0000_0011])?; // supported, don't set a bunch of flags
|
||||
let tpl = ty.cdb_template();
|
||||
data_in.write_all(
|
||||
&u16::try_from(tpl.len())
|
||||
.expect("length of TPL to be same as CDB")
|
||||
.to_be_bytes(),
|
||||
)?;
|
||||
data_in.write_all(tpl)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn one_command_not_supported(data_in: &mut impl Write) -> io::Result<()> {
|
||||
data_in.write_all(&[0])?; // unused flags
|
||||
data_in.write_all(&[0b0000_0001])?; // not supported
|
||||
data_in.write_all(&[0; 2])?; // cdb len
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn timeout_descriptor(data_in: &mut impl Write) -> io::Result<()> {
|
||||
// timeout descriptor
|
||||
data_in.write_all(&0xa_u16.to_be_bytes())?; // len
|
||||
data_in.write_all(&[0, 0])?; // reserved, cmd specific
|
||||
data_in.write_all(&0_u32.to_be_bytes())?;
|
||||
data_in.write_all(&0_u32.to_be_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
match mode {
|
||||
ReportSupportedOpCodesMode::All => {
|
||||
let cmd_len = if rctd { 20 } else { 8 };
|
||||
let len = u32::try_from(OPCODES.len() * cmd_len)
|
||||
.expect("less than (2^32 / 20) ~= 2^27 opcodes");
|
||||
data_in
|
||||
.write_all(&len.to_be_bytes())
|
||||
.map_err(CmdError::DataIn)?;
|
||||
|
||||
for &(ty, (opcode, sa)) in OPCODES {
|
||||
data_in.write_all(&[opcode]).map_err(CmdError::DataIn)?;
|
||||
data_in.write_all(&[0]).map_err(CmdError::DataIn)?; // reserved
|
||||
data_in
|
||||
.write_all(&sa.unwrap_or(0).to_be_bytes())
|
||||
.map_err(CmdError::DataIn)?;
|
||||
data_in.write_all(&[0]).map_err(CmdError::DataIn)?; // reserved
|
||||
|
||||
let ctdp: u8 = if rctd { 0b10 } else { 0b00 };
|
||||
let servactv = u8::from(sa.is_some());
|
||||
data_in
|
||||
.write_all(&[ctdp | servactv])
|
||||
.map_err(CmdError::DataIn)?;
|
||||
|
||||
data_in
|
||||
.write_all(
|
||||
&u16::try_from(ty.cdb_template().len())
|
||||
.expect("length of TPL to be same as CDB")
|
||||
.to_be_bytes(),
|
||||
)
|
||||
.map_err(CmdError::DataIn)?;
|
||||
|
||||
if rctd {
|
||||
timeout_descriptor(data_in).map_err(CmdError::DataIn)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
ReportSupportedOpCodesMode::OneCommand(opcode) => match parse_opcode(opcode) {
|
||||
ParseOpcodeResult::Command(ty) => {
|
||||
one_command_supported(data_in, ty).map_err(CmdError::DataIn)?;
|
||||
|
||||
if rctd {
|
||||
timeout_descriptor(data_in).map_err(CmdError::DataIn)?;
|
||||
}
|
||||
}
|
||||
ParseOpcodeResult::ServiceAction(_) => {
|
||||
return Ok(CmdOutput::check_condition(sense::INVALID_FIELD_IN_CDB));
|
||||
}
|
||||
ParseOpcodeResult::Invalid => {
|
||||
warn!("Reporting that we don't support command {:#2x}. It might be worth adding.", opcode);
|
||||
one_command_not_supported(data_in).map_err(CmdError::DataIn)?;
|
||||
}
|
||||
},
|
||||
ReportSupportedOpCodesMode::OneServiceAction(opcode, sa) => {
|
||||
match parse_opcode(opcode) {
|
||||
ParseOpcodeResult::Command(_) => {
|
||||
return Ok(CmdOutput::check_condition(sense::INVALID_FIELD_IN_CDB))
|
||||
}
|
||||
ParseOpcodeResult::ServiceAction(unparsed_sa) => {
|
||||
if let Some(ty) = unparsed_sa.parse(sa) {
|
||||
one_command_supported(data_in, ty).map_err(CmdError::DataIn)?;
|
||||
|
||||
if rctd {
|
||||
timeout_descriptor(data_in).map_err(CmdError::DataIn)?;
|
||||
}
|
||||
} else {
|
||||
warn!("Reporting that we don't support command {:#2x}/{:#2x}. It might be worth adding.", opcode, sa);
|
||||
one_command_not_supported(data_in).map_err(CmdError::DataIn)?;
|
||||
}
|
||||
}
|
||||
ParseOpcodeResult::Invalid => {
|
||||
// the spec isn't super clear what we're supposed to do here, but I
|
||||
// think an invalid opcode is one for which our implementation
|
||||
// "does not implement service actions", so we say invalid field in
|
||||
// CDB
|
||||
warn!("Reporting that we don't support command {:#2x}/{:#2x}. It might be worth adding.", opcode, sa);
|
||||
return Ok(CmdOutput::check_condition(sense::INVALID_FIELD_IN_CDB));
|
||||
}
|
||||
}
|
||||
}
|
||||
ReportSupportedOpCodesMode::OneCommandOrServiceAction(opcode, sa) => {
|
||||
match parse_opcode(opcode) {
|
||||
ParseOpcodeResult::Command(ty) => {
|
||||
if sa == 0 {
|
||||
one_command_supported(data_in, ty).map_err(CmdError::DataIn)?;
|
||||
|
||||
if rctd {
|
||||
timeout_descriptor(data_in).map_err(CmdError::DataIn)?;
|
||||
}
|
||||
} else {
|
||||
one_command_not_supported(data_in).map_err(CmdError::DataIn)?;
|
||||
}
|
||||
}
|
||||
ParseOpcodeResult::ServiceAction(unparsed_sa) => {
|
||||
if let Some(ty) = unparsed_sa.parse(sa) {
|
||||
one_command_supported(data_in, ty).map_err(CmdError::DataIn)?;
|
||||
|
||||
if rctd {
|
||||
timeout_descriptor(data_in).map_err(CmdError::DataIn)?;
|
||||
}
|
||||
} else {
|
||||
warn!("Reporting that we don't support command {:#2x}/{:#2x}. It might be worth adding.", opcode, sa);
|
||||
one_command_not_supported(data_in).map_err(CmdError::DataIn)?;
|
||||
}
|
||||
}
|
||||
ParseOpcodeResult::Invalid => {
|
||||
warn!("Reporting that we don't support command {:#2x}[/{:#2x}]. It might be worth adding.", opcode, sa);
|
||||
one_command_not_supported(data_in).map_err(CmdError::DataIn)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(CmdOutput::ok())
|
||||
}
|
||||
LunSpecificCommand::RequestSense(format) => {
|
||||
match format {
|
||||
SenseFormat::Fixed => {
|
||||
data_in
|
||||
.write_all(&sense::NO_ADDITIONAL_SENSE_INFORMATION.to_fixed_sense())
|
||||
.map_err(CmdError::DataIn)?;
|
||||
Ok(CmdOutput::ok())
|
||||
}
|
||||
SenseFormat::Descriptor => {
|
||||
// Don't support desciptor format.
|
||||
Ok(CmdOutput::check_condition(sense::INVALID_FIELD_IN_CDB))
|
||||
}
|
||||
}
|
||||
}
|
||||
LunSpecificCommand::SynchronizeCache10 => {
|
||||
// While SCSI allows just syncing a range, we just sync the entire file
|
||||
match self.backend.sync() {
|
||||
Ok(()) => Ok(CmdOutput::ok()),
|
||||
Err(e) => {
|
||||
error!("Error syncing block device: {}", e);
|
||||
Ok(CmdOutput::check_condition(sense::TARGET_FAILURE))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
681
src/scsi/emulation/command.rs
Normal file
681
src/scsi/emulation/command.rs
Normal file
|
@ -0,0 +1,681 @@
|
|||
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
|
||||
|
||||
//! Data structures and parsing code for SCSI commands. A rough overview:
|
||||
//! We need to deal with opcodes in two places: in parsing commands themselves,
|
||||
//! and in implementing REPORT SUPPORTED OPERATION CODES. Therefore, we parse
|
||||
//! commands in two steps. First, we parse the opcode (and sometimes service
|
||||
//! action) into a `CommandType` (a C-style enum containing just the commands,
|
||||
//! not their parameters), then using that, we parse the rest of the CDB and
|
||||
//! obtain a `Cdb`, which consists of a `Command`, an enum representing a
|
||||
//! command and its parameters, along with some fields shared across many or all
|
||||
//! commands.
|
||||
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
|
||||
use log::warn;
|
||||
use num_enum::TryFromPrimitive;
|
||||
|
||||
use crate::scsi::emulation::mode_page::ModePage;
|
||||
|
||||
/// One of the modes supported by SCSI's REPORT LUNS command.
|
||||
#[derive(PartialEq, Eq, TryFromPrimitive, Debug, Copy, Clone)]
|
||||
#[repr(u8)]
|
||||
pub(crate) enum ReportLunsSelectReport {
|
||||
NoWellKnown = 0x0,
|
||||
WellKnownOnly = 0x1,
|
||||
All = 0x2,
|
||||
Administrative = 0x10,
|
||||
TopLevel = 0x11,
|
||||
SameConglomerate = 0x12,
|
||||
}
|
||||
|
||||
/// A type of "vital product data" page returned by SCSI's INQUIRY command.
|
||||
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
|
||||
pub(crate) enum VpdPage {
|
||||
Ascii(u8),
|
||||
Ata, // *
|
||||
BlockDeviceCharacteristics, // *
|
||||
BlockDeviceCharacteristicsExt,
|
||||
BlockLimits, // *
|
||||
BlockLimitsExt,
|
||||
CfaProfile,
|
||||
DeviceConstituents,
|
||||
DeviceIdentification, // *
|
||||
ExtendedInquiry,
|
||||
FormatPresets,
|
||||
LogicalBlockProvisioning, // *
|
||||
ManagementNetworkAddresses,
|
||||
ModePagePolicy,
|
||||
PowerCondition,
|
||||
PowerConsumption,
|
||||
PortocolSpecificLogicalUnit,
|
||||
ProtocolSpecificPort,
|
||||
Referrals,
|
||||
ScsiFeatureSets,
|
||||
ScsiPorts,
|
||||
SoftwareInterfaceIdentification,
|
||||
SupportedVpdPages, // *
|
||||
ThirdPartyCopy,
|
||||
UnitSerialNumber, // *
|
||||
ZonedBlockDeviceCharacteristics, // *
|
||||
}
|
||||
// starred ones are ones Linux will use if available
|
||||
|
||||
#[derive(PartialEq, Eq, TryFromPrimitive, Debug, Copy, Clone)]
|
||||
#[repr(u8)]
|
||||
pub(crate) enum ModeSensePageControl {
|
||||
Current = 0b00,
|
||||
Changeable = 0b01,
|
||||
Default = 0b10,
|
||||
Saved = 0b11,
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for VpdPage {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(val: u8) -> Result<Self, ()> {
|
||||
match val {
|
||||
0x00 => Ok(Self::SupportedVpdPages),
|
||||
0x1..=0x7f => Ok(Self::Ascii(val)),
|
||||
0x80 => Ok(Self::UnitSerialNumber),
|
||||
0x83 => Ok(Self::DeviceIdentification),
|
||||
0x84 => Ok(Self::SoftwareInterfaceIdentification),
|
||||
0x85 => Ok(Self::ManagementNetworkAddresses),
|
||||
0x86 => Ok(Self::ExtendedInquiry),
|
||||
0x87 => Ok(Self::ModePagePolicy),
|
||||
0x88 => Ok(Self::ScsiPorts),
|
||||
0x89 => Ok(Self::Ata),
|
||||
0x8a => Ok(Self::PowerCondition),
|
||||
0x8b => Ok(Self::DeviceConstituents),
|
||||
0x8c => Ok(Self::CfaProfile),
|
||||
0x8d => Ok(Self::PowerConsumption),
|
||||
0x8f => Ok(Self::ThirdPartyCopy),
|
||||
0x90 => Ok(Self::PortocolSpecificLogicalUnit),
|
||||
0x91 => Ok(Self::ProtocolSpecificPort),
|
||||
0x92 => Ok(Self::ScsiFeatureSets),
|
||||
0xb0 => Ok(Self::BlockLimits),
|
||||
0xb1 => Ok(Self::BlockDeviceCharacteristics),
|
||||
0xb2 => Ok(Self::LogicalBlockProvisioning),
|
||||
0xb3 => Ok(Self::Referrals),
|
||||
0xb5 => Ok(Self::BlockDeviceCharacteristicsExt),
|
||||
0xb6 => Ok(Self::ZonedBlockDeviceCharacteristics),
|
||||
0xb7 => Ok(Self::BlockLimitsExt),
|
||||
0xb8 => Ok(Self::FormatPresets),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<VpdPage> for u8 {
|
||||
fn from(pc: VpdPage) -> Self {
|
||||
match pc {
|
||||
VpdPage::Ascii(val) => val,
|
||||
VpdPage::Ata => 0x89,
|
||||
VpdPage::BlockDeviceCharacteristics => 0xb1,
|
||||
VpdPage::BlockDeviceCharacteristicsExt => 0xb5,
|
||||
VpdPage::BlockLimits => 0xb0,
|
||||
VpdPage::BlockLimitsExt => 0xb7,
|
||||
VpdPage::CfaProfile => 0x8c,
|
||||
VpdPage::DeviceConstituents => 0x8b,
|
||||
VpdPage::DeviceIdentification => 0x83,
|
||||
VpdPage::ExtendedInquiry => 0x86,
|
||||
VpdPage::FormatPresets => 0xb8,
|
||||
VpdPage::LogicalBlockProvisioning => 0xb2,
|
||||
VpdPage::ManagementNetworkAddresses => 0x85,
|
||||
VpdPage::ModePagePolicy => 0x87,
|
||||
VpdPage::PowerCondition => 0x8a,
|
||||
VpdPage::PowerConsumption => 0x8d,
|
||||
VpdPage::PortocolSpecificLogicalUnit => 0x90,
|
||||
VpdPage::ProtocolSpecificPort => 0x91,
|
||||
VpdPage::Referrals => 0xb3,
|
||||
VpdPage::ScsiFeatureSets => 0x92,
|
||||
VpdPage::ScsiPorts => 0x88,
|
||||
VpdPage::SoftwareInterfaceIdentification => 0x84,
|
||||
VpdPage::SupportedVpdPages => 0x00,
|
||||
VpdPage::ThirdPartyCopy => 0x8f,
|
||||
VpdPage::UnitSerialNumber => 0x80,
|
||||
VpdPage::ZonedBlockDeviceCharacteristics => 0xb6,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub(crate) enum SenseFormat {
|
||||
Fixed,
|
||||
Descriptor,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub(crate) enum ModePageSelection {
|
||||
AllPageZeros,
|
||||
Single(ModePage),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum LunIndependentCommand {
|
||||
ReportLuns(ReportLunsSelectReport),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum LunSpecificCommand {
|
||||
Inquiry(Option<VpdPage>),
|
||||
ModeSense6 {
|
||||
pc: ModeSensePageControl,
|
||||
mode_page: ModePageSelection,
|
||||
/// Disable block descriptors
|
||||
dbd: bool,
|
||||
},
|
||||
Read10 {
|
||||
/// Disable page out (i.e. hint that this page won't be accessed again
|
||||
/// soon, so we shouldn't bother caching it)
|
||||
dpo: bool,
|
||||
/// Force unit access (i.e. bypass cache)
|
||||
fua: bool,
|
||||
lba: u32,
|
||||
transfer_length: u16,
|
||||
},
|
||||
Write10 {
|
||||
/// Disable page out (i.e. hint that this page won't be accessed again
|
||||
/// soon, so we shouldn't bother caching it)
|
||||
dpo: bool,
|
||||
/// Force unit access (i.e. bypass cache)
|
||||
fua: bool,
|
||||
lba: u32,
|
||||
transfer_length: u16,
|
||||
},
|
||||
WriteSame16 {
|
||||
lba: u64,
|
||||
number_of_logical_blocks: u32,
|
||||
anchor: bool,
|
||||
},
|
||||
ReadCapacity10,
|
||||
ReadCapacity16,
|
||||
ReportSupportedOperationCodes {
|
||||
/// SCSI RCTD bit: whether we should include timeout descriptors.
|
||||
rctd: bool,
|
||||
mode: ReportSupportedOpCodesMode,
|
||||
},
|
||||
RequestSense(SenseFormat),
|
||||
TestUnitReady,
|
||||
SynchronizeCache10,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum Command {
|
||||
LunIndependentCommand(LunIndependentCommand),
|
||||
LunSpecificCommand(LunSpecificCommand),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(crate) enum CommandType {
|
||||
Inquiry,
|
||||
ModeSense6,
|
||||
Read10,
|
||||
ReadCapacity10,
|
||||
ReadCapacity16,
|
||||
ReportLuns,
|
||||
ReportSupportedOperationCodes,
|
||||
RequestSense,
|
||||
TestUnitReady,
|
||||
Write10,
|
||||
WriteSame16,
|
||||
SynchronizeCache10,
|
||||
}
|
||||
|
||||
pub(crate) const OPCODES: &[(CommandType, (u8, Option<u16>))] = &[
|
||||
(CommandType::TestUnitReady, (0x0, None)),
|
||||
(CommandType::RequestSense, (0x3, None)),
|
||||
(CommandType::Inquiry, (0x12, None)),
|
||||
(CommandType::ModeSense6, (0x1a, None)),
|
||||
(CommandType::ReadCapacity10, (0x25, None)),
|
||||
(CommandType::Read10, (0x28, None)),
|
||||
(CommandType::Write10, (0x2a, None)),
|
||||
(CommandType::SynchronizeCache10, (0x35, None)),
|
||||
(CommandType::WriteSame16, (0x93, None)),
|
||||
(CommandType::ReadCapacity16, (0x9e, Some(0x10))),
|
||||
(CommandType::ReportLuns, (0xa0, None)),
|
||||
(
|
||||
CommandType::ReportSupportedOperationCodes,
|
||||
(0xa3, Some(0xc)),
|
||||
),
|
||||
];
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct UnparsedServiceAction(u8);
|
||||
impl UnparsedServiceAction {
|
||||
pub fn parse(self, service_action: u16) -> Option<CommandType> {
|
||||
OPCODES
|
||||
.iter()
|
||||
.find(|(_, opcode)| *opcode == (self.0, Some(service_action)))
|
||||
.map(|&(ty, _)| ty)
|
||||
}
|
||||
}
|
||||
|
||||
/// See `parse_opcode`
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) enum ParseOpcodeResult {
|
||||
/// The opcode represents a single command.
|
||||
Command(CommandType),
|
||||
/// The opcode requires a service action.
|
||||
ServiceAction(UnparsedServiceAction),
|
||||
/// The opcode is invalid.
|
||||
Invalid,
|
||||
}
|
||||
|
||||
/// Determine the command that corresponds to a SCSI opcode.
|
||||
///
|
||||
/// This is a little weird. Most SCSI commands are just identified by the
|
||||
/// opcode - the first byte of the CDB - but some opcodes require a second
|
||||
/// byte, called the service action. Generally, each distinct service action
|
||||
/// value is treated as a first-class command. But there's some weirdness
|
||||
/// around parsing, especially with invalid commands: sometimes, we're
|
||||
/// expected to behave differently for a valid opcode with an invalid
|
||||
/// service action vs an invalid opcode.
|
||||
///
|
||||
/// To allow for this, we have a two-step parsing API. First, a caller
|
||||
/// calls `parse_opcode` with the first byte of the CDB. This could return
|
||||
/// three things:
|
||||
/// - `Command`: the opcode corresponded to a single-byte command; we're done.
|
||||
/// - `Invalid`: the opcode isn't recognized at all; we're done.
|
||||
/// - `ServiceAction`: the opcode is the first byte of a service action; the
|
||||
/// caller needs to call .parse() on the `UnparsedServiceAction` we returned
|
||||
/// with the service action byte.
|
||||
pub(crate) fn parse_opcode(opcode: u8) -> ParseOpcodeResult {
|
||||
let found = OPCODES.iter().find(|(_, (x, _))| *x == opcode);
|
||||
match found {
|
||||
Some(&(ty, (_, None))) => ParseOpcodeResult::Command(ty),
|
||||
Some((_, (_, Some(_)))) => {
|
||||
// we found some service action that uses this opcode; so this is a
|
||||
// service action opcode, and we need the service action
|
||||
ParseOpcodeResult::ServiceAction(UnparsedServiceAction(opcode))
|
||||
}
|
||||
None => ParseOpcodeResult::Invalid,
|
||||
}
|
||||
}
|
||||
|
||||
impl CommandType {
|
||||
fn from_cdb(cdb: &[u8]) -> Result<Self, ParseError> {
|
||||
// TODO: Variable-length CDBs put the service action in a different
|
||||
// place. This'll need to change if we ever support those. IIRC, Linux
|
||||
// doesn't ever use them, so it may never be relevant.
|
||||
match parse_opcode(cdb[0]) {
|
||||
ParseOpcodeResult::Command(ty) => Ok(ty),
|
||||
ParseOpcodeResult::ServiceAction(sa) => sa
|
||||
.parse(u16::from(cdb[1] & 0b0001_1111))
|
||||
.ok_or(ParseError::InvalidField),
|
||||
ParseOpcodeResult::Invalid => Err(ParseError::InvalidCommand),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the SCSI "CDB usage data" (see SPC-6 6.34.3) for this command
|
||||
/// type.
|
||||
///
|
||||
/// Basically, this consists of a structure the size of the CDB for the
|
||||
/// command, starting with the opcode and service action (if any), then
|
||||
/// proceeding to a bitmap of fields we recognize.
|
||||
pub const fn cdb_template(self) -> &'static [u8] {
|
||||
match self {
|
||||
Self::TestUnitReady => &[
|
||||
0x0,
|
||||
0b0000_0000,
|
||||
0b0000_0000,
|
||||
0b0000_0000,
|
||||
0b0000_0000,
|
||||
0b0000_0100,
|
||||
],
|
||||
Self::RequestSense => &[
|
||||
0x3,
|
||||
0b0000_0001,
|
||||
0b0000_0000,
|
||||
0b0000_0000,
|
||||
0b1111_1111,
|
||||
0b0000_0100,
|
||||
],
|
||||
Self::ReportLuns => &[
|
||||
0xa0,
|
||||
0b0000_0000,
|
||||
0b1111_1111,
|
||||
0b0000_0000,
|
||||
0b0000_0000,
|
||||
0b0000_0000,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b0000_0000,
|
||||
0b0000_0100,
|
||||
],
|
||||
Self::ReadCapacity10 => &[
|
||||
0x25,
|
||||
0b0000_0000,
|
||||
0b0000_0000,
|
||||
0b0000_0000,
|
||||
0b0000_0000,
|
||||
0b0000_0000,
|
||||
0b0000_0000,
|
||||
0b0000_0000,
|
||||
0b0000_0000,
|
||||
0b0000_0100,
|
||||
],
|
||||
Self::ReadCapacity16 => &[
|
||||
0x9e,
|
||||
0x10,
|
||||
0b0000_0000,
|
||||
0b0000_0000,
|
||||
0b0000_0000,
|
||||
0b0000_0000,
|
||||
0b0000_0000,
|
||||
0b0000_0000,
|
||||
0b0000_0000,
|
||||
0b0000_0000,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b0000_0000,
|
||||
0b0000_0100,
|
||||
],
|
||||
Self::ModeSense6 => &[
|
||||
0x1a,
|
||||
0b0000_1000,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b0000_0100,
|
||||
],
|
||||
Self::Read10 => &[
|
||||
0x28,
|
||||
0b1111_1100,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b0011_1111,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b0000_0100,
|
||||
],
|
||||
Self::Write10 => &[
|
||||
0x2A,
|
||||
0b1111_1100,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b0011_1111,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b0000_0100,
|
||||
],
|
||||
Self::WriteSame16 => &[
|
||||
0x93,
|
||||
0b1111_1001,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b0011_1111,
|
||||
0b0000_0100,
|
||||
],
|
||||
Self::Inquiry => &[
|
||||
0x12,
|
||||
0b0000_0001,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b0000_0100,
|
||||
],
|
||||
Self::ReportSupportedOperationCodes => &[
|
||||
0xa3,
|
||||
0xc,
|
||||
0b1000_0111,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b0000_0000,
|
||||
0b0000_0100,
|
||||
],
|
||||
Self::SynchronizeCache10 => &[
|
||||
0x53,
|
||||
0b0000_0010,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b0011_1111,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b0000_0100,
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Cdb {
|
||||
pub command: Command,
|
||||
pub allocation_length: Option<u32>,
|
||||
pub naca: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
pub(crate) enum ParseError {
|
||||
/// The opcode (specifically the first byte of the CDB) is unknown, i.e. we
|
||||
/// should respond with INVALID COMMAND OPERATION CODE
|
||||
InvalidCommand,
|
||||
/// Another field of the CDB (including the service action, if any) is
|
||||
/// invalid, i.e. we should respond with INVALID FIELD IN CDB.
|
||||
InvalidField,
|
||||
/// The CDB has fewer bytes than necessary for its opcode.
|
||||
TooSmall,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
pub(crate) enum ReportSupportedOpCodesMode {
|
||||
All,
|
||||
OneCommand(u8),
|
||||
OneServiceAction(u8, u16),
|
||||
OneCommandOrServiceAction(u8, u16),
|
||||
}
|
||||
|
||||
impl Cdb {
|
||||
// TODO: do we want to ensure reserved fields are 0? SCSI allows, but
|
||||
// doesn't require, us to do so.
|
||||
pub(crate) fn parse(cdb: &[u8]) -> Result<Self, ParseError> {
|
||||
let ct = CommandType::from_cdb(cdb)?;
|
||||
if cdb.len() < ct.cdb_template().len() {
|
||||
return Err(ParseError::TooSmall);
|
||||
}
|
||||
// Shrink the cdb down to its size, so accidentally accessing fields past the
|
||||
// length panics
|
||||
let cdb = &cdb[..ct.cdb_template().len()];
|
||||
|
||||
// unwraps below are safe: they're just calling TryFrom to convert from slices
|
||||
// to fixed-size arrays; in each case, we're using constant indexes and we
|
||||
// verified above that they're in bounds, so none of them can panic at runtime
|
||||
|
||||
match ct {
|
||||
CommandType::Inquiry => {
|
||||
// INQUIRY
|
||||
let evpd = match cdb[1] {
|
||||
0 => false,
|
||||
1 => true,
|
||||
// obselete or reserved bits set
|
||||
_ => return Err(ParseError::InvalidField),
|
||||
};
|
||||
let page_code_raw = cdb[2];
|
||||
let page_code = match (evpd, page_code_raw) {
|
||||
(false, 0) => None,
|
||||
(true, pc) => Some(pc.try_into().map_err(|_| ParseError::InvalidField)?),
|
||||
(false, _) => return Err(ParseError::InvalidField),
|
||||
};
|
||||
Ok(Self {
|
||||
command: Command::LunSpecificCommand(LunSpecificCommand::Inquiry(page_code)),
|
||||
allocation_length: Some(u32::from(u16::from_be_bytes(
|
||||
cdb[3..5].try_into().unwrap(),
|
||||
))),
|
||||
naca: (cdb[5] & 0b0000_0100) != 0,
|
||||
})
|
||||
}
|
||||
CommandType::ModeSense6 => {
|
||||
let dbd = match cdb[1] {
|
||||
0b0000_1000 => true,
|
||||
0b0000_0000 => false,
|
||||
_ => return Err(ParseError::InvalidField),
|
||||
};
|
||||
let pc = (cdb[2] & 0b1100_0000) >> 6;
|
||||
let page_code = cdb[2] & 0b0011_1111;
|
||||
let subpage_code = cdb[3];
|
||||
let mode: ModePageSelection = match (page_code, subpage_code) {
|
||||
(0x8, 0x0) => ModePageSelection::Single(ModePage::Caching),
|
||||
(0x3f, 0x0) => ModePageSelection::AllPageZeros,
|
||||
_ => {
|
||||
warn!(
|
||||
"Rejecting request for unknown mode page {:#2x}/{:#2x}.",
|
||||
page_code, subpage_code
|
||||
);
|
||||
return Err(ParseError::InvalidField);
|
||||
}
|
||||
};
|
||||
Ok(Self {
|
||||
command: Command::LunSpecificCommand(LunSpecificCommand::ModeSense6 {
|
||||
pc: pc.try_into().map_err(|_| ParseError::InvalidField)?,
|
||||
mode_page: mode,
|
||||
dbd,
|
||||
}),
|
||||
allocation_length: Some(u32::from(cdb[4])),
|
||||
naca: (cdb[5] & 0b0000_0100) != 0,
|
||||
})
|
||||
}
|
||||
CommandType::Read10 => {
|
||||
if cdb[1] & 0b1110_0100 != 0 {
|
||||
// Features (protection and rebuild assist) we don't
|
||||
// support; the standard says to respond with INVALID
|
||||
// FIELD IN CDB for these if unsupported
|
||||
return Err(ParseError::InvalidField);
|
||||
}
|
||||
Ok(Self {
|
||||
command: Command::LunSpecificCommand(LunSpecificCommand::Read10 {
|
||||
dpo: cdb[1] & 0b0001_0000 != 0,
|
||||
fua: cdb[1] & 0b0000_1000 != 0,
|
||||
lba: u32::from_be_bytes(cdb[2..6].try_into().unwrap()),
|
||||
transfer_length: u16::from_be_bytes(cdb[7..9].try_into().unwrap()),
|
||||
}),
|
||||
allocation_length: None,
|
||||
naca: (cdb[9] & 0b0000_0100) != 0,
|
||||
})
|
||||
}
|
||||
CommandType::Write10 => {
|
||||
if cdb[1] & 0b1110_0000 != 0 {
|
||||
// Feature (protection) that we don't
|
||||
// support; the standard says to respond with INVALID
|
||||
// FIELD IN CDB for these if unsupported
|
||||
return Err(ParseError::InvalidField);
|
||||
}
|
||||
Ok(Self {
|
||||
command: Command::LunSpecificCommand(LunSpecificCommand::Write10 {
|
||||
dpo: cdb[1] & 0b0001_0000 != 0,
|
||||
fua: cdb[1] & 0b0000_1000 != 0,
|
||||
lba: u32::from_be_bytes(cdb[2..6].try_into().unwrap()),
|
||||
transfer_length: u16::from_be_bytes(cdb[7..9].try_into().unwrap()),
|
||||
}),
|
||||
allocation_length: None,
|
||||
naca: (cdb[9] & 0b0000_0100) != 0,
|
||||
})
|
||||
}
|
||||
CommandType::WriteSame16 => {
|
||||
if cdb[1] & 0b1110_0001 != 0 {
|
||||
warn!("Unsupported field in WriteSame16");
|
||||
// We neither support protections nor logical block provisioning
|
||||
return Err(ParseError::InvalidField);
|
||||
}
|
||||
Ok(Self {
|
||||
command: Command::LunSpecificCommand(LunSpecificCommand::WriteSame16 {
|
||||
lba: u64::from_be_bytes(cdb[2..10].try_into().expect("lba should fit u64")),
|
||||
number_of_logical_blocks: u32::from_be_bytes(
|
||||
cdb[10..14].try_into().expect("block count should fit u32"),
|
||||
),
|
||||
anchor: (cdb[1] & 0b0001_0000) != 0,
|
||||
}),
|
||||
allocation_length: None,
|
||||
naca: (cdb[15] & 0b0000_0100) != 0,
|
||||
})
|
||||
}
|
||||
CommandType::SynchronizeCache10 => Ok(Self {
|
||||
command: Command::LunSpecificCommand(LunSpecificCommand::SynchronizeCache10),
|
||||
allocation_length: None,
|
||||
naca: (cdb[9] & 0b0000_0100) != 0,
|
||||
}),
|
||||
CommandType::ReadCapacity10 => Ok(Self {
|
||||
command: Command::LunSpecificCommand(LunSpecificCommand::ReadCapacity10),
|
||||
allocation_length: None,
|
||||
naca: (cdb[9] & 0b0000_0100) != 0,
|
||||
}),
|
||||
CommandType::ReadCapacity16 => Ok(Self {
|
||||
command: Command::LunSpecificCommand(LunSpecificCommand::ReadCapacity16),
|
||||
allocation_length: Some(u32::from_be_bytes(cdb[10..14].try_into().unwrap())),
|
||||
naca: (cdb[15] & 0b0000_0100) != 0,
|
||||
}),
|
||||
CommandType::ReportLuns => Ok(Self {
|
||||
command: Command::LunIndependentCommand(LunIndependentCommand::ReportLuns(
|
||||
cdb[2].try_into().map_err(|_| ParseError::InvalidField)?,
|
||||
)),
|
||||
allocation_length: Some(u32::from_be_bytes(cdb[6..10].try_into().unwrap())),
|
||||
naca: (cdb[9] & 0b0000_0100) != 0,
|
||||
}),
|
||||
CommandType::ReportSupportedOperationCodes => {
|
||||
let rctd = cdb[2] & 0b1000_0000 != 0;
|
||||
let mode = match cdb[2] & 0b0000_0111 {
|
||||
0b000 => ReportSupportedOpCodesMode::All,
|
||||
0b001 => ReportSupportedOpCodesMode::OneCommand(cdb[3]),
|
||||
0b010 => ReportSupportedOpCodesMode::OneServiceAction(
|
||||
cdb[3],
|
||||
u16::from_be_bytes(cdb[4..6].try_into().unwrap()),
|
||||
),
|
||||
0b011 => ReportSupportedOpCodesMode::OneCommandOrServiceAction(
|
||||
cdb[3],
|
||||
u16::from_be_bytes(cdb[4..6].try_into().unwrap()),
|
||||
),
|
||||
_ => return Err(ParseError::InvalidField),
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
command: Command::LunSpecificCommand(
|
||||
LunSpecificCommand::ReportSupportedOperationCodes { rctd, mode },
|
||||
),
|
||||
allocation_length: Some(u32::from_be_bytes(cdb[6..10].try_into().unwrap())),
|
||||
naca: (cdb[11] & 0b0000_0100) != 0,
|
||||
})
|
||||
}
|
||||
CommandType::RequestSense => {
|
||||
let format = if cdb[1] & 0b0000_0001 == 1 {
|
||||
SenseFormat::Descriptor
|
||||
} else {
|
||||
SenseFormat::Fixed
|
||||
};
|
||||
Ok(Self {
|
||||
command: Command::LunSpecificCommand(LunSpecificCommand::RequestSense(format)),
|
||||
allocation_length: Some(u32::from(cdb[4])),
|
||||
naca: (cdb[5] & 0b0000_0100) != 0,
|
||||
})
|
||||
}
|
||||
CommandType::TestUnitReady => Ok(Self {
|
||||
command: Command::LunSpecificCommand(LunSpecificCommand::TestUnitReady),
|
||||
allocation_length: None,
|
||||
naca: (cdb[5] & 0b0000_0100) != 0,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
62
src/scsi/emulation/missing_lun.rs
Normal file
62
src/scsi/emulation/missing_lun.rs
Normal file
|
@ -0,0 +1,62 @@
|
|||
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
|
||||
|
||||
use std::io::{Read, Write};
|
||||
|
||||
use super::{
|
||||
command::{LunSpecificCommand, SenseFormat},
|
||||
response_data::{respond_standard_inquiry_data, SilentlyTruncate},
|
||||
target::{LogicalUnit, LunRequest},
|
||||
};
|
||||
use crate::scsi::{sense, CmdError, CmdError::DataIn, CmdOutput};
|
||||
|
||||
pub(crate) struct MissingLun;
|
||||
|
||||
impl LogicalUnit for MissingLun {
|
||||
fn execute_command(
|
||||
&mut self,
|
||||
data_in: &mut SilentlyTruncate<&mut dyn Write>,
|
||||
_data_out: &mut dyn Read,
|
||||
_req: LunRequest,
|
||||
cmd: LunSpecificCommand,
|
||||
) -> Result<CmdOutput, CmdError> {
|
||||
match cmd {
|
||||
LunSpecificCommand::Inquiry(page_code) => {
|
||||
// peripheral qualifier 0b011: logical unit not accessible
|
||||
// device type 0x1f: unknown/no device type
|
||||
data_in.write_all(&[0b0110_0000 | 0x1f]).map_err(DataIn)?;
|
||||
match page_code {
|
||||
Some(_) => {
|
||||
// SPC-6 7.7.2: "If the PERIPHERAL QUALIFIER field is
|
||||
// not set to 000b, the contents of the PAGE LENGTH
|
||||
// field and the VPD parameters are outside the
|
||||
// scope of this standard."
|
||||
//
|
||||
// Returning a 0 length and no data seems sensible enough.
|
||||
data_in.write_all(&[0]).map_err(DataIn)?;
|
||||
}
|
||||
None => {
|
||||
respond_standard_inquiry_data(data_in).map_err(DataIn)?;
|
||||
}
|
||||
}
|
||||
Ok(CmdOutput::ok())
|
||||
}
|
||||
LunSpecificCommand::RequestSense(format) => {
|
||||
match format {
|
||||
SenseFormat::Fixed => {
|
||||
data_in
|
||||
.write_all(&sense::LOGICAL_UNIT_NOT_SUPPORTED.to_fixed_sense())
|
||||
.map_err(DataIn)?;
|
||||
Ok(CmdOutput::ok())
|
||||
}
|
||||
SenseFormat::Descriptor => {
|
||||
// Don't support desciptor format.
|
||||
Ok(CmdOutput::check_condition(sense::INVALID_FIELD_IN_CDB))
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => Ok(CmdOutput::check_condition(
|
||||
sense::LOGICAL_UNIT_NOT_SUPPORTED,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
11
src/scsi/emulation/mod.rs
Normal file
11
src/scsi/emulation/mod.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
|
||||
|
||||
pub(crate) mod block_device;
|
||||
mod command;
|
||||
pub(crate) mod missing_lun;
|
||||
pub(crate) mod mode_page;
|
||||
mod response_data;
|
||||
pub(crate) mod target;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
48
src/scsi/emulation/mode_page.rs
Normal file
48
src/scsi/emulation/mode_page.rs
Normal file
|
@ -0,0 +1,48 @@
|
|||
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
|
||||
|
||||
use std::io::{self, Write};
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
pub(crate) enum ModePage {
|
||||
Caching,
|
||||
}
|
||||
|
||||
impl ModePage {
|
||||
pub(crate) const ALL_ZERO: &'static [Self] = &[Self::Caching];
|
||||
|
||||
pub(crate) const fn page_code(self) -> (u8, u8) {
|
||||
match self {
|
||||
Self::Caching => (0x8, 0),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const fn page_length(self) -> u8 {
|
||||
match self {
|
||||
Self::Caching => 0x12,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn write(self, data_in: &mut impl Write) -> io::Result<()> {
|
||||
assert_eq!(self.page_code().1, 0, "Subpages aren't supported yet.");
|
||||
|
||||
data_in.write_all(&[
|
||||
self.page_code().0, // top 2 bits: no subpage, saving not supported
|
||||
self.page_length(), // page length
|
||||
])?;
|
||||
|
||||
match self {
|
||||
Self::Caching => {
|
||||
data_in.write_all(&[
|
||||
// Writeback Cache Enable, lots of bits zero
|
||||
// n.b. kernel logs will show WCE off; it always says
|
||||
// that for read-only devices, which we are rn
|
||||
0b0000_0100,
|
||||
])?;
|
||||
// various cache fine-tuning stuff we can't really control
|
||||
data_in.write_all(&[0; 0x11])?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
107
src/scsi/emulation/response_data.rs
Normal file
107
src/scsi/emulation/response_data.rs
Normal file
|
@ -0,0 +1,107 @@
|
|||
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
|
||||
|
||||
//! Some helpers for writing response data, shared between `BlockDevice` and
|
||||
//! `MissingLun`
|
||||
|
||||
use std::{cmp::min, convert::TryFrom, io, io::Write};
|
||||
|
||||
/// A wrapper around a `Write` that silently truncates its input after a given
|
||||
/// number of bytes. This matches the semantics of SCSI's ALLOCATION LENGTH
|
||||
/// field; anything beyond the allocation length is silently omitted.
|
||||
pub struct SilentlyTruncate<W: Write>(W, usize);
|
||||
|
||||
impl<W: Write> SilentlyTruncate<W> {
|
||||
pub const fn new(writer: W, len: usize) -> Self {
|
||||
Self(writer, len)
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> Write for SilentlyTruncate<W> {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
if self.1 == 0 {
|
||||
// our goal is to silently fail, so once we've stopped actually
|
||||
// writing, just pretend all writes work
|
||||
return Ok(buf.len());
|
||||
}
|
||||
let len = min(buf.len(), self.1);
|
||||
let buf = &buf[..len];
|
||||
let written = self.0.write(buf)?;
|
||||
self.1 -= written;
|
||||
Ok(written)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
self.0.flush()
|
||||
}
|
||||
}
|
||||
|
||||
fn encode_lun(lun: u16) -> [u8; 8] {
|
||||
let lun = u8::try_from(lun).expect("more than 255 LUNs are currently unsupported");
|
||||
[0, lun, 0, 0, 0, 0, 0, 0]
|
||||
}
|
||||
|
||||
/// Write the response data for a REPORT LUNS command.
|
||||
pub fn respond_report_luns<T>(data_in: &mut impl Write, luns: T) -> io::Result<()>
|
||||
where
|
||||
T: IntoIterator<Item = u16>,
|
||||
T::IntoIter: ExactSizeIterator,
|
||||
{
|
||||
let iter = luns.into_iter();
|
||||
data_in.write_all(
|
||||
&(u32::try_from(iter.len() * 8))
|
||||
.expect("less than 256 LUNS")
|
||||
.to_be_bytes(),
|
||||
)?;
|
||||
data_in.write_all(&[0; 4])?; // reserved
|
||||
for lun in iter {
|
||||
data_in.write_all(&encode_lun(lun))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write the response data for a standard (i.e. not VPD) inquiry, excluding the
|
||||
/// first byte (the peripheal qualifier and device type).
|
||||
pub fn respond_standard_inquiry_data(data_in: &mut impl Write) -> io::Result<()> {
|
||||
// TODO: Feature bits here we might want to support:
|
||||
// - NormACA
|
||||
// - command queueing
|
||||
data_in.write_all(&[
|
||||
// various bits: not removable, not part of a
|
||||
// conglomerate, no info on hotpluggability
|
||||
0,
|
||||
0x7, // version: SPC-6
|
||||
// bits: don't support NormACA, support modern LUN format
|
||||
// INQUIRY data version 2
|
||||
0b0001_0000 | 0x2,
|
||||
91, // additional INQURIY data length
|
||||
// bunch of feature bits we don't support:
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
])?;
|
||||
|
||||
// TODO: register this or another name with T10
|
||||
data_in.write_all(b"rust-vmm")?;
|
||||
data_in.write_all(b"vhost-user-scsi ")?;
|
||||
data_in.write_all(b"v0 ")?;
|
||||
|
||||
// The Linux kernel doesn't request any more than this, so any data we return
|
||||
// after this point is mostly academic.
|
||||
|
||||
data_in.write_all(&[0; 22])?;
|
||||
|
||||
let product_descs: &[u16; 8] = &[
|
||||
0x00c0, // SAM-6 (no version claimed)
|
||||
0x05c0, // SPC-5 (no version claimed)
|
||||
0x0600, // SBC-4 (no version claimed)
|
||||
0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
];
|
||||
|
||||
for desc in product_descs {
|
||||
data_in.write_all(&desc.to_be_bytes())?;
|
||||
}
|
||||
|
||||
data_in.write_all(&[0; 22])?;
|
||||
|
||||
Ok(())
|
||||
}
|
143
src/scsi/emulation/target.rs
Normal file
143
src/scsi/emulation/target.rs
Normal file
|
@ -0,0 +1,143 @@
|
|||
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use std::io::{Read, Write};
|
||||
|
||||
use log::error;
|
||||
|
||||
use super::{
|
||||
command::{
|
||||
Cdb, Command, LunIndependentCommand, LunSpecificCommand, ParseError, ReportLunsSelectReport,
|
||||
},
|
||||
missing_lun::MissingLun,
|
||||
response_data::{respond_report_luns, SilentlyTruncate},
|
||||
};
|
||||
use crate::scsi::{sense, CmdError, CmdOutput, Request, Target, TaskAttr};
|
||||
|
||||
pub(crate) struct LunRequest {
|
||||
pub _id: u64,
|
||||
pub task_attr: TaskAttr,
|
||||
pub crn: u8,
|
||||
pub prio: u8,
|
||||
pub _allocation_length: Option<u32>,
|
||||
pub naca: bool,
|
||||
}
|
||||
|
||||
/// A single logical unit of an emulated SCSI device.
|
||||
pub(crate) trait LogicalUnit: Send + Sync {
|
||||
/// Process a SCSI command sent to this logical unit.
|
||||
///
|
||||
/// # Return value
|
||||
/// This function returns a Result, but it should return Err only in limited
|
||||
/// circumstances: when something goes wrong at the transport level, such
|
||||
/// as writes to `req.data_in` failing or `req.cdb` being too short.
|
||||
/// Any other errors, such as invalid SCSI commands or I/O errors
|
||||
/// accessing an underlying file, should result in an Ok return value
|
||||
/// with a `CmdOutput` representing a SCSI-level error (i.e. CHECK
|
||||
/// CONDITION status, and appropriate sense data).
|
||||
fn execute_command(
|
||||
&mut self,
|
||||
data_in: &mut SilentlyTruncate<&mut dyn Write>,
|
||||
data_out: &mut dyn Read,
|
||||
parameters: LunRequest,
|
||||
command: LunSpecificCommand,
|
||||
) -> Result<CmdOutput, CmdError>;
|
||||
}
|
||||
|
||||
/// A SCSI target implemented by emulating a device within vhost-device-scsi.
|
||||
pub(crate) struct EmulatedTarget {
|
||||
luns: Vec<Box<dyn LogicalUnit>>,
|
||||
}
|
||||
|
||||
impl EmulatedTarget {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self { luns: Vec::new() }
|
||||
}
|
||||
|
||||
pub(crate) fn add_lun(&mut self, logical_unit: Box<dyn LogicalUnit>) {
|
||||
self.luns.push(logical_unit);
|
||||
}
|
||||
|
||||
pub(crate) fn luns(&self) -> impl Iterator<Item = u16> + ExactSizeIterator + '_ {
|
||||
// unwrap is safe: we limit LUNs at 256
|
||||
self.luns
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, _logical_unit)| u16::try_from(idx).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for EmulatedTarget {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Target for EmulatedTarget {
|
||||
fn execute_command(
|
||||
&mut self,
|
||||
lun: u16,
|
||||
data_out: &mut dyn Read,
|
||||
data_in: &mut dyn Write,
|
||||
req: Request,
|
||||
) -> Result<CmdOutput, CmdError> {
|
||||
match Cdb::parse(req.cdb) {
|
||||
Ok(cdb) => {
|
||||
let mut data_in = SilentlyTruncate::new(
|
||||
data_in,
|
||||
cdb.allocation_length.map_or(usize::MAX, |x| x as usize),
|
||||
);
|
||||
|
||||
match cdb.command {
|
||||
Command::LunIndependentCommand(cmd) => match cmd {
|
||||
LunIndependentCommand::ReportLuns(select_report) => {
|
||||
match select_report {
|
||||
ReportLunsSelectReport::NoWellKnown
|
||||
| ReportLunsSelectReport::All => {
|
||||
respond_report_luns(&mut data_in, self.luns())
|
||||
.map_err(CmdError::DataIn)?;
|
||||
}
|
||||
ReportLunsSelectReport::WellKnownOnly
|
||||
| ReportLunsSelectReport::Administrative
|
||||
| ReportLunsSelectReport::TopLevel
|
||||
| ReportLunsSelectReport::SameConglomerate => {
|
||||
respond_report_luns(&mut data_in, vec![].into_iter())
|
||||
.map_err(CmdError::DataIn)?;
|
||||
}
|
||||
}
|
||||
Ok(CmdOutput::ok())
|
||||
}
|
||||
},
|
||||
Command::LunSpecificCommand(cmd) => {
|
||||
let req = LunRequest {
|
||||
_id: req.id,
|
||||
task_attr: req.task_attr,
|
||||
crn: req.crn,
|
||||
prio: req.prio,
|
||||
_allocation_length: cdb.allocation_length,
|
||||
naca: cdb.naca,
|
||||
};
|
||||
match self.luns.get_mut(lun as usize) {
|
||||
Some(lun) => lun.execute_command(&mut data_in, data_out, req, cmd),
|
||||
None => MissingLun.execute_command(&mut data_in, data_out, req, cmd),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(ParseError::InvalidCommand) => {
|
||||
error!("Rejecting CDB for unknown command: {:?}", req.cdb);
|
||||
Ok(CmdOutput::check_condition(
|
||||
sense::INVALID_COMMAND_OPERATION_CODE,
|
||||
))
|
||||
}
|
||||
// TODO: SCSI has a provision for INVALID FIELD IN CDB to include the
|
||||
// index of the invalid field, but it's not clear if that's mandatory.
|
||||
// In any case, QEMU omits it.
|
||||
Err(ParseError::InvalidField) => {
|
||||
error!("Rejecting CDB with invalid field: {:?}", req.cdb);
|
||||
Ok(CmdOutput::check_condition(sense::INVALID_FIELD_IN_CDB))
|
||||
}
|
||||
Err(ParseError::TooSmall) => Err(CmdError::CdbTooShort),
|
||||
}
|
||||
}
|
||||
}
|
198
src/scsi/emulation/tests/bad_lun.rs
Normal file
198
src/scsi/emulation/tests/bad_lun.rs
Normal file
|
@ -0,0 +1,198 @@
|
|||
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
|
||||
|
||||
use super::{do_command_fail_lun, do_command_in_lun, null_image};
|
||||
use crate::scsi::{
|
||||
emulation::{block_device::BlockDevice, target::EmulatedTarget},
|
||||
sense,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_report_luns() {
|
||||
let mut target = EmulatedTarget::new();
|
||||
for _ in 0..5 {
|
||||
let dev = BlockDevice::new(null_image());
|
||||
target.add_lun(Box::new(dev));
|
||||
}
|
||||
|
||||
let select_reports = &[0x0, 0x2]; // all but well known, all
|
||||
|
||||
for &sr in select_reports {
|
||||
do_command_in_lun(
|
||||
&mut target,
|
||||
6,
|
||||
&[
|
||||
0xa0, // REPORT LUNS
|
||||
0, // reserved
|
||||
sr, // select report
|
||||
0, 0, 0, // reserved
|
||||
0, 0, 1, 0, // alloc length: 256
|
||||
0, 0,
|
||||
],
|
||||
&[],
|
||||
&[
|
||||
0, 0, 0, 40, // length: 5*8 = 40
|
||||
0, 0, 0, 0, // reserved
|
||||
0, 0, 0, 0, 0, 0, 0, 0, // LUN 0
|
||||
0, 1, 0, 0, 0, 0, 0, 0, // LUN 1
|
||||
0, 2, 0, 0, 0, 0, 0, 0, // LUN 2
|
||||
0, 3, 0, 0, 0, 0, 0, 0, // LUN 3
|
||||
0, 4, 0, 0, 0, 0, 0, 0, // LUN 4
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_report_luns_empty() {
|
||||
let mut target = EmulatedTarget::new();
|
||||
for _ in 0..5 {
|
||||
let dev = BlockDevice::new(null_image());
|
||||
target.add_lun(Box::new(dev));
|
||||
}
|
||||
|
||||
// well-known only and several modes explictly defined to return an empty list
|
||||
// for all but ceratin types of recieving LUNs
|
||||
let select_reports = &[0x1, 0x10, 0x11, 0x12];
|
||||
|
||||
for &sr in select_reports {
|
||||
do_command_in_lun(
|
||||
&mut target,
|
||||
6,
|
||||
&[
|
||||
0xa0, // REPORT LUNS
|
||||
0, // reserved
|
||||
sr, // select report
|
||||
0, 0, 0, // reserved
|
||||
0, 0, 1, 0, // alloc length: 256
|
||||
0, 0,
|
||||
],
|
||||
&[],
|
||||
&[
|
||||
0, 0, 0, 0, // length: 0
|
||||
0, 0, 0, 0, // reserved
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_request_sense() {
|
||||
let mut target = EmulatedTarget::new();
|
||||
let dev = BlockDevice::new(null_image());
|
||||
target.add_lun(Box::new(dev));
|
||||
|
||||
do_command_in_lun(
|
||||
&mut target,
|
||||
1,
|
||||
&[
|
||||
0x3, // REQUEST SENSE
|
||||
0, // fixed format sense data
|
||||
0, 0, // reserved
|
||||
255, // alloc length
|
||||
0, // control
|
||||
],
|
||||
&[],
|
||||
&sense::LOGICAL_UNIT_NOT_SUPPORTED.to_fixed_sense(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_request_sense_descriptor_format() {
|
||||
let mut target = EmulatedTarget::new();
|
||||
let dev = BlockDevice::new(null_image());
|
||||
target.add_lun(Box::new(dev));
|
||||
|
||||
do_command_fail_lun(
|
||||
&mut target,
|
||||
1,
|
||||
&[
|
||||
0x3, // REQUEST SENSE
|
||||
1, // descriptor format sense data
|
||||
0, 0, // reserved
|
||||
255, // alloc length
|
||||
0, // control
|
||||
],
|
||||
sense::INVALID_FIELD_IN_CDB,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inquiry() {
|
||||
let mut target = EmulatedTarget::new();
|
||||
let dev = BlockDevice::new(null_image());
|
||||
target.add_lun(Box::new(dev));
|
||||
|
||||
do_command_in_lun(
|
||||
&mut target,
|
||||
1,
|
||||
&[
|
||||
0x12, // INQUIRY
|
||||
0, // EVPD bit: 0
|
||||
0, // page code
|
||||
1, 0, // alloc length: 256
|
||||
0, // control
|
||||
],
|
||||
&[],
|
||||
// some empty comments to get rustfmt to do something vaguely sensible
|
||||
&[
|
||||
0x7f, // device not accessible, unknown type
|
||||
0, // features
|
||||
0x7, // version
|
||||
0x12, // response data format v2, HiSup = 1
|
||||
91, // addl length
|
||||
0, 0, 0, // unsupported features
|
||||
// vendor
|
||||
b'r', b'u', b's', b't', b'-', b'v', b'm', b'm', //
|
||||
// product
|
||||
b'v', b'h', b'o', b's', b't', b'-', b'u', b's', b'e', b'r', b'-', b's', b'c', b's',
|
||||
b'i', b' ', //
|
||||
// revision
|
||||
b'v', b'0', b' ', b' ', //
|
||||
// reserved/obselete/vendor specific
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
// version descriptors
|
||||
0x0, 0xc0, // SAM-6
|
||||
0x05, 0xc0, // SPC-5 (no code assigned for 6 yet)
|
||||
0x06, 0x0, // SBC-4
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //
|
||||
// reserved
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_other_command() {
|
||||
let mut target = EmulatedTarget::new();
|
||||
let dev = BlockDevice::new(null_image());
|
||||
target.add_lun(Box::new(dev));
|
||||
|
||||
do_command_fail_lun(
|
||||
&mut target,
|
||||
1,
|
||||
&[
|
||||
0, // TEST UNIT READY
|
||||
0, 0, 0, 0, // reserved
|
||||
0, // control
|
||||
],
|
||||
sense::LOGICAL_UNIT_NOT_SUPPORTED,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_command() {
|
||||
let mut target = EmulatedTarget::new();
|
||||
let dev = BlockDevice::new(null_image());
|
||||
target.add_lun(Box::new(dev));
|
||||
|
||||
do_command_fail_lun(
|
||||
&mut target,
|
||||
1,
|
||||
&[
|
||||
0xff, // vendor specific
|
||||
0, 0, 0, 0, // reserved
|
||||
0, // control
|
||||
],
|
||||
sense::INVALID_COMMAND_OPERATION_CODE,
|
||||
);
|
||||
}
|
108
src/scsi/emulation/tests/generic.rs
Normal file
108
src/scsi/emulation/tests/generic.rs
Normal file
|
@ -0,0 +1,108 @@
|
|||
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
|
||||
|
||||
//! Tests for stuff shared between commands.
|
||||
|
||||
use assert_matches::assert_matches;
|
||||
use std::io::ErrorKind;
|
||||
|
||||
use super::{do_command_fail, test_image};
|
||||
use crate::scsi::{
|
||||
emulation::{block_device::BlockDevice, target::EmulatedTarget},
|
||||
sense, CmdError, Request, Target, TaskAttr,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_invalid_opcode() {
|
||||
let mut target = EmulatedTarget::new();
|
||||
let dev = BlockDevice::new(test_image());
|
||||
target.add_lun(Box::new(dev));
|
||||
|
||||
do_command_fail(
|
||||
&mut target,
|
||||
&[
|
||||
0xff, // vendor specific, unused by us
|
||||
0, 0, 0, 0, 0,
|
||||
],
|
||||
sense::INVALID_COMMAND_OPERATION_CODE,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_service_action() {
|
||||
let mut target = EmulatedTarget::new();
|
||||
let dev = BlockDevice::new(test_image());
|
||||
target.add_lun(Box::new(dev));
|
||||
|
||||
do_command_fail(
|
||||
&mut target,
|
||||
&[
|
||||
0xa3, // MAINTAINANCE IN
|
||||
0x1f, // vendor specific, unused by us
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
],
|
||||
sense::INVALID_FIELD_IN_CDB,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_short_data_out_buffer() {
|
||||
let mut target = EmulatedTarget::new();
|
||||
let dev = BlockDevice::new(test_image());
|
||||
target.add_lun(Box::new(dev));
|
||||
|
||||
let mut data_in: &mut [u8] = &mut [];
|
||||
let mut data_out: &[u8] = &[0_u8; 511];
|
||||
|
||||
let res = target.execute_command(
|
||||
0,
|
||||
&mut data_out,
|
||||
&mut data_in,
|
||||
Request {
|
||||
id: 0,
|
||||
cdb: &[
|
||||
0x28, // READ (10)
|
||||
0, // flags
|
||||
0, 0, 0, 15, // LBA: 5
|
||||
0, // reserved, group #
|
||||
0, 1, // transfer length: 1
|
||||
0, // control
|
||||
],
|
||||
task_attr: TaskAttr::Simple,
|
||||
crn: 0,
|
||||
prio: 0,
|
||||
},
|
||||
);
|
||||
|
||||
if let CmdError::DataIn(e) = res.unwrap_err() {
|
||||
assert_eq!(e.kind(), ErrorKind::WriteZero);
|
||||
} else {
|
||||
panic!();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_short_cdb() {
|
||||
let mut target: EmulatedTarget = EmulatedTarget::new();
|
||||
let dev = BlockDevice::new(test_image());
|
||||
target.add_lun(Box::new(dev));
|
||||
|
||||
let mut data_in: &mut [u8] = &mut [];
|
||||
let mut data_out: &[u8] = &[];
|
||||
|
||||
let res = target.execute_command(
|
||||
0,
|
||||
&mut data_out,
|
||||
&mut data_in,
|
||||
Request {
|
||||
id: 0,
|
||||
cdb: &[
|
||||
0x28, // READ (10)
|
||||
],
|
||||
task_attr: TaskAttr::Simple,
|
||||
crn: 0,
|
||||
prio: 0,
|
||||
},
|
||||
);
|
||||
|
||||
assert_matches!(res.unwrap_err(), CmdError::CdbTooShort);
|
||||
}
|
520
src/scsi/emulation/tests/mod.rs
Normal file
520
src/scsi/emulation/tests/mod.rs
Normal file
|
@ -0,0 +1,520 @@
|
|||
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
mod bad_lun;
|
||||
mod generic;
|
||||
mod report_supported_operation_codes;
|
||||
|
||||
use std::{
|
||||
fs::File,
|
||||
io::Write,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use tempfile::tempfile;
|
||||
|
||||
use super::{
|
||||
block_device::{
|
||||
BlockDevice, BlockDeviceBackend, BlockOffset, BlockSize, ByteOffset, FileBackend,
|
||||
},
|
||||
target::EmulatedTarget,
|
||||
};
|
||||
use crate::scsi::{
|
||||
sense::{self, SenseTriple},
|
||||
CmdOutput, Request, Target, TaskAttr,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct TestBackend {
|
||||
data: Arc<Mutex<[u8; 512 * 16]>>,
|
||||
}
|
||||
|
||||
impl TestBackend {
|
||||
fn new() -> Self {
|
||||
TestBackend {
|
||||
data: Arc::new(Mutex::new([0; 512 * 16])),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockDeviceBackend for TestBackend {
|
||||
fn read_exact_at(&mut self, buf: &mut [u8], offset: ByteOffset) -> std::io::Result<()> {
|
||||
let data = self.data.lock().unwrap();
|
||||
|
||||
let offset = usize::try_from(u64::from(offset)).expect("offset should fit usize");
|
||||
buf.copy_from_slice(&data[offset..(offset + buf.len())]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_exact_at(&mut self, buf: &[u8], offset: ByteOffset) -> std::io::Result<()> {
|
||||
let mut data = self.data.lock().unwrap();
|
||||
|
||||
let offset = usize::try_from(u64::from(offset)).expect("offset should fit usize");
|
||||
data[offset..(offset + buf.len())].copy_from_slice(buf);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn size_in_blocks(&mut self) -> std::io::Result<BlockOffset> {
|
||||
Ok(ByteOffset::from(
|
||||
u64::try_from(self.data.lock().unwrap().len()).expect("size_in_blocks should fit u64"),
|
||||
) / self.block_size())
|
||||
}
|
||||
|
||||
fn block_size(&self) -> BlockSize {
|
||||
BlockSize::try_from(512).expect("512 should be a valid BlockSize")
|
||||
}
|
||||
|
||||
fn sync(&mut self) -> std::io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn null_image() -> FileBackend {
|
||||
FileBackend::new(File::open("/dev/null").unwrap())
|
||||
}
|
||||
|
||||
fn test_image() -> FileBackend {
|
||||
let mut f = tempfile().unwrap();
|
||||
// generate 16 512-byte sectors, each of which consist of a single
|
||||
// repeated hex character, i.e.
|
||||
// sector 00: 0000000....0000
|
||||
// sector 15: fffffff....ffff
|
||||
for chr in b'0'..=b'9' {
|
||||
f.write_all(&[chr; 512]).unwrap();
|
||||
}
|
||||
for chr in b'a'..=b'f' {
|
||||
f.write_all(&[chr; 512]).unwrap();
|
||||
}
|
||||
FileBackend::new(f)
|
||||
}
|
||||
|
||||
fn do_command_in_lun(
|
||||
target: &mut EmulatedTarget,
|
||||
lun: u16,
|
||||
cdb: &[u8],
|
||||
data_out: &[u8],
|
||||
expected_data_in: &[u8],
|
||||
) {
|
||||
let mut data_in = Vec::new();
|
||||
|
||||
let res = target.execute_command(
|
||||
lun,
|
||||
&mut &data_out[..],
|
||||
&mut data_in,
|
||||
Request {
|
||||
id: 0,
|
||||
cdb,
|
||||
task_attr: TaskAttr::Simple,
|
||||
crn: 0,
|
||||
prio: 0,
|
||||
},
|
||||
);
|
||||
|
||||
assert_eq!(res.unwrap(), CmdOutput::ok());
|
||||
assert_eq!(&data_in, expected_data_in);
|
||||
}
|
||||
|
||||
fn do_command_fail_lun(
|
||||
target: &mut EmulatedTarget,
|
||||
lun: u16,
|
||||
cdb: &[u8],
|
||||
expected_error: SenseTriple,
|
||||
) {
|
||||
let mut data_in = Vec::new();
|
||||
let mut data_out: &[u8] = &[];
|
||||
|
||||
let res = target.execute_command(
|
||||
lun,
|
||||
&mut data_out,
|
||||
&mut data_in,
|
||||
Request {
|
||||
id: 0,
|
||||
cdb,
|
||||
task_attr: TaskAttr::Simple,
|
||||
crn: 0,
|
||||
prio: 0,
|
||||
},
|
||||
);
|
||||
|
||||
assert_eq!(res.unwrap(), CmdOutput::check_condition(expected_error));
|
||||
assert_eq!(&data_in, &[]);
|
||||
}
|
||||
|
||||
fn do_command_in(
|
||||
target: &mut EmulatedTarget,
|
||||
cdb: &[u8],
|
||||
data_out: &[u8],
|
||||
expected_data_in: &[u8],
|
||||
) {
|
||||
do_command_in_lun(target, 0, cdb, data_out, expected_data_in);
|
||||
}
|
||||
|
||||
fn do_command_fail(target: &mut EmulatedTarget, cdb: &[u8], expected_error: SenseTriple) {
|
||||
do_command_fail_lun(target, 0, cdb, expected_error);
|
||||
}
|
||||
|
||||
fn block_size_512() -> BlockSize {
|
||||
BlockSize::try_from(512).expect("512 should be a valid block_size")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_test_unit_ready() {
|
||||
let mut target = EmulatedTarget::new();
|
||||
let dev = BlockDevice::new(null_image());
|
||||
target.add_lun(Box::new(dev));
|
||||
|
||||
do_command_in(&mut target, &[0, 0, 0, 0, 0, 0], &[], &[]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_report_luns() {
|
||||
let mut target = EmulatedTarget::new();
|
||||
for _ in 0..5 {
|
||||
let dev = BlockDevice::new(null_image());
|
||||
target.add_lun(Box::new(dev));
|
||||
}
|
||||
|
||||
do_command_in(
|
||||
&mut target,
|
||||
&[
|
||||
0xa0, // REPORT LUNS
|
||||
0, // reserved
|
||||
0, // select report
|
||||
0, 0, 0, // reserved
|
||||
0, 0, 1, 0, // alloc length: 256
|
||||
0, 0,
|
||||
],
|
||||
&[],
|
||||
&[
|
||||
0, 0, 0, 40, // length: 5*8 = 40
|
||||
0, 0, 0, 0, // reserved
|
||||
0, 0, 0, 0, 0, 0, 0, 0, // LUN 0
|
||||
0, 1, 0, 0, 0, 0, 0, 0, // LUN 1
|
||||
0, 2, 0, 0, 0, 0, 0, 0, // LUN 2
|
||||
0, 3, 0, 0, 0, 0, 0, 0, // LUN 3
|
||||
0, 4, 0, 0, 0, 0, 0, 0, // LUN 4
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_10() {
|
||||
let mut target = EmulatedTarget::new();
|
||||
let dev = BlockDevice::new(test_image());
|
||||
target.add_lun(Box::new(dev));
|
||||
|
||||
// TODO: this test relies on the default logical block size of 512. We should
|
||||
// make that explicit.
|
||||
|
||||
do_command_in(
|
||||
&mut target,
|
||||
&[
|
||||
0x28, // READ (10)
|
||||
0, // flags
|
||||
0, 0, 0, 5, // LBA: 5
|
||||
0, // reserved, group #
|
||||
0, 1, // transfer length: 1
|
||||
0, // control
|
||||
],
|
||||
&[],
|
||||
&[b'5'; 512],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_10_last_block() {
|
||||
let mut target = EmulatedTarget::new();
|
||||
let dev = BlockDevice::new(test_image());
|
||||
target.add_lun(Box::new(dev));
|
||||
|
||||
// TODO: this test relies on the default logical block size of 512. We should
|
||||
// make that explicit.
|
||||
|
||||
do_command_in(
|
||||
&mut target,
|
||||
&[
|
||||
0x28, // READ (10)
|
||||
0, // flags
|
||||
0, 0, 0, 15, // LBA: 5
|
||||
0, // reserved, group #
|
||||
0, 1, // transfer length: 1
|
||||
0, // control
|
||||
],
|
||||
&[],
|
||||
&[b'f'; 512],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_10_out_of_range() {
|
||||
let mut target = EmulatedTarget::new();
|
||||
let dev = BlockDevice::new(test_image());
|
||||
target.add_lun(Box::new(dev));
|
||||
|
||||
// TODO: this test relies on the default logical block size of 512. We should
|
||||
// make that explicit.
|
||||
|
||||
do_command_fail(
|
||||
&mut target,
|
||||
&[
|
||||
0x28, // READ (10)
|
||||
0, // flags
|
||||
0, 0, 0, 16, // LBA: 16
|
||||
0, // reserved, group #
|
||||
0, 1, // transfer length: 1
|
||||
0, // control
|
||||
],
|
||||
sense::LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_10_cross_out() {
|
||||
let mut target = EmulatedTarget::new();
|
||||
let dev = BlockDevice::new(null_image());
|
||||
target.add_lun(Box::new(dev));
|
||||
|
||||
// TODO: this test relies on the default logical block size of 512. We should
|
||||
// make that explicit.
|
||||
|
||||
do_command_fail(
|
||||
&mut target,
|
||||
&[
|
||||
0x28, // READ (10)
|
||||
0, // flags
|
||||
0, 0, 0, 15, // LBA: 15
|
||||
0, // reserved, group #
|
||||
0, 2, // transfer length: 2
|
||||
0, // control
|
||||
],
|
||||
sense::LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_write_10() {
|
||||
let mut target = EmulatedTarget::new();
|
||||
let mut backend = TestBackend::new();
|
||||
let dev = BlockDevice::new(backend.clone());
|
||||
target.add_lun(Box::new(dev));
|
||||
|
||||
// TODO: this test relies on the default logical block size of 512. We should
|
||||
// make that explicit.
|
||||
|
||||
{
|
||||
let data_out = [b'w'; 512];
|
||||
|
||||
do_command_in(
|
||||
&mut target,
|
||||
&[
|
||||
0x2a, // WRITE (10)
|
||||
0, // flags
|
||||
0, 0, 0, 5, // LBA: 5
|
||||
0, // reserved, group #
|
||||
0, 1, // transfer length: 1
|
||||
0, // control
|
||||
],
|
||||
&data_out,
|
||||
&[],
|
||||
);
|
||||
|
||||
let mut buf = [0_u8; 512];
|
||||
backend
|
||||
.read_exact_at(&mut buf, BlockOffset::from(5) * block_size_512())
|
||||
.expect("Reading should work");
|
||||
assert_eq!(data_out, buf);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_write_same_16() {
|
||||
let mut target = EmulatedTarget::new();
|
||||
let mut backend = TestBackend::new();
|
||||
let dev = BlockDevice::new(backend.clone());
|
||||
target.add_lun(Box::new(dev));
|
||||
|
||||
// TODO: this test relies on the default logical block size of 512. We should
|
||||
// make that explicit.
|
||||
|
||||
backend
|
||||
.write_exact_at(&[0xff; 512 * 6], BlockOffset::from(5) * block_size_512())
|
||||
.expect("Write should succeed");
|
||||
|
||||
let data_out = [0_u8; 512];
|
||||
|
||||
do_command_in(
|
||||
&mut target,
|
||||
&[
|
||||
0x93, // WRITE SAME (16)
|
||||
0, // flags
|
||||
0, 0, 0, 0, 0, 0, 0, 5, // LBA: 5
|
||||
0, 0, 0, 5, // tnumber of blocks: 5
|
||||
0, // reserved, group #
|
||||
0, // control
|
||||
],
|
||||
&data_out,
|
||||
&[],
|
||||
);
|
||||
|
||||
let mut buf = [0_u8; 512 * 5];
|
||||
backend
|
||||
.read_exact_at(&mut buf, BlockOffset::from(5) * block_size_512())
|
||||
.expect("Reading should work");
|
||||
assert_eq!([0_u8; 512 * 5], buf, "5 sectors should have been zero'd");
|
||||
|
||||
let mut buf = [0_u8; 512];
|
||||
backend
|
||||
.read_exact_at(&mut buf, BlockOffset::from(10) * block_size_512())
|
||||
.expect("Reading should work");
|
||||
assert_eq!(
|
||||
[0xff_u8; 512], buf,
|
||||
"sector after write should be left untouched"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_capacity_10() {
|
||||
let mut target = EmulatedTarget::new();
|
||||
let dev = BlockDevice::new(test_image());
|
||||
target.add_lun(Box::new(dev));
|
||||
|
||||
// TODO: this test relies on the default logical block size of 512. We should
|
||||
// make that explicit.
|
||||
|
||||
// TODO: we should test behavior with ≥ 2 TiB images. But not sure how we
|
||||
// can do that reliably without risking using 2 TiB of disk
|
||||
|
||||
do_command_in(
|
||||
&mut target,
|
||||
&[
|
||||
0x25, // READ CAPACITY (10)
|
||||
0, 0, 0, 0, 0, 0, 0, 0, // flags
|
||||
0, // control
|
||||
],
|
||||
&[],
|
||||
&[
|
||||
0, 0, 0, 15, // returned LBA (last valid LBA),
|
||||
0, 0, 2, 0, // block size (512)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_capacity_16() {
|
||||
let mut target = EmulatedTarget::new();
|
||||
let dev = BlockDevice::new(test_image());
|
||||
target.add_lun(Box::new(dev));
|
||||
|
||||
// TODO: this test relies on the default logical block size of 512. We should
|
||||
// make that explicit.
|
||||
|
||||
do_command_in(
|
||||
&mut target,
|
||||
&[
|
||||
0x9e, 0x10, // READ CAPACITY (16)
|
||||
0, 0, 0, 0, 0, 0, 0, 0, // obsolete
|
||||
0, 0, 0, 32, // allocation length: 32
|
||||
0, // obselete/reserved
|
||||
0, // control
|
||||
],
|
||||
&[],
|
||||
&[
|
||||
0, 0, 0, 0, 0, 0, 0, 15, // returned LBA (last valid LBA),
|
||||
0, 0, 2, 0, // block size (512)
|
||||
0, // reserved, zoned stuff, protection stuff
|
||||
0, // one PB per LB
|
||||
0xc0, // thin provisioning, unmapped blocks read 0
|
||||
0, // LBA 0 is aligned (top bits above)
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // reserved
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inquiry() {
|
||||
let mut target = EmulatedTarget::new();
|
||||
let dev = BlockDevice::new(null_image());
|
||||
target.add_lun(Box::new(dev));
|
||||
|
||||
do_command_in(
|
||||
&mut target,
|
||||
&[
|
||||
0x12, // INQUIRY
|
||||
0, // EVPD bit: 0
|
||||
0, // page code
|
||||
1, 0, // alloc length: 256
|
||||
0, // control
|
||||
],
|
||||
&[],
|
||||
// some empty comments to get rustfmt to do something vaguely sensible
|
||||
&[
|
||||
0, // accessible; direct acccess block device
|
||||
0, // features
|
||||
0x7, // version
|
||||
0x12, // response data format v2, HiSup = 1
|
||||
91, // addl length
|
||||
0, 0, 0, // unsupported features
|
||||
// vendor
|
||||
b'r', b'u', b's', b't', b'-', b'v', b'm', b'm', //
|
||||
// product
|
||||
b'v', b'h', b'o', b's', b't', b'-', b'u', b's', b'e', b'r', b'-', b's', b'c', b's',
|
||||
b'i', b' ', //
|
||||
// revision
|
||||
b'v', b'0', b' ', b' ', //
|
||||
// reserved/obselete/vendor specific
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
// version descriptors
|
||||
0x0, 0xc0, // SAM-6
|
||||
0x05, 0xc0, // SPC-5 (no code assigned for 6 yet)
|
||||
0x06, 0, // SBC-4
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //
|
||||
// reserved
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_request_sense() {
|
||||
let mut target = EmulatedTarget::new();
|
||||
let dev = BlockDevice::new(null_image());
|
||||
target.add_lun(Box::new(dev));
|
||||
|
||||
do_command_in(
|
||||
&mut target,
|
||||
&[
|
||||
0x3, // INQUIRY
|
||||
0, // desc bit: 0
|
||||
0, 0, // reserved
|
||||
255, // alloc length
|
||||
0, // control
|
||||
],
|
||||
&[],
|
||||
// We'll always return this - modern SCSI has autosense, so any errors are sent with the
|
||||
// response to the command that caused them (and therefore immediately cleared), and
|
||||
// REQUEST SENSE returns an actual error only under some exceptional circumstances
|
||||
// we don't implement.
|
||||
&sense::NO_ADDITIONAL_SENSE_INFORMATION.to_fixed_sense(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_request_sense_descriptor_format() {
|
||||
let mut target = EmulatedTarget::new();
|
||||
let dev = BlockDevice::new(null_image());
|
||||
target.add_lun(Box::new(dev));
|
||||
|
||||
do_command_fail(
|
||||
&mut target,
|
||||
&[
|
||||
0x3, // INQUIRY
|
||||
1, // desc bit: 1
|
||||
0, 0, // reserved
|
||||
255, // alloc length
|
||||
0, // control
|
||||
],
|
||||
// We don't support descriptor format sense data.
|
||||
sense::INVALID_FIELD_IN_CDB,
|
||||
);
|
||||
}
|
420
src/scsi/emulation/tests/report_supported_operation_codes.rs
Normal file
420
src/scsi/emulation/tests/report_supported_operation_codes.rs
Normal file
|
@ -0,0 +1,420 @@
|
|||
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
|
||||
|
||||
use super::{do_command_fail, do_command_in, null_image};
|
||||
use crate::scsi::{
|
||||
emulation::{block_device::BlockDevice, target::EmulatedTarget},
|
||||
sense,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_one_command() {
|
||||
let mut target = EmulatedTarget::new();
|
||||
let dev = BlockDevice::new(null_image());
|
||||
target.add_lun(Box::new(dev));
|
||||
|
||||
do_command_in(
|
||||
&mut target,
|
||||
&[
|
||||
0xa3, 0x0c, // REPORT SUPPORTED OPERATION CODES
|
||||
0b1, // reporting options: one command
|
||||
0, 1, 2, // opcode: TEST UNIT READY, SA ignored
|
||||
0, 0, 1, 0, // allocation length: 256
|
||||
0, // reserved
|
||||
0, // control
|
||||
],
|
||||
&[],
|
||||
&[
|
||||
0, 0b11, // flags, supported
|
||||
0, 6, // cdb len
|
||||
0, 0, 0, 0, 0, 0b0100, // usage data
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_one_command_with_timeout_descriptor() {
|
||||
let mut target = EmulatedTarget::new();
|
||||
let dev = BlockDevice::new(null_image());
|
||||
target.add_lun(Box::new(dev));
|
||||
|
||||
do_command_in(
|
||||
&mut target,
|
||||
&[
|
||||
0xa3, 0x0c, // REPORT SUPPORTED OPERATION CODES
|
||||
0x81, // request timeout descs, reporting options: one command
|
||||
0, 1, 2, // opcode: TEST UNIT READY, SA ignored
|
||||
0, 0, 1, 0, // allocation length: 256
|
||||
0, // reserved
|
||||
0, // control
|
||||
],
|
||||
&[],
|
||||
&[
|
||||
0, 0b11, // flags, supported
|
||||
0, 6, // cdb len
|
||||
0, 0, 0, 0, 0, 0b0100, // usage data
|
||||
0, 0xa, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // no timeouts
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_one_command_unsupported() {
|
||||
let mut target = EmulatedTarget::new();
|
||||
let dev = BlockDevice::new(null_image());
|
||||
target.add_lun(Box::new(dev));
|
||||
|
||||
do_command_in(
|
||||
&mut target,
|
||||
&[
|
||||
0xa3, 0x0c, // REPORT SUPPORTED OPERATION CODES
|
||||
0b1, // reporting options: one command
|
||||
0xff, 1, 2, // opcode: vendor specific, SA ignored
|
||||
0, 0, 1, 0, // allocation length: 256
|
||||
0, // reserved
|
||||
0, // control
|
||||
],
|
||||
&[],
|
||||
&[
|
||||
0, 0b01, // flags, not supported
|
||||
0, 0, // cdb len
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_one_command_valid_service_action() {
|
||||
let mut target = EmulatedTarget::new();
|
||||
let dev = BlockDevice::new(null_image());
|
||||
target.add_lun(Box::new(dev));
|
||||
|
||||
do_command_fail(
|
||||
&mut target,
|
||||
&[
|
||||
0xa3, 0x0c, // REPORT SUPPORTED OPERATION CODES
|
||||
0b1, // reporting options: one command
|
||||
0x9e, 0, 0x10, // SERVICE ACTION IN (16), READ CAPACITY (16)
|
||||
0, 0, 1, 0, // allocation length: 256
|
||||
0, // reserved
|
||||
0, // control
|
||||
],
|
||||
sense::INVALID_FIELD_IN_CDB,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_one_command_invalid_service_action() {
|
||||
let mut target = EmulatedTarget::new();
|
||||
let dev = BlockDevice::new(null_image());
|
||||
target.add_lun(Box::new(dev));
|
||||
|
||||
do_command_fail(
|
||||
&mut target,
|
||||
&[
|
||||
0xa3, 0x0c, // REPORT SUPPORTED OPERATION CODES
|
||||
0b1, // reporting options: one command
|
||||
0x9e, 0, 0xff, // SERVICE ACTION IN (16), invalid
|
||||
0, 0, 1, 0, // allocation length: 256
|
||||
0, // reserved
|
||||
0, // control
|
||||
],
|
||||
sense::INVALID_FIELD_IN_CDB,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_one_service_action() {
|
||||
let mut target = EmulatedTarget::new();
|
||||
let dev = BlockDevice::new(null_image());
|
||||
target.add_lun(Box::new(dev));
|
||||
|
||||
do_command_in(
|
||||
&mut target,
|
||||
&[
|
||||
0xa3, 0x0c, // REPORT SUPPORTED OPERATION CODES
|
||||
0b10, // reporting options: one service action
|
||||
0x9e, 0, 0x10, // SERVICE ACTION IN (16), READ CAPACITY (16)
|
||||
0, 0, 1, 0, // allocation length: 256
|
||||
0, // reserved
|
||||
0, // control
|
||||
],
|
||||
&[],
|
||||
&[
|
||||
0, 0b11, // flags, supported
|
||||
0, 16, // cdb len
|
||||
0x9e, 0x10, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0,
|
||||
0b0100, // usage data
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_one_service_action_with_timeout_descriptor() {
|
||||
let mut target = EmulatedTarget::new();
|
||||
let dev = BlockDevice::new(null_image());
|
||||
target.add_lun(Box::new(dev));
|
||||
|
||||
do_command_in(
|
||||
&mut target,
|
||||
&[
|
||||
0xa3, 0x0c, // REPORT SUPPORTED OPERATION CODES
|
||||
0x82, // request timeout descs, reporting options: one service action
|
||||
0x9e, 0, 0x10, // SERVICE ACTION IN (16), READ CAPACITY (16)
|
||||
0, 0, 1, 0, // allocation length: 256
|
||||
0, // reserved
|
||||
0, // control
|
||||
],
|
||||
&[],
|
||||
&[
|
||||
0, 0b11, // flags, supported
|
||||
0, 16, // cdb len
|
||||
0x9e, 0x10, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0,
|
||||
0b0100, // usage data
|
||||
0, 0xa, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // no timeouts
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_one_service_action_unknown_opcode() {
|
||||
let mut target = EmulatedTarget::new();
|
||||
let dev = BlockDevice::new(null_image());
|
||||
target.add_lun(Box::new(dev));
|
||||
|
||||
// not entirely sure this behavior is correct; see comment in implementation
|
||||
do_command_fail(
|
||||
&mut target,
|
||||
&[
|
||||
0xa3, 0x0c, // REPORT SUPPORTED OPERATION CODES
|
||||
0b10, // reporting options: one service action
|
||||
0xff, 1, 2, // opcode: vendor specific, unimplemented
|
||||
0, 0, 1, 0, // allocation length: 256
|
||||
0, // reserved
|
||||
0, // control
|
||||
],
|
||||
sense::INVALID_FIELD_IN_CDB,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_one_service_action_unknown_service_action() {
|
||||
let mut target = EmulatedTarget::new();
|
||||
let dev = BlockDevice::new(null_image());
|
||||
target.add_lun(Box::new(dev));
|
||||
|
||||
do_command_in(
|
||||
&mut target,
|
||||
&[
|
||||
0xa3, 0x0c, // REPORT SUPPORTED OPERATION CODES
|
||||
0b10, // reporting options: one service action
|
||||
0x9e, 0, 0xff, // SERVICE ACTION IN (16), invalid SA
|
||||
0, 0, 1, 0, // allocation length: 256
|
||||
0, // reserved
|
||||
0, // control
|
||||
],
|
||||
&[],
|
||||
&[
|
||||
0, 0b01, // flags, not supported
|
||||
0, 0, // cdb len
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_one_service_action_not_service_action() {
|
||||
let mut target = EmulatedTarget::new();
|
||||
let dev = BlockDevice::new(null_image());
|
||||
target.add_lun(Box::new(dev));
|
||||
|
||||
do_command_fail(
|
||||
&mut target,
|
||||
&[
|
||||
0xa3, 0x0c, // REPORT SUPPORTED OPERATION CODES
|
||||
0b10, // reporting options: one service action
|
||||
0, 1, 2, // TEST UNIT READY
|
||||
0, 0, 1, 0, // allocation length: 256
|
||||
0, // reserved
|
||||
0, // control
|
||||
],
|
||||
sense::INVALID_FIELD_IN_CDB,
|
||||
);
|
||||
}
|
||||
|
||||
// rest of these tests are for "mode 3", which the spec calls 011b and our
|
||||
// implementation calls OneCommandOrServiceAction, but that's a mouthful so just
|
||||
// use "mode 3" for test names
|
||||
|
||||
#[test]
|
||||
fn test_mode_3_opcode_without_service_action() {
|
||||
let mut target = EmulatedTarget::new();
|
||||
let dev = BlockDevice::new(null_image());
|
||||
target.add_lun(Box::new(dev));
|
||||
|
||||
do_command_in(
|
||||
&mut target,
|
||||
&[
|
||||
0xa3, 0x0c, // REPORT SUPPORTED OPERATION CODES
|
||||
0b11, // reporting options: mode 3
|
||||
0, 0, 0, // opcode: TEST UNIT READY, SA: 0
|
||||
0, 0, 1, 0, // allocation length: 256
|
||||
0, // reserved
|
||||
0, // control
|
||||
],
|
||||
&[],
|
||||
&[
|
||||
0, 0b11, // flags, supported
|
||||
0, 6, // cdb len
|
||||
0, 0, 0, 0, 0, 0b0100, // usage data
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mode_3_with_timeout_descriptor() {
|
||||
let mut target = EmulatedTarget::new();
|
||||
let dev = BlockDevice::new(null_image());
|
||||
target.add_lun(Box::new(dev));
|
||||
|
||||
do_command_in(
|
||||
&mut target,
|
||||
&[
|
||||
0xa3, 0x0c, // REPORT SUPPORTED OPERATION CODES
|
||||
0x83, // request timeout descs, reporting options: mode 3
|
||||
0, 0, 0, // opcode: TEST UNIT READY, SA: 0
|
||||
0, 0, 1, 0, // allocation length: 256
|
||||
0, // reserved
|
||||
0, // control
|
||||
],
|
||||
&[],
|
||||
&[
|
||||
0, 0b11, // flags, supported
|
||||
0, 6, // cdb len
|
||||
0, 0, 0, 0, 0, 0b0100, // usage data
|
||||
0, 0xa, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // no timeouts
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mode_3_opcode_with_unnecessary_service_action() {
|
||||
let mut target = EmulatedTarget::new();
|
||||
let dev = BlockDevice::new(null_image());
|
||||
target.add_lun(Box::new(dev));
|
||||
|
||||
do_command_in(
|
||||
&mut target,
|
||||
&[
|
||||
0xa3, 0x0c, // REPORT SUPPORTED OPERATION CODES
|
||||
0b11, // reporting options: mode 3
|
||||
0, 0, 1, // opcode: TEST UNIT READY, SA: 1
|
||||
0, 0, 1, 0, // allocation length: 256
|
||||
0, // reserved
|
||||
0, // control
|
||||
],
|
||||
&[],
|
||||
&[
|
||||
0, 0b01, // flags, not supported
|
||||
0, 0, // cdb len
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mode_3_invalid_opcode() {
|
||||
let mut target = EmulatedTarget::new();
|
||||
let dev = BlockDevice::new(null_image());
|
||||
target.add_lun(Box::new(dev));
|
||||
|
||||
do_command_in(
|
||||
&mut target,
|
||||
&[
|
||||
0xa3, 0x0c, // REPORT SUPPORTED OPERATION CODES
|
||||
0b11, // reporting options: mode 3
|
||||
0xff, 0, 0, // opcode: vendor specific
|
||||
0, 0, 1, 0, // allocation length: 256
|
||||
0, // reserved
|
||||
0, // control
|
||||
],
|
||||
&[],
|
||||
&[
|
||||
0, 0b01, // flags, not supported
|
||||
0, 0, // cdb len
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mode_3_service_action() {
|
||||
let mut target = EmulatedTarget::new();
|
||||
let dev = BlockDevice::new(null_image());
|
||||
target.add_lun(Box::new(dev));
|
||||
|
||||
do_command_in(
|
||||
&mut target,
|
||||
&[
|
||||
0xa3, 0x0c, // REPORT SUPPORTED OPERATION CODES
|
||||
0b11, // reporting options: mode 3
|
||||
0x9e, 0, 0x10, // opcode: SERVICE ACTION IN (16), READ CAPACITY (16)
|
||||
0, 0, 1, 0, // allocation length: 256
|
||||
0, // reserved
|
||||
0, // control
|
||||
],
|
||||
&[],
|
||||
&[
|
||||
0, 0b11, // flags, supported
|
||||
0, 16, // cdb len
|
||||
0x9e, 0x10, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0,
|
||||
0b0100, // usage data
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mode_3_service_action_with_timeout_descriptor() {
|
||||
let mut target = EmulatedTarget::new();
|
||||
let dev = BlockDevice::new(null_image());
|
||||
target.add_lun(Box::new(dev));
|
||||
|
||||
do_command_in(
|
||||
&mut target,
|
||||
&[
|
||||
0xa3, 0x0c, // REPORT SUPPORTED OPERATION CODES
|
||||
0x83, // request timeout desc, tireporting options: mode 3
|
||||
0x9e, 0, 0x10, // opcode: SERVICE ACTION IN (16), READ CAPACITY (16)
|
||||
0, 0, 1, 0, // allocation length: 256
|
||||
0, // reserved
|
||||
0, // control
|
||||
],
|
||||
&[],
|
||||
&[
|
||||
0, 0b11, // flags, supported
|
||||
0, 16, // cdb len
|
||||
0x9e, 0x10, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0,
|
||||
0b0100, // usage data
|
||||
0, 0xa, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // no timeouts
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mode_3_invalid_service_action() {
|
||||
let mut target = EmulatedTarget::new();
|
||||
let dev = BlockDevice::new(null_image());
|
||||
target.add_lun(Box::new(dev));
|
||||
|
||||
do_command_in(
|
||||
&mut target,
|
||||
&[
|
||||
0xa3, 0x0c, // REPORT SUPPORTED OPERATION CODES
|
||||
0b11, // reporting options: mode 3
|
||||
0x9e, 0, 0xff, // opcode: SERVICE ACTION IN (16), invalid SA
|
||||
0, 0, 1, 0, // allocation length: 256
|
||||
0, // reserved
|
||||
0, // control
|
||||
],
|
||||
&[],
|
||||
&[
|
||||
0, 0b01, // flags, not supported
|
||||
0, 0, // cdb len
|
||||
],
|
||||
);
|
||||
}
|
76
src/scsi/mod.rs
Normal file
76
src/scsi/mod.rs
Normal file
|
@ -0,0 +1,76 @@
|
|||
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
|
||||
|
||||
pub mod emulation;
|
||||
pub mod sense;
|
||||
|
||||
use std::io::{self, Read, Write};
|
||||
|
||||
use self::sense::SenseTriple;
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
|
||||
pub enum TaskAttr {
|
||||
Simple,
|
||||
Ordered,
|
||||
HeadOfQueue,
|
||||
Aca,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct CmdOutput {
|
||||
pub status: u8,
|
||||
pub status_qualifier: u16,
|
||||
pub sense: Vec<u8>,
|
||||
}
|
||||
|
||||
impl CmdOutput {
|
||||
pub const fn ok() -> Self {
|
||||
Self {
|
||||
status: 0,
|
||||
status_qualifier: 0,
|
||||
sense: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_condition(sense: SenseTriple) -> Self {
|
||||
Self {
|
||||
status: 2,
|
||||
status_qualifier: 0,
|
||||
sense: sense.to_fixed_sense(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Request<'a> {
|
||||
pub id: u64,
|
||||
pub cdb: &'a [u8],
|
||||
pub task_attr: TaskAttr,
|
||||
pub crn: u8,
|
||||
pub prio: u8,
|
||||
}
|
||||
|
||||
/// An transport-level error encountered while processing a SCSI command.
|
||||
///
|
||||
/// This is only for transport-level errors; anything else should be handled by
|
||||
/// returning a CHECK CONDITION status at the SCSI level.
|
||||
#[derive(Debug)]
|
||||
pub enum CmdError {
|
||||
/// The provided CDB is too short for its operation code.
|
||||
CdbTooShort,
|
||||
/// An error occurred while writing to the provided data in writer.
|
||||
DataIn(io::Error),
|
||||
}
|
||||
|
||||
/// A transport-independent implementation of a SCSI target.
|
||||
///
|
||||
/// Currently, we only support emulated targets (see the `emulation` module),
|
||||
/// but other implementations of this trait could implement pass-through to
|
||||
/// iSCSI targets or SCSI devices on the host.
|
||||
pub trait Target: Send + Sync {
|
||||
fn execute_command(
|
||||
&mut self,
|
||||
lun: u16,
|
||||
data_out: &mut dyn Read,
|
||||
data_in: &mut dyn Write,
|
||||
req: Request,
|
||||
) -> Result<CmdOutput, CmdError>;
|
||||
}
|
37
src/scsi/sense.rs
Normal file
37
src/scsi/sense.rs
Normal file
|
@ -0,0 +1,37 @@
|
|||
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
pub struct SenseTriple(u8, u8, u8);
|
||||
|
||||
impl SenseTriple {
|
||||
pub fn to_fixed_sense(self) -> Vec<u8> {
|
||||
vec![
|
||||
0x70, // response code (fixed, current); valid bit (0)
|
||||
0x0, // reserved
|
||||
self.0, // sk; various upper bits 0
|
||||
0x0, 0x0, 0x0, 0x0, // information
|
||||
0xa, // add'l sense length
|
||||
0x0, 0x0, 0x0, 0x0, // cmd-specific information
|
||||
self.1, // asc
|
||||
self.2, // ascq
|
||||
0x0, // field-replacable unit code
|
||||
0x0, 0x0, 0x0, // sense-key-sepcific information
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
const NO_SENSE: u8 = 0;
|
||||
const MEDIUM_ERROR: u8 = 0x3;
|
||||
const HARDWARE_ERROR: u8 = 0x4;
|
||||
const ILLEGAL_REQUEST: u8 = 0x5;
|
||||
|
||||
pub const NO_ADDITIONAL_SENSE_INFORMATION: SenseTriple = SenseTriple(NO_SENSE, 0, 0);
|
||||
|
||||
pub const INVALID_COMMAND_OPERATION_CODE: SenseTriple = SenseTriple(ILLEGAL_REQUEST, 0x20, 0x0);
|
||||
pub const LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE: SenseTriple = SenseTriple(ILLEGAL_REQUEST, 0x21, 0x0);
|
||||
pub const INVALID_FIELD_IN_CDB: SenseTriple = SenseTriple(ILLEGAL_REQUEST, 0x24, 0x0);
|
||||
pub const LOGICAL_UNIT_NOT_SUPPORTED: SenseTriple = SenseTriple(ILLEGAL_REQUEST, 0x21, 0x0);
|
||||
pub const SAVING_PARAMETERS_NOT_SUPPORTED: SenseTriple = SenseTriple(ILLEGAL_REQUEST, 0x39, 0x0);
|
||||
|
||||
pub const UNRECOVERED_READ_ERROR: SenseTriple = SenseTriple(MEDIUM_ERROR, 0x11, 0x0);
|
||||
pub const TARGET_FAILURE: SenseTriple = SenseTriple(HARDWARE_ERROR, 0x44, 0x0);
|
Loading…
Reference in a new issue