#![cfg_attr(feature = "cargo-clippy", allow(print_with_newline))]
#![warn(
missing_debug_implementations,
// TODO?..
//missing_docs,
//missing_copy_implementations,
trivial_casts,
trivial_numeric_casts,
unsafe_code,
unstable_features,
unused_import_braces,
unused_qualifications,
)]
extern crate hdd;
use hdd::{device, Device};
use hdd::scsi::SCSIDevice;
use hdd::ata::ATADevice;
use hdd::ata::data::id;
use hdd::drivedb;
use hdd::ata::misc::{self, Misc};
use hdd::scsi::ATAError;
#[macro_use]
extern crate clap;
use clap::{
App,
AppSettings,
Arg,
Values,
};
extern crate serde_json;
extern crate separator;
extern crate number_prefix;
extern crate prettytable;
extern crate log;
extern crate env_logger;
use log::LogLevelFilter;
use env_logger::LogBuilder;
#[macro_use]
extern crate lazy_static;
mod subcommands;
use subcommands::SUBCOMMANDS;
pub fn when_smart_enabled<F>(status: &id::Ternary, action_name: &str, mut action: F) where F: FnMut() -> () {
match *status {
id::Ternary::Unsupported => eprint!("S.M.A.R.T. is not supported, cannot show {}\n", action_name),
id::Ternary::Disabled => eprint!("S.M.A.R.T. is disabled, cannot show {}\n", action_name),
id::Ternary::Enabled => action(),
}
}
#[allow(non_upper_case_globals)]
static drivedb_default: [&'static str; 3] = [
"/var/lib/smartmontools/drivedb/drivedb.h",
"/usr/local/share/smartmontools/drivedb.h", "/usr/share/smartmontools/drivedb.h",
];
#[allow(non_upper_case_globals)]
static drivedb_additional_default: [&'static str; 1] = [
"/etc/smart_drivedb.h",
];
pub fn open_drivedb(options: Option<Values>) -> Option<drivedb::DriveDB> {
let options = options
.map(|vals| vals.collect())
.unwrap_or_else(|| vec![]);
let (paths_add, paths_main): (Vec<&str>, Vec<&str>) = options.iter().partition(|path| path.starts_with('+'));
let paths_add: Vec<&str> = paths_add.iter().map(|path| &path[1..]).collect();
let mut show_warn_main = true;
let mut show_warn_add = true;
let (paths_main, paths_add) = if paths_main.is_empty() {
show_warn_main = false;
let paths_main = drivedb_default.to_vec();
let paths_add = if paths_add.is_empty() {
show_warn_add = false;
drivedb_additional_default.to_vec()
} else {
paths_add
};
(paths_main, paths_add)
} else {
(paths_main, paths_add)
};
let mut loader = drivedb::Loader::new();
for f in paths_add {
match loader.load_additional(f) {
Ok(()) => (),
Err(e) => if show_warn_add {
eprint!("Cannot open additional drivedb file {}: {}\n", f, e);
},
}
}
for f in paths_main {
match loader.load(f) {
Ok(()) => {
break; },
Err(e) => if show_warn_main {
eprint!("Cannot open drivedb file {}: {}\n", f, e);
},
}
}
let db = loader.db();
Some(db)
}
#[cfg(target_os = "linux")]
arg_enum! {
enum Type { Auto, SAT, SCSI }
}
#[cfg(target_os = "freebsd")]
arg_enum! {
enum Type { Auto, ATA, SAT, SCSI }
}
#[derive(Debug)]
pub enum DeviceArgument {
#[cfg(not(target_os = "linux"))]
ATA(ATADevice<Device>, id::Id),
SAT(ATADevice<SCSIDevice>, id::Id),
SCSI(SCSIDevice),
}
fn main() {
let mut log = LogBuilder::new();
let type_variants: Vec<_> = Type::variants().iter()
.map(|s| std::ascii::AsciiExt::to_ascii_lowercase(s.to_owned()))
.collect();
let type_variants: Vec<_> = type_variants.iter()
.map(|s| &**s)
.collect();
let args = App::new("hdd")
.about("yet another disk querying tool")
.version(crate_version!())
.setting(AppSettings::SubcommandRequired)
.subcommands(SUBCOMMANDS.values().map(|&subcommand| subcommand.subcommand()))
.arg(Arg::with_name("type")
.short("t")
.long("type")
.takes_value(true)
.possible_values(type_variants.as_slice())
.help("device type")
)
.arg(Arg::with_name("debug")
.short("d")
.long("debug")
.multiple(true)
.help("Verbose output: set once to log actions, twice to also show raw data buffers\ncan also be set though env_logger's RUST_LOG env")
)
.arg(Arg::with_name("device")
.help("Device to query")
.index(1)
)
.get_matches();
if let Ok(var) = std::env::var("RUST_LOG") {
log.parse(&var);
}
log.filter(Some("hdd"), {
use self::LogLevelFilter::*;
match args.occurrences_of("debug") {
0 => Warn,
1 => Info,
_ => Debug,
}
});
log.init().unwrap();
let path = args.value_of("device");
let dev = path.map(|p| Device::open(p).unwrap());
let dtype = args.value_of("type")
.unwrap_or("auto")
.parse::<Type>().unwrap();
let (subcommand, sargs) = args.subcommand();
let subcommand = SUBCOMMANDS.get(subcommand).unwrap();
let sargs = sargs.unwrap();
let dev = dev.map(|dev| match dtype {
Type::Auto => {
match dev.get_type().unwrap() {
device::Type::SCSI => {
let satdev = ATADevice::new(SCSIDevice::new(dev));
match satdev.get_device_id() {
Ok(id) =>
DeviceArgument::SAT(satdev, id),
Err(misc::Error::SCSI(ATAError::NotSupported)) =>
DeviceArgument::SCSI(satdev.unwrap()),
_ => DeviceArgument::SCSI(satdev.unwrap()),
}
},
#[cfg(not(target_os = "linux"))]
device::Type::ATA => {
let atadev = ATADevice::new(dev);
let id = atadev.get_device_id().unwrap();
DeviceArgument::ATA(atadev, id)
},
}
},
#[cfg(target_os = "freebsd")]
Type::ATA => {
let dev = ATADevice::new(dev);
let id = dev.get_device_id().unwrap();
DeviceArgument::ATA(dev, id)
},
Type::SAT => {
let dev = ATADevice::new(SCSIDevice::new(dev));
let id = dev.get_device_id().unwrap();
DeviceArgument::SAT(dev, id)
},
Type::SCSI => DeviceArgument::SCSI(SCSIDevice::new(dev)),
});
subcommand.run(&path, &dev.as_ref(), sargs)
}