]> git.proxmox.com Git - proxmox-backup.git/blob - src/api2/tape/drive.rs
tape: correctly sort drive api subdir
[proxmox-backup.git] / src / api2 / tape / drive.rs
1 use anyhow::{bail, Error};
2 use serde_json::Value;
3
4 use proxmox::api::{api, Router, SubdirMap};
5 use proxmox::{sortable, identity, list_subdirs_api_method};
6
7 use crate::{
8 config,
9 api2::types::{
10 DRIVE_ID_SCHEMA,
11 MEDIA_LABEL_SCHEMA,
12 LinuxTapeDrive,
13 ScsiTapeChanger,
14 TapeDeviceInfo,
15 },
16 tape::{
17 MediaChange,
18 mtx_load,
19 mtx_unload,
20 linux_tape_device_list,
21 open_drive,
22 media_changer,
23 },
24 };
25
26 #[api(
27 input: {
28 properties: {
29 drive: {
30 schema: DRIVE_ID_SCHEMA,
31 },
32 slot: {
33 description: "Source slot number",
34 minimum: 1,
35 },
36 },
37 },
38 )]
39 /// Load media via changer from slot
40 pub fn load_slot(
41 drive: String,
42 slot: u64,
43 _param: Value,
44 ) -> Result<(), Error> {
45
46 let (config, _digest) = config::drive::config()?;
47
48 let drive_config: LinuxTapeDrive = config.lookup("linux", &drive)?;
49
50 let changer: ScsiTapeChanger = match drive_config.changer {
51 Some(ref changer) => config.lookup("changer", changer)?,
52 None => bail!("drive '{}' has no associated changer", drive),
53 };
54
55 let drivenum = drive_config.changer_drive_id.unwrap_or(0);
56
57 mtx_load(&changer.path, slot, drivenum)
58 }
59
60 #[api(
61 input: {
62 properties: {
63 drive: {
64 schema: DRIVE_ID_SCHEMA,
65 },
66 "changer-id": {
67 schema: MEDIA_LABEL_SCHEMA,
68 },
69 },
70 },
71 )]
72 /// Load media with specified label
73 ///
74 /// Issue a media load request to the associated changer device.
75 pub fn load_media(drive: String, changer_id: String) -> Result<(), Error> {
76
77 let (config, _digest) = config::drive::config()?;
78
79 let (mut changer, _) = media_changer(&config, &drive, false)?;
80
81 changer.load_media(&changer_id)?;
82
83 Ok(())
84 }
85
86 #[api(
87 input: {
88 properties: {
89 drive: {
90 schema: DRIVE_ID_SCHEMA,
91 },
92 slot: {
93 description: "Target slot number. If omitted, defaults to the slot that the drive was loaded from.",
94 minimum: 1,
95 optional: true,
96 },
97 },
98 },
99 )]
100 /// Unload media via changer
101 pub fn unload(
102 drive: String,
103 slot: Option<u64>,
104 _param: Value,
105 ) -> Result<(), Error> {
106
107 let (config, _digest) = config::drive::config()?;
108
109 let mut drive_config: LinuxTapeDrive = config.lookup("linux", &drive)?;
110
111 let changer: ScsiTapeChanger = match drive_config.changer {
112 Some(ref changer) => config.lookup("changer", changer)?,
113 None => bail!("drive '{}' has no associated changer", drive),
114 };
115
116 let drivenum: u64 = 0;
117
118 if let Some(slot) = slot {
119 mtx_unload(&changer.path, slot, drivenum)
120 } else {
121 drive_config.unload_media()
122 }
123 }
124
125 #[api(
126 input: {
127 properties: {},
128 },
129 returns: {
130 description: "The list of autodetected tape drives.",
131 type: Array,
132 items: {
133 type: TapeDeviceInfo,
134 },
135 },
136 )]
137 /// Scan tape drives
138 pub fn scan_drives(_param: Value) -> Result<Vec<TapeDeviceInfo>, Error> {
139
140 let list = linux_tape_device_list();
141
142 Ok(list)
143 }
144
145 #[api(
146 input: {
147 properties: {
148 drive: {
149 schema: DRIVE_ID_SCHEMA,
150 },
151 fast: {
152 description: "Use fast erase.",
153 type: bool,
154 optional: true,
155 default: true,
156 },
157 },
158 },
159 )]
160 /// Erase media
161 pub fn erase_media(drive: String, fast: Option<bool>) -> Result<(), Error> {
162
163 let (config, _digest) = config::drive::config()?;
164
165 let mut drive = open_drive(&config, &drive)?;
166
167 drive.erase_media(fast.unwrap_or(true))?;
168
169 Ok(())
170 }
171
172 #[api(
173 input: {
174 properties: {
175 drive: {
176 schema: DRIVE_ID_SCHEMA,
177 },
178 },
179 },
180 )]
181 /// Rewind tape
182 pub fn rewind(drive: String) -> Result<(), Error> {
183
184 let (config, _digest) = config::drive::config()?;
185
186 let mut drive = open_drive(&config, &drive)?;
187
188 drive.rewind()?;
189
190 Ok(())
191 }
192
193 #[api(
194 input: {
195 properties: {
196 drive: {
197 schema: DRIVE_ID_SCHEMA,
198 },
199 },
200 },
201 )]
202 /// Eject/Unload drive media
203 pub fn eject_media(drive: String) -> Result<(), Error> {
204
205 let (config, _digest) = config::drive::config()?;
206
207 let (mut changer, _) = media_changer(&config, &drive, false)?;
208
209 if !changer.eject_on_unload() {
210 let mut drive = open_drive(&config, &drive)?;
211 drive.eject_media()?;
212 }
213
214 changer.unload_media()?;
215
216 Ok(())
217 }
218
219 #[sortable]
220 pub const SUBDIRS: SubdirMap = &sorted!([
221 (
222 "eject-media",
223 &Router::new()
224 .put(&API_METHOD_EJECT_MEDIA)
225 ),
226 (
227 "erase-media",
228 &Router::new()
229 .put(&API_METHOD_ERASE_MEDIA)
230 ),
231 (
232 "load-slot",
233 &Router::new()
234 .put(&API_METHOD_LOAD_SLOT)
235 ),
236 (
237 "rewind",
238 &Router::new()
239 .put(&API_METHOD_REWIND)
240 ),
241 (
242 "scan",
243 &Router::new()
244 .get(&API_METHOD_SCAN_DRIVES)
245 ),
246 (
247 "unload",
248 &Router::new()
249 .put(&API_METHOD_UNLOAD)
250 ),
251 ]);
252
253 pub const ROUTER: Router = Router::new()
254 .get(&list_subdirs_api_method!(SUBDIRS))
255 .subdirs(SUBDIRS);