No clue what-all's sitting here.

- Started a bunch of crates.
- Incomplete migration, or rewrites of most.
- Some wild guessing with scsi. Probably better off coming up with my
  own solution if possible? Don't remember. Have to look into how the
  driver works again.
This commit is contained in:
2026-06-21 10:34:44 -04:00
parent 84d222729e
commit 1264340154
18 changed files with 635 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
[package]
name = "dvd"
version.workspace = true
edition.workspace = true
authors.workspace = true
repository.workspace = true
license.workspace = true
publish.workspace = true
[dependencies]
derive_more.workspace = true
libdvdcss.workspace = true
media.workspace = true
+31
View File
@@ -0,0 +1,31 @@
use std::path::PathBuf;
use media::Media;
use crate::error::Error;
pub type DVD = libdvdcss::DVD;
// Probably wipe and restart this.
// Not thought out yet.
impl TryFrom<PathBuf> for DVD {
type Error = crate::Error;
fn try_from(value: PathBuf) -> Result<Self, Self::Error> {
libdvdcss::DVD::new(value).ok_or(Error::LibDvdCssOpenError)
}
}
impl Media for DVD {}
mod error {
use derive_more::{Display, From};
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, From, Display)]
pub enum Error {
LibDvdCssOpenError,
}
}
+10
View File
@@ -0,0 +1,10 @@
[package]
name = "mapping"
version = "0.1.0"
edition.workspace = true
authors.workspace = true
repository.workspace = true
license.workspace = true
publish.workspace = true
[dependencies]
+14
View File
@@ -0,0 +1,14 @@
pub fn add(left: u64, right: u64) -> u64 {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}
+10
View File
@@ -0,0 +1,10 @@
[package]
name = "media"
version = "0.1.0"
edition.workspace = true
authors.workspace = true
repository.workspace = true
license.workspace = true
publish.workspace = true
[dependencies]
+20
View File
@@ -0,0 +1,20 @@
use std::io::Read;
pub trait Media {
fn type_(&self) -> MediaType;
fn encryption(&self) -> Vec<Box<dyn Encryption>>;
fn has_bus_encryption(&self) -> bool;
fn read(&self) -> Box<dyn Read>;
}
pub enum MediaType {
BluRay { writable: bool },
DVD { writable: bool },
}
pub trait Encryption {
fn is_bus_encryption(&self) -> bool;
}
+11
View File
@@ -0,0 +1,11 @@
[package]
name = "plugins"
version = "0.1.0"
edition.workspace = true
authors.workspace = true
repository.workspace = true
license.workspace = true
publish.workspace = true
[dependencies]
semver.workspace = true
+180
View File
@@ -0,0 +1,180 @@
use semver::{Version, VersionReq};
pub struct PluginCompatBuilder {
name: String,
plugin_version: Version,
core_version: Option<VersionReq>,
is_preprocessor: bool,
dependencies: Vec<DependencyCompat>,
incompatibilities: Vec<IncompatibilityCompat>,
}
impl PluginCompatBuilder {
// Fails if the provided name is empty.
pub fn new(name: String, version: Version) -> Option<Self> {
if name.is_empty() {
return None;
}
Some(Self {
name,
plugin_version: version,
..Default::default()
})
}
pub fn core_version(mut self, version: VersionReq) -> Self {
self.core_version = Some(version);
self
}
pub fn preprocessor(mut self, is_preprocessor: bool) -> Self {
self.is_preprocessor = is_preprocessor;
self
}
pub fn dependency(mut self, dep: DependencyCompat) -> Self {
self.dependencies.push(dep);
self
}
pub fn incompatible(mut self, dep: IncompatibilityCompat) -> Self {
self.incompatibilities.push(dep);
self
}
pub fn finalize(self) -> PluginCompat {
PluginCompat {
name: self.name,
plugin_version: self.plugin_version,
core_version: self.core_version,
is_preprocessor: self.is_preprocessor,
dependencies: self.dependencies,
incompatibilities: self.incompatibilities,
}
}
}
impl Default for PluginCompatBuilder {
fn default() -> Self {
Self {
name: String::from("AnonymousPlugin"),
plugin_version: Version::new(0, 1, 0),
core_version: None,
is_preprocessor: false,
dependencies: vec![],
incompatibilities: vec![],
}
}
}
#[derive(Debug, Clone)]
pub struct PluginCompat {
name: String,
plugin_version: Version,
core_version: Option<VersionReq>,
is_preprocessor: bool,
dependencies: Vec<DependencyCompat>,
incompatibilities: Vec<IncompatibilityCompat>,
}
// Most things shouldn't ever be modified by the caller, so passing
// references to reduce memory consumption should be preferable.
//
// Worst case, maybe a few common calls on Version/VersionReq require
// mutability, in which case maybe they won't be by reference.
impl PluginCompat {
pub fn new_dep_or_incompatible(name: String, version: Version) -> Self {
let mut def = Self::default();
def.name = name;
def.plugin_version = version;
def
}
pub fn name(&self) -> &str {
self.name.as_str()
}
pub fn plugin_version(&self) -> &Version {
&self.plugin_version
}
pub fn core_version(&self) -> Option<&VersionReq> {
self.core_version.as_ref()
}
pub fn is_preprocessor(&self) -> bool {
self.is_preprocessor
}
pub fn is_postprocessor(&self) -> bool {
!self.is_preprocessor()
}
pub fn dependencies(&self) -> &[DependencyCompat] {
self.dependencies.as_slice()
}
pub fn incompatibilities(&self) -> &[IncompatibilityCompat] {
self.incompatibilities.as_slice()
}
/// Returns if there is *any* incompatibility between `self` and `plugin`.
pub fn incompatible_with<C: AsRef<PluginCompat>>(&self, plugin: C) -> bool {
return self.contains_incompatibility_with(&plugin)
&& plugin.as_ref().contains_incompatibility_with(self);
}
/// Returns if `self` has any incompatibility with `plugin`
fn contains_incompatibility_with<C: AsRef<PluginCompat>>(&self, plugin: C) -> bool {
if !self.incompatibilities.is_empty()
&& self.incompatibilities.iter().any(|inc| {
inc.name() == plugin.as_ref().name()
&& !inc.version.matches(&plugin.as_ref().plugin_version)
})
{
true
} else {
false
}
}
}
impl Default for PluginCompat {
fn default() -> Self {
Self {
name: String::from("AnonymousPlugin"),
plugin_version: Version::new(0, 1, 0),
core_version: None,
is_preprocessor: false,
dependencies: vec![],
incompatibilities: vec![],
}
}
}
impl AsRef<Self> for PluginCompat {
fn as_ref(&self) -> &Self {
&self
}
}
#[derive(Debug, Clone)]
pub struct DependencyCompat {
name: String,
version: VersionReq,
}
impl DependencyCompat {
pub fn name(&self) -> &str {
self.name.as_str()
}
pub fn plugin_version(&self) -> &VersionReq {
&self.version
}
}
pub type IncompatibilityCompat = DependencyCompat;
+14
View File
@@ -0,0 +1,14 @@
use semver::Version;
use crate::compat::PluginCompat;
pub mod compat;
// Surely there's a way to automate this against the cargo manifest?
pub const CORE_VERSION: Version = Version::new(0, 1, 0);
pub trait Plugin {
fn name(&self) -> &str;
fn compatibility(&self) -> PluginCompat;
}
+16
View File
@@ -0,0 +1,16 @@
[package]
name = "scsi"
version.workspace = true
edition.workspace = true
authors.workspace = true
repository.workspace = true
license.workspace = true
publish.workspace = true
[dependencies]
derive_more.workspace = true
nix = { version = "0.31.2", features = ["ioctl"] } # Re-exports a compatible libc version.
packed_struct = "0.10"
[build-dependencies]
bindgen = "0.72.1"
+24
View File
@@ -0,0 +1,24 @@
use std::env;
use std::path::PathBuf;
fn main() {
// Path from which to search for shared libraries.
// Is there no better (automated), platform-specific way?
println!("cargo:rustc-link-search=/usr/lib");
// Locate and link the shared library.
//println!("cargo:rustc-link-lib=sg3_utils");
let bindings = bindgen::Builder::default()
.header("src/wrapper.h")
// Invalidate the build if a header has changed.
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.generate()
.expect("Failed to generate bindings.");
// Write the bindings to the $OUT_DIR/bindings.rs file.
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
bindings
.write_to_file(out_path.join("bindings.rs"))
.expect("Couldn't write bindings!");
}
+9
View File
@@ -0,0 +1,9 @@
use derive_more::{Display, From};
use nix;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, From, Display)]
pub enum Error {
Nix(nix::Error),
}
+73
View File
@@ -0,0 +1,73 @@
mod error;
mod sg;
mod spc;
pub use error::Error;
use packed_struct::{
derive::PackedStruct,
types::{Integer, ReservedZero, bits::Bits},
};
/*
#[derive(Debug)]
pub struct Command6 {
opcode: u8,
payload: [u8; 4],
control: u8,
}
#[derive(Debug)]
pub struct Command10 {
opcode: u8,
payload: [u8; 8],
control: u8,
}
#[derive(Debug)]
pub struct Command12 {
opcode: u8,
payload: [u8; 10],
control: u8,
}
#[derive(Debug)]
pub struct Command16 {
opcode: u8,
payload: [u8; 14],
control: u8,
}
#[derive(Debug)]
pub struct CommandVariable {
opcode: u8,
control: u8,
payload: [u8; 4],
additional_len: u8,
service_action: [u8; 2],
additional_payload: Vec<u8>,
}
// There's also XCDBs... but I'm not about to mess with that.
#[derive(Debug)]
pub struct SenseData {}
*/
#[derive(Debug, Default, PackedStruct)]
#[packed_struct(bit_numbering = "msb0")]
pub struct Control {
#[packed_field(bits = "0..2")]
vendor_specific: Integer<u8, Bits<2>>,
#[packed_field(bits = "2..5")]
_reserved: ReservedZero<Bits<3>>,
#[packed_field(bits = "5..6")]
naca: bool,
#[packed_field(bits = "6")]
_obsolete_1: bool, // SCSI uses LSB0 bit ordering, so name accordingly.
#[packed_field(bits = "7")]
_obsolete_0: bool, // SCSI uses LSB0 bit ordering, so name accordingly.
}
pub trait Command {}
+85
View File
@@ -0,0 +1,85 @@
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(unused)]
use std::{fs::File, io::Write, ops::Add};
use packed_struct::{
PackedStruct,
derive::PackedStruct,
types::{Integer, ReservedZero, bits::Bits},
};
use crate::{Command, spc::InquiryCommand};
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
#[derive(PackedStruct)]
#[packed_struct(endian = "msb")]
pub struct SgCommand {
#[packed_field(element_size_bytes = "36")]
header: Header,
#[packed_field(element_size_bytes = "6")]
scsi_cmd: InquiryCommand,
}
impl From<InquiryCommand> for SgCommand {
fn from(value: InquiryCommand) -> Self {
SgCommand {
header: Header::new(&value, 0, 0),
scsi_cmd: value,
}
}
}
#[derive(Debug, Default, PackedStruct)]
#[packed_struct(endian = "msb")]
pub struct Header {
_packet_len: u32,
reply_len: u32,
#[packed_field(element_size_bits = "32")]
_pack_id: ReservedZero<Bits<32>>,
result: u32,
#[packed_field(element_size_bits = "1")]
twelve_byte: bool,
#[packed_field(element_size_bits = "5")]
target_status: Integer<u8, Bits<5>>,
#[packed_field(element_size_bits = "8")]
host_status: u8,
#[packed_field(element_size_bits = "8")]
driver_status: u8,
#[packed_field(element_size_bits = "10")]
_other_flags: ReservedZero<Bits<10>>,
sense_buffer: [u8; 16],
}
impl Header {
fn new(relevant_cmd: &InquiryCommand, in_size: u32, out_size: u32) -> Self {
Self {
_packet_len: (size_of::<Header>() as u32
+ size_of::<InquiryCommand>() as u32
+ in_size)
.into(),
reply_len: size_of::<Header>() as u32 + out_size,
_pack_id: ReservedZero::default(),
result: 0,
twelve_byte: size_of_val(relevant_cmd) == 12,
target_status: 0.into(),
host_status: 0,
driver_status: 0,
_other_flags: ReservedZero::default(),
sense_buffer: [0; 16],
}
}
}
pub fn send_command(mut fd: File, cmd: SgCommand) -> Result<(), std::io::Error> {
fd.write_all(&cmd.pack().unwrap())
}
+80
View File
@@ -0,0 +1,80 @@
use packed_struct::derive::PackedStruct;
use packed_struct::types::ReservedZero;
use packed_struct::types::bits::Bits;
use crate::{Command, Control};
#[derive(Debug, PackedStruct)]
#[packed_struct(endian = "msb")]
pub struct InquiryCommand {
opcode: u8,
#[packed_field(element_size_bits = "6")]
_reserved: ReservedZero<Bits<6>>,
#[packed_field(element_size_bits = "1")]
_obsolete: ReservedZero<Bits<1>>,
#[packed_field(element_size_bits = "1")]
enable_vital_product_data: bool,
page_code: u8,
allocation_length: u16,
#[packed_field(element_size_bytes = "1")]
control: Control,
}
impl InquiryCommand {
pub fn new(evpd: bool, page_code: u8, alloc_len: u16) -> Self {
let mut cmd = Self::default();
cmd.enable_vital_product_data = evpd;
cmd.page_code = page_code;
cmd.allocation_length = alloc_len;
cmd
}
}
impl Default for InquiryCommand {
fn default() -> Self {
Self {
opcode: 0x12,
_reserved: ReservedZero::default(),
_obsolete: ReservedZero::default(),
enable_vital_product_data: false,
page_code: 0,
allocation_length: 0,
control: Control::default(),
}
}
}
impl Command for InquiryCommand {}
#[cfg(test)]
mod tests {
use std::fs::OpenOptions;
use crate::sg::{SgCommand, send_command};
use super::*;
#[test]
fn pack_inquiry() {
let cmd = InquiryCommand::new(false, 0, 36);
println!("{}", cmd.packed_struct_display_formatter());
}
#[test]
fn send_inquiry() {
let fd = OpenOptions::new()
.read(true)
.write(true)
.open("/dev/sr0")
.unwrap();
dbg!(&fd);
let cmd = SgCommand::from(InquiryCommand::new(false, 0, 36));
send_command(fd, cmd).unwrap();
}
}
+1
View File
@@ -0,0 +1 @@
#include <scsi/sg.h>
+13
View File
@@ -0,0 +1,13 @@
[package]
name = "scraper"
version.workspace = true
edition.workspace = true
authors.workspace = true
repository.workspace = true
license.workspace = true
publish.workspace = true
[dependencies]
plugins.workspace = true
semver.workspace = true
+31
View File
@@ -0,0 +1,31 @@
use plugins::compat::{PluginCompat, PluginCompatBuilder};
use plugins::{self, Plugin};
use semver::{Version, VersionReq};
const PLUGIN_NAME: &str = "Scraper";
const PLUGIN_VERSION: Version = Version::new(0, 1, 0);
pub struct DynamicPlugin;
impl DynamicPlugin {
#[allow(dead_code)]
#[unsafe(no_mangle)]
fn new() -> Box<dyn Plugin> {
Box::new(Self)
}
}
impl Plugin for DynamicPlugin {
#[unsafe(no_mangle)]
fn name(&self) -> &str {
PLUGIN_NAME
}
#[unsafe(no_mangle)]
fn compatibility(&self) -> PluginCompat {
PluginCompatBuilder::new(PLUGIN_NAME.to_string(), PLUGIN_VERSION)
.unwrap()
.core_version(VersionReq::parse(plugins::CORE_VERSION.to_string().as_str()).unwrap())
.finalize()
}
}