]> git.proxmox.com Git - proxmox-backup.git/blame - src/tape/drive/mod.rs
change tape drive lock path
[proxmox-backup.git] / src / tape / drive / mod.rs
CommitLineData
37796ff7
DM
1//! Tape drivers
2
fa9c9be7 3mod virtual_tape;
83b8949a 4
a79082a0
DM
5mod lto;
6pub use lto::*;
fa9c9be7 7
25aa55b5 8use std::os::unix::io::AsRawFd;
cd44fb8d 9use std::path::PathBuf;
25aa55b5 10
fa9c9be7 11use anyhow::{bail, format_err, Error};
fe6c1938 12use ::serde::{Deserialize};
feb1645f 13use serde_json::Value;
fa9c9be7 14
2b191385
DM
15use proxmox::{
16 tools::{
17 Uuid,
18 io::ReadExt,
546d2653
DC
19 fs::{
20 fchown,
21 file_read_optional_string,
22 replace_file,
23 CreateOptions,
24 }
2b191385
DM
25 },
26 api::section_config::SectionConfigData,
27};
fa9c9be7 28
c23192d3
WB
29use pbs_datastore::task_log;
30use pbs_datastore::task::TaskState;
31
fa9c9be7 32use crate::{
feb1645f
DM
33 backup::{
34 Fingerprint,
35 KeyConfig,
36 },
fa9c9be7
DM
37 api2::types::{
38 VirtualTapeDrive,
a79082a0 39 LtoTapeDrive,
fa9c9be7 40 },
28926247
DC
41 server::{
42 send_load_media_email,
43 WorkerTask,
44 },
fa9c9be7
DM
45 tape::{
46 TapeWrite,
47 TapeRead,
318b3106 48 BlockReadError,
fe6c1938 49 MediaId,
109ccd30 50 drive::lto::TapeAlertFlags,
fa9c9be7 51 file_formats::{
a78348ac 52 PROXMOX_BACKUP_MEDIA_LABEL_MAGIC_1_0,
fa9c9be7 53 PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0,
a78348ac 54 MediaLabel,
fa9c9be7
DM
55 MediaSetLabel,
56 MediaContentHeader,
57 },
58 changer::{
59 MediaChange,
37796ff7 60 MtxMediaChanger,
fa9c9be7
DM
61 },
62 },
63};
64
fa9c9be7
DM
65/// Tape driver interface
66pub trait TapeDriver {
67
68 /// Flush all data to the tape
69 fn sync(&mut self) -> Result<(), Error>;
70
71 /// Rewind the tape
72 fn rewind(&mut self) -> Result<(), Error>;
73
74 /// Move to end of recorded data
75 ///
7b11a809
DM
76 /// We assume this flushes the tape write buffer. if
77 /// write_missing_eof is true, we verify that there is a filemark
78 /// at the end. If not, we write one.
79 fn move_to_eom(&mut self, write_missing_eof: bool) -> Result<(), Error>;
fa9c9be7 80
20cc25d7 81 /// Move to last file
8b2c6f5d 82 fn move_to_last_file(&mut self) -> Result<(), Error>;
20cc25d7 83
56d36ca4
DC
84 /// Move to given file nr
85 fn move_to_file(&mut self, file: u64) -> Result<(), Error>;
86
fa9c9be7 87 /// Current file number
26aa9aca 88 fn current_file_number(&mut self) -> Result<u64, Error>;
fa9c9be7
DM
89
90 /// Completely erase the media
e29f456e 91 fn format_media(&mut self, fast: bool) -> Result<(), Error>;
fa9c9be7
DM
92
93 /// Read/Open the next file
318b3106 94 fn read_next_file<'a>(&'a mut self) -> Result<Box<dyn TapeRead + 'a>, BlockReadError>;
fa9c9be7
DM
95
96 /// Write/Append a new file
97 fn write_file<'a>(&'a mut self) -> Result<Box<dyn TapeWrite + 'a>, std::io::Error>;
98
99 /// Write label to tape (erase tape content)
fe6c1938 100 fn label_tape(&mut self, label: &MediaLabel) -> Result<(), Error> {
fa9c9be7 101
619554af
DM
102 self.set_encryption(None)?;
103
e29f456e 104 self.format_media(true)?; // this rewinds the tape
fa9c9be7
DM
105
106 let raw = serde_json::to_string_pretty(&serde_json::to_value(&label)?)?;
107
a78348ac 108 let header = MediaContentHeader::new(PROXMOX_BACKUP_MEDIA_LABEL_MAGIC_1_0, raw.len() as u32);
fa9c9be7
DM
109
110 {
111 let mut writer = self.write_file()?;
112 writer.write_header(&header, raw.as_bytes())?;
113 writer.finish(false)?;
114 }
115
116 self.sync()?; // sync data to tape
117
fe6c1938 118 Ok(())
fa9c9be7
DM
119 }
120
121 /// Write the media set label to tape
feb1645f
DM
122 ///
123 /// If the media-set is encrypted, we also store the encryption
124 /// key_config, so that it is possible to restore the key.
125 fn write_media_set_label(
126 &mut self,
127 media_set_label: &MediaSetLabel,
128 key_config: Option<&KeyConfig>,
129 ) -> Result<(), Error>;
fa9c9be7
DM
130
131 /// Read the media label
132 ///
feb1645f
DM
133 /// This tries to read both media labels (label and
134 /// media_set_label). Also returns the optional encryption key configuration.
135 fn read_label(&mut self) -> Result<(Option<MediaId>, Option<KeyConfig>), Error> {
fa9c9be7
DM
136
137 self.rewind()?;
138
fe6c1938 139 let label = {
318b3106
DM
140 let mut reader = match self.read_next_file() {
141 Err(BlockReadError::EndOfStream) => {
142 return Ok((None, None)); // tape is empty
143 }
144 Err(BlockReadError::EndOfFile) => {
145 bail!("got unexpected filemark at BOT");
146 }
147 Err(BlockReadError::Error(err)) => {
148 return Err(err.into());
149 }
150 Ok(reader) => reader,
fa9c9be7
DM
151 };
152
153 let header: MediaContentHeader = unsafe { reader.read_le_value()? };
a78348ac 154 header.check(PROXMOX_BACKUP_MEDIA_LABEL_MAGIC_1_0, 1, 64*1024)?;
fa9c9be7
DM
155 let data = reader.read_exact_allocated(header.size as usize)?;
156
a78348ac 157 let label: MediaLabel = serde_json::from_slice(&data)
fa9c9be7
DM
158 .map_err(|err| format_err!("unable to parse drive label - {}", err))?;
159
160 // make sure we read the EOF marker
161 if reader.skip_to_end()? != 0 {
162 bail!("got unexpected data after label");
163 }
164
fe6c1938 165 label
fa9c9be7
DM
166 };
167
fe6c1938 168 let mut media_id = MediaId { label, media_set_label: None };
fa9c9be7 169
fe6c1938 170 // try to read MediaSet label
318b3106
DM
171 let mut reader = match self.read_next_file() {
172 Err(BlockReadError::EndOfStream) => {
173 return Ok((Some(media_id), None));
174 }
175 Err(BlockReadError::EndOfFile) => {
176 bail!("got unexpected filemark after label");
177 }
178 Err(BlockReadError::Error(err)) => {
179 return Err(err.into());
180 }
181 Ok(reader) => reader,
fa9c9be7
DM
182 };
183
184 let header: MediaContentHeader = unsafe { reader.read_le_value()? };
185 header.check(PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0, 1, 64*1024)?;
186 let data = reader.read_exact_allocated(header.size as usize)?;
187
feb1645f
DM
188 let mut data: Value = serde_json::from_slice(&data)
189 .map_err(|err| format_err!("unable to parse media set label - {}", err))?;
190
191 let key_config_value = data["key-config"].take();
192 let key_config: Option<KeyConfig> = if !key_config_value.is_null() {
193 Some(serde_json::from_value(key_config_value)?)
194 } else {
195 None
196 };
197
198 let media_set_label: MediaSetLabel = serde_json::from_value(data)
fa9c9be7
DM
199 .map_err(|err| format_err!("unable to parse media set label - {}", err))?;
200
201 // make sure we read the EOF marker
202 if reader.skip_to_end()? != 0 {
203 bail!("got unexpected data after media set label");
204 }
205
fe6c1938 206 media_id.media_set_label = Some(media_set_label);
fa9c9be7 207
feb1645f 208 Ok((Some(media_id), key_config))
fa9c9be7
DM
209 }
210
211 /// Eject media
212 fn eject_media(&mut self) -> Result<(), Error>;
5843268c
DM
213
214 /// Read Tape Alert Flags
215 ///
216 /// This make only sense for real LTO drives. Virtual tape drives should
217 /// simply return empty flags (default).
218 fn tape_alert_flags(&mut self) -> Result<TapeAlertFlags, Error> {
219 Ok(TapeAlertFlags::empty())
220 }
d5a48b5c
DM
221
222 /// Set or clear encryption key
2b191385
DM
223 ///
224 /// We use the media_set_uuid to XOR the secret key with the
d1d74c43 225 /// uuid (first 16 bytes), so that each media set uses an unique
2b191385
DM
226 /// key for encryption.
227 fn set_encryption(
228 &mut self,
229 key_fingerprint: Option<(Fingerprint, Uuid)>,
230 ) -> Result<(), Error> {
d5a48b5c
DM
231 if key_fingerprint.is_some() {
232 bail!("drive does not support encryption");
233 }
234 Ok(())
235 }
fa9c9be7
DM
236}
237
284eb5da 238/// Get the media changer (MediaChange + name) associated with a tape drive.
fa9c9be7 239///
284eb5da 240/// Returns Ok(None) if the drive has no associated changer device.
75656a78
DM
241///
242/// Note: This may return the drive name as changer-name if the drive
243/// implements some kind of internal changer (which is true for our
244/// 'virtual' drive implementation).
fa9c9be7
DM
245pub fn media_changer(
246 config: &SectionConfigData,
247 drive: &str,
284eb5da 248) -> Result<Option<(Box<dyn MediaChange>, String)>, Error> {
fa9c9be7
DM
249
250 match config.sections.get(drive) {
251 Some((section_type_name, config)) => {
252 match section_type_name.as_ref() {
253 "virtual" => {
254 let tape = VirtualTapeDrive::deserialize(config)?;
284eb5da 255 Ok(Some((Box::new(tape), drive.to_string())))
fa9c9be7 256 }
a79082a0
DM
257 "lto" => {
258 let drive_config = LtoTapeDrive::deserialize(config)?;
6fe16039 259 match drive_config.changer {
fa9c9be7 260 Some(ref changer_name) => {
6fe16039 261 let changer = MtxMediaChanger::with_drive_config(&drive_config)?;
fa9c9be7 262 let changer_name = changer_name.to_string();
6fe16039 263 Ok(Some((Box::new(changer), changer_name)))
fa9c9be7 264 }
284eb5da 265 None => Ok(None),
fa9c9be7
DM
266 }
267 }
284eb5da 268 _ => bail!("unknown drive type '{}' - internal error"),
fa9c9be7
DM
269 }
270 }
271 None => {
272 bail!("no such drive '{}'", drive);
273 }
274 }
275}
276
284eb5da
DM
277/// Get the media changer (MediaChange + name) associated with a tape drive.
278///
279/// This fail if the drive has no associated changer device.
280pub fn required_media_changer(
281 config: &SectionConfigData,
282 drive: &str,
283) -> Result<(Box<dyn MediaChange>, String), Error> {
284 match media_changer(config, drive) {
285 Ok(Some(result)) => {
38556bf6 286 Ok(result)
284eb5da
DM
287 }
288 Ok(None) => {
289 bail!("drive '{}' has no associated changer device", drive);
290 },
291 Err(err) => {
38556bf6 292 Err(err)
284eb5da
DM
293 }
294 }
295}
296
edda5039 297/// Opens a tape drive (this fails if there is no media loaded)
fa9c9be7
DM
298pub fn open_drive(
299 config: &SectionConfigData,
300 drive: &str,
301) -> Result<Box<dyn TapeDriver>, Error> {
302
303 match config.sections.get(drive) {
304 Some((section_type_name, config)) => {
305 match section_type_name.as_ref() {
306 "virtual" => {
307 let tape = VirtualTapeDrive::deserialize(config)?;
d5a48b5c
DM
308 let handle = tape.open()?;
309 Ok(Box::new(handle))
fa9c9be7 310 }
a79082a0
DM
311 "lto" => {
312 let tape = LtoTapeDrive::deserialize(config)?;
d5a48b5c 313 let handle = tape.open()?;
fa9c9be7
DM
314 Ok(Box::new(handle))
315 }
284eb5da 316 _ => bail!("unknown drive type '{}' - internal error"),
fa9c9be7
DM
317 }
318 }
319 None => {
320 bail!("no such drive '{}'", drive);
321 }
322 }
323}
324
9ac8b73e
DC
325#[derive(PartialEq, Eq)]
326enum TapeRequestError {
327 None,
328 EmptyTape,
329 OpenFailed(String),
330 WrongLabel(String),
331 ReadFailed(String),
332}
333
334impl std::fmt::Display for TapeRequestError {
335 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
336 match self {
337 TapeRequestError::None => {
338 write!(f, "no error")
339 },
340 TapeRequestError::OpenFailed(reason) => {
341 write!(f, "tape open failed - {}", reason)
342 }
343 TapeRequestError::WrongLabel(label) => {
344 write!(f, "wrong media label {}", label)
345 }
346 TapeRequestError::EmptyTape => {
347 write!(f, "found empty media without label (please label all tapes first)")
348 }
349 TapeRequestError::ReadFailed(reason) => {
350 write!(f, "tape read failed - {}", reason)
351 }
352 }
353 }
354}
355
fa9c9be7
DM
356/// Requests a specific 'media' to be inserted into 'drive'. Within a
357/// loop, this then tries to read the media label and waits until it
358/// finds the requested media.
359///
360/// Returns a handle to the opened drive and the media labels.
361pub fn request_and_load_media(
ff58c519 362 worker: &WorkerTask,
fa9c9be7
DM
363 config: &SectionConfigData,
364 drive: &str,
a78348ac 365 label: &MediaLabel,
c9793d47 366 notify_email: &Option<String>,
fa9c9be7
DM
367) -> Result<(
368 Box<dyn TapeDriver>,
fe6c1938 369 MediaId,
fa9c9be7
DM
370), Error> {
371
ff58c519 372 let check_label = |handle: &mut dyn TapeDriver, uuid: &proxmox::tools::Uuid| {
feb1645f 373 if let Ok((Some(media_id), _)) = handle.read_label() {
271764de
DM
374 task_log!(
375 worker,
284eb5da 376 "found media label {} ({})",
8446fbca 377 media_id.label.label_text,
271764de
DM
378 media_id.label.uuid,
379 );
380
ff58c519
DM
381 if media_id.label.uuid == *uuid {
382 return Ok(media_id);
383 }
384 }
385 bail!("read label failed (please label all tapes first)");
386 };
387
fa9c9be7
DM
388 match config.sections.get(drive) {
389 Some((section_type_name, config)) => {
390 match section_type_name.as_ref() {
391 "virtual" => {
ff58c519 392 let mut tape = VirtualTapeDrive::deserialize(config)?;
fa9c9be7 393
8446fbca 394 let label_text = label.label_text.clone();
fa9c9be7 395
8446fbca 396 tape.load_media(&label_text)?;
fa9c9be7 397
ff58c519 398 let mut handle: Box<dyn TapeDriver> = Box::new(tape.open()?);
fa9c9be7 399
ff58c519
DM
400 let media_id = check_label(handle.as_mut(), &label.uuid)?;
401
38556bf6 402 Ok((handle, media_id))
fa9c9be7 403 }
a79082a0
DM
404 "lto" => {
405 let drive_config = LtoTapeDrive::deserialize(config)?;
ff58c519 406
8446fbca 407 let label_text = label.label_text.clone();
fa9c9be7 408
6fe16039 409 if drive_config.changer.is_some() {
fa9c9be7 410
3fbf2311
DM
411 task_log!(worker, "loading media '{}' into drive '{}'", label_text, drive);
412
6fe16039 413 let mut changer = MtxMediaChanger::with_drive_config(&drive_config)?;
8446fbca 414 changer.load_media(&label_text)?;
ff58c519 415
6fe16039 416 let mut handle: Box<dyn TapeDriver> = Box::new(drive_config.open()?);
ff58c519
DM
417
418 let media_id = check_label(handle.as_mut(), &label.uuid)?;
419
420 return Ok((handle, media_id));
421 }
422
9ac8b73e
DC
423 let mut last_error = TapeRequestError::None;
424
425 let update_and_log_request_error =
426 |old: &mut TapeRequestError, new: TapeRequestError| -> Result<(), Error>
427 {
428 if new != *old {
429 task_log!(worker, "{}", new);
430 task_log!(
431 worker,
432 "Please insert media '{}' into drive '{}'",
433 label_text,
434 drive
435 );
436 if let Some(to) = notify_email {
437 send_load_media_email(
438 drive,
439 &label_text,
440 to,
441 Some(new.to_string()),
442 )?;
443 }
444 *old = new;
445 }
446 Ok(())
447 };
78593b5b 448
fa9c9be7 449 loop {
271764de
DM
450 worker.check_abort()?;
451
9ac8b73e 452 if last_error != TapeRequestError::None {
78593b5b
DC
453 for _ in 0..50 { // delay 5 seconds
454 worker.check_abort()?;
455 std::thread::sleep(std::time::Duration::from_millis(100));
456 }
9ac8b73e
DC
457 } else {
458 task_log!(
459 worker,
460 "Checking for media '{}' in drive '{}'",
461 label_text,
462 drive
463 );
78593b5b
DC
464 }
465
6fe16039 466 let mut handle = match drive_config.open() {
fa9c9be7 467 Ok(handle) => handle,
81764111 468 Err(err) => {
9ac8b73e
DC
469 update_and_log_request_error(
470 &mut last_error,
471 TapeRequestError::OpenFailed(err.to_string()),
472 )?;
fa9c9be7
DM
473 continue;
474 }
475 };
476
9ac8b73e
DC
477 let request_error = match handle.read_label() {
478 Ok((Some(media_id), _)) if media_id.label.uuid == label.uuid => {
479 task_log!(
480 worker,
481 "found media label {} ({})",
482 media_id.label.label_text,
483 media_id.label.uuid.to_string(),
484 );
485 return Ok((Box::new(handle), media_id));
486 }
feb1645f 487 Ok((Some(media_id), _)) => {
9ac8b73e
DC
488 let label_string = format!(
489 "{} ({})",
490 media_id.label.label_text,
491 media_id.label.uuid.to_string(),
492 );
493 TapeRequestError::WrongLabel(label_string)
ff58c519 494 }
feb1645f 495 Ok((None, _)) => {
9ac8b73e 496 TapeRequestError::EmptyTape
fa9c9be7 497 }
81764111 498 Err(err) => {
9ac8b73e 499 TapeRequestError::ReadFailed(err.to_string())
81764111 500 }
9ac8b73e
DC
501 };
502
503 update_and_log_request_error(&mut last_error, request_error)?;
fa9c9be7
DM
504 }
505 }
506 _ => bail!("drive type '{}' not implemented!"),
507 }
508 }
509 None => {
510 bail!("no such drive '{}'", drive);
511 }
512 }
513}
25aa55b5 514
e5950360
DC
515#[derive(thiserror::Error, Debug)]
516pub enum TapeLockError {
517 #[error("timeout while trying to lock")]
518 TimeOut,
519 #[error("{0}")]
520 Other(#[from] Error),
521}
522
523impl From<std::io::Error> for TapeLockError {
524 fn from(error: std::io::Error) -> Self {
525 Self::Other(error.into())
526 }
527}
528
d1d74c43 529/// Acquires an exclusive lock for the tape device
25aa55b5
DM
530///
531/// Basically calls lock_device_path() using the configured drive path.
532pub fn lock_tape_device(
533 config: &SectionConfigData,
534 drive: &str,
e5950360 535) -> Result<DeviceLockGuard, TapeLockError> {
546d2653 536 let path = tape_device_path(config, drive)?;
e5950360
DC
537 lock_device_path(&path).map_err(|err| match err {
538 TapeLockError::Other(err) => {
539 TapeLockError::Other(format_err!("unable to lock drive '{}' - {}", drive, err))
540 }
541 other => other,
542 })
546d2653
DC
543}
544
545/// Writes the given state for the specified drive
546///
547/// This function does not lock, so make sure the drive is locked
548pub fn set_tape_device_state(
549 drive: &str,
550 state: &str,
551) -> Result<(), Error> {
cd44fb8d
DM
552
553 let mut path = PathBuf::from(crate::tape::DRIVE_STATE_DIR);
554 path.push(drive);
546d2653
DC
555
556 let backup_user = crate::backup::backup_user()?;
557 let mode = nix::sys::stat::Mode::from_bits_truncate(0o0644);
558 let options = CreateOptions::new()
559 .perm(mode)
560 .owner(backup_user.uid)
561 .group(backup_user.gid);
562
563 replace_file(path, state.as_bytes(), options)
564}
25aa55b5 565
546d2653
DC
566/// Get the device state
567pub fn get_tape_device_state(
568 config: &SectionConfigData,
569 drive: &str,
570) -> Result<Option<String>, Error> {
a0cd0f9c 571 let path = format!("{}/{}", crate::tape::DRIVE_STATE_DIR, drive);
546d2653
DC
572 let state = file_read_optional_string(path)?;
573
574 let device_path = tape_device_path(config, drive)?;
575 if test_device_path_lock(&device_path)? {
576 Ok(state)
577 } else {
578 Ok(None)
579 }
580}
581
582fn tape_device_path(
583 config: &SectionConfigData,
584 drive: &str,
585) -> Result<String, Error> {
25aa55b5
DM
586 match config.sections.get(drive) {
587 Some((section_type_name, config)) => {
588 let path = match section_type_name.as_ref() {
589 "virtual" => {
590 VirtualTapeDrive::deserialize(config)?.path
591 }
a79082a0
DM
592 "lto" => {
593 LtoTapeDrive::deserialize(config)?.path
25aa55b5
DM
594 }
595 _ => bail!("unknown drive type '{}' - internal error"),
596 };
546d2653 597 Ok(path)
25aa55b5
DM
598 }
599 None => {
600 bail!("no such drive '{}'", drive);
601 }
602 }
603}
604
605pub struct DeviceLockGuard(std::fs::File);
606
d1d74c43 607// Acquires an exclusive lock on `device_path`
25aa55b5
DM
608//
609// Uses systemd escape_unit to compute a file name from `device_path`, the try
610// to lock `/var/lock/<name>`.
e5950360 611fn lock_device_path(device_path: &str) -> Result<DeviceLockGuard, TapeLockError> {
25aa55b5
DM
612
613 let lock_name = crate::tools::systemd::escape_unit(device_path, true);
614
a0cd0f9c 615 let mut path = std::path::PathBuf::from(crate::tape::DRIVE_LOCK_DIR);
25aa55b5
DM
616 path.push(lock_name);
617
618 let timeout = std::time::Duration::new(10, 0);
619 let mut file = std::fs::OpenOptions::new().create(true).append(true).open(path)?;
e5950360
DC
620 if let Err(err) = proxmox::tools::fs::lock_file(&mut file, true, Some(timeout)) {
621 if err.kind() == std::io::ErrorKind::Interrupted {
622 return Err(TapeLockError::TimeOut);
623 } else {
624 return Err(err.into());
625 }
626 }
25aa55b5
DM
627
628 let backup_user = crate::backup::backup_user()?;
629 fchown(file.as_raw_fd(), Some(backup_user.uid), Some(backup_user.gid))?;
630
631 Ok(DeviceLockGuard(file))
632}
33c06b33
DC
633
634// Same logic as lock_device_path, but uses a timeout of 0, making it
635// non-blocking, and returning if the file is locked or not
636fn test_device_path_lock(device_path: &str) -> Result<bool, Error> {
637
638 let lock_name = crate::tools::systemd::escape_unit(device_path, true);
639
a0cd0f9c 640 let mut path = std::path::PathBuf::from(crate::tape::DRIVE_LOCK_DIR);
33c06b33
DC
641 path.push(lock_name);
642
643 let timeout = std::time::Duration::new(0, 0);
644 let mut file = std::fs::OpenOptions::new().create(true).append(true).open(path)?;
645 match proxmox::tools::fs::lock_file(&mut file, true, Some(timeout)) {
646 // file was not locked, continue
647 Ok(()) => {},
648 // file was locked, return true
649 Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => return Ok(true),
650 Err(err) => bail!("{}", err),
651 }
652
653 let backup_user = crate::backup::backup_user()?;
654 fchown(file.as_raw_fd(), Some(backup_user.uid), Some(backup_user.gid))?;
655
656 Ok(false)
657}