]> git.proxmox.com Git - proxmox-backup.git/blob - src/tape/linux_list_drives.rs
78ee6e42c31a720cef327f6bbd7fbd537fbdc8bf
[proxmox-backup.git] / src / tape / linux_list_drives.rs
1 use std::path::{Path, PathBuf};
2 use std::collections::HashMap;
3
4 use anyhow::{bail, Error};
5
6 use crate::{
7 api2::types::{
8 DeviceKind,
9 OptionalDeviceIdentification,
10 TapeDeviceInfo,
11 },
12 tools::fs::scan_subdir,
13 };
14
15 lazy_static::lazy_static!{
16 static ref SCSI_GENERIC_NAME_REGEX: regex::Regex =
17 regex::Regex::new(r"^sg\d+$").unwrap();
18 }
19
20 /// List linux tape changer devices
21 pub fn linux_tape_changer_list() -> Vec<TapeDeviceInfo> {
22
23 let mut list = Vec::new();
24
25 let dir_iter = match scan_subdir(
26 libc::AT_FDCWD,
27 "/sys/class/scsi_generic",
28 &SCSI_GENERIC_NAME_REGEX)
29 {
30 Err(_) => return list,
31 Ok(iter) => iter,
32 };
33
34 for item in dir_iter {
35 let item = match item {
36 Err(_) => continue,
37 Ok(item) => item,
38 };
39
40 let name = item.file_name().to_str().unwrap().to_string();
41
42 let mut sys_path = PathBuf::from("/sys/class/scsi_generic");
43 sys_path.push(&name);
44
45 let device = match udev::Device::from_syspath(&sys_path) {
46 Err(_) => continue,
47 Ok(device) => device,
48 };
49
50 let devnum = match device.devnum() {
51 None => continue,
52 Some(devnum) => devnum,
53 };
54
55 let parent = match device.parent() {
56 None => continue,
57 Some(parent) => parent,
58 };
59
60 match parent.attribute_value("type") {
61 Some(type_osstr) => {
62 if type_osstr != "8" {
63 continue;
64 }
65 }
66 _ => { continue; }
67 }
68
69 // let mut test_path = sys_path.clone();
70 // test_path.push("device/scsi_changer");
71 // if !test_path.exists() { continue; }
72
73 let _dev_path = match device.devnode().map(Path::to_owned) {
74 None => continue,
75 Some(dev_path) => dev_path,
76 };
77
78 let serial = match device.property_value("ID_SCSI_SERIAL")
79 .map(std::ffi::OsString::from)
80 .and_then(|s| if let Ok(s) = s.into_string() { Some(s) } else { None })
81 {
82 None => continue,
83 Some(serial) => serial,
84 };
85
86 let vendor = device.property_value("ID_VENDOR")
87 .map(std::ffi::OsString::from)
88 .and_then(|s| if let Ok(s) = s.into_string() { Some(s) } else { None })
89 .unwrap_or_else(|| String::from("unknown"));
90
91 let model = device.property_value("ID_MODEL")
92 .map(std::ffi::OsString::from)
93 .and_then(|s| if let Ok(s) = s.into_string() { Some(s) } else { None })
94 .unwrap_or_else(|| String::from("unknown"));
95
96 let dev_path = format!("/dev/tape/by-id/scsi-{}", serial);
97
98 if PathBuf::from(&dev_path).exists() {
99 list.push(TapeDeviceInfo {
100 kind: DeviceKind::Changer,
101 path: dev_path,
102 serial,
103 vendor,
104 model,
105 major: unsafe { libc::major(devnum) },
106 minor: unsafe { libc::minor(devnum) },
107 });
108 }
109 }
110
111 list
112 }
113
114 /// List LTO drives
115 pub fn lto_tape_device_list() -> Vec<TapeDeviceInfo> {
116
117 let mut list = Vec::new();
118
119 let dir_iter = match scan_subdir(
120 libc::AT_FDCWD,
121 "/sys/class/scsi_generic",
122 &SCSI_GENERIC_NAME_REGEX)
123 {
124 Err(_) => return list,
125 Ok(iter) => iter,
126 };
127
128 for item in dir_iter {
129 let item = match item {
130 Err(_) => continue,
131 Ok(item) => item,
132 };
133
134 let name = item.file_name().to_str().unwrap().to_string();
135
136 let mut sys_path = PathBuf::from("/sys/class/scsi_generic");
137 sys_path.push(&name);
138
139 let device = match udev::Device::from_syspath(&sys_path) {
140 Err(_) => continue,
141 Ok(device) => device,
142 };
143
144 let devnum = match device.devnum() {
145 None => continue,
146 Some(devnum) => devnum,
147 };
148
149 let parent = match device.parent() {
150 None => continue,
151 Some(parent) => parent,
152 };
153
154 match parent.attribute_value("type") {
155 Some(type_osstr) => {
156 if type_osstr != "1" {
157 continue;
158 }
159 }
160 _ => { continue; }
161 }
162
163 // let mut test_path = sys_path.clone();
164 // test_path.push("device/scsi_tape");
165 // if !test_path.exists() { continue; }
166
167 let _dev_path = match device.devnode().map(Path::to_owned) {
168 None => continue,
169 Some(dev_path) => dev_path,
170 };
171
172 let serial = match device.property_value("ID_SCSI_SERIAL")
173 .map(std::ffi::OsString::from)
174 .and_then(|s| if let Ok(s) = s.into_string() { Some(s) } else { None })
175 {
176 None => continue,
177 Some(serial) => serial,
178 };
179
180 let vendor = device.property_value("ID_VENDOR")
181 .map(std::ffi::OsString::from)
182 .and_then(|s| if let Ok(s) = s.into_string() { Some(s) } else { None })
183 .unwrap_or_else(|| String::from("unknown"));
184
185 let model = device.property_value("ID_MODEL")
186 .map(std::ffi::OsString::from)
187 .and_then(|s| if let Ok(s) = s.into_string() { Some(s) } else { None })
188 .unwrap_or_else(|| String::from("unknown"));
189
190 let dev_path = format!("/dev/tape/by-id/scsi-{}-sg", serial);
191
192 if PathBuf::from(&dev_path).exists() {
193 list.push(TapeDeviceInfo {
194 kind: DeviceKind::Tape,
195 path: dev_path,
196 serial,
197 vendor,
198 model,
199 major: unsafe { libc::major(devnum) },
200 minor: unsafe { libc::minor(devnum) },
201 });
202 }
203 }
204
205 list
206 }
207
208 /// Test if a device exists, and returns associated `TapeDeviceInfo`
209 pub fn lookup_device<'a>(
210 devices: &'a[TapeDeviceInfo],
211 path: &str,
212 ) -> Option<&'a TapeDeviceInfo> {
213
214 if let Ok(stat) = nix::sys::stat::stat(path) {
215
216 let major = unsafe { libc::major(stat.st_rdev) };
217 let minor = unsafe { libc::minor(stat.st_rdev) };
218
219 devices.iter().find(|d| d.major == major && d.minor == minor)
220 } else {
221 None
222 }
223 }
224
225 /// Lookup optional drive identification attributes
226 pub fn lookup_device_identification<'a>(
227 devices: &'a[TapeDeviceInfo],
228 path: &str,
229 ) -> OptionalDeviceIdentification {
230
231 if let Some(info) = lookup_device(devices, path) {
232 OptionalDeviceIdentification {
233 vendor: Some(info.vendor.clone()),
234 model: Some(info.model.clone()),
235 serial: Some(info.serial.clone()),
236 }
237 } else {
238 OptionalDeviceIdentification {
239 vendor: None,
240 model: None,
241 serial: None,
242 }
243 }
244 }
245
246 /// Make sure path is a lto tape device
247 pub fn check_drive_path(
248 drives: &[TapeDeviceInfo],
249 path: &str,
250 ) -> Result<(), Error> {
251 if lookup_device(drives, path).is_none() {
252 bail!("path '{}' is not a lto SCSI-generic tape device", path);
253 }
254 Ok(())
255 }
256
257 // shell completion helper
258
259 /// List changer device paths
260 pub fn complete_changer_path(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
261 linux_tape_changer_list().iter().map(|v| v.path.clone()).collect()
262 }
263
264 /// List tape device paths
265 pub fn complete_drive_path(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
266 lto_tape_device_list().iter().map(|v| v.path.clone()).collect()
267 }