]> git.proxmox.com Git - proxmox-backup.git/blob - src/tape/file_formats/snapshot_archive.rs
tape: cleanup - move tape file readers/writers into src/tape/file_formats folder
[proxmox-backup.git] / src / tape / file_formats / snapshot_archive.rs
1 use std::io::{Read, Write};
2 use std::pin::Pin;
3 use std::task::{Context, Poll};
4
5 use proxmox::{
6 sys::error::SysError,
7 tools::Uuid,
8 };
9
10 use crate::tape::{
11 TapeWrite,
12 SnapshotReader,
13 file_formats::{
14 PROXMOX_TAPE_BLOCK_SIZE,
15 PROXMOX_BACKUP_SNAPSHOT_ARCHIVE_MAGIC_1_0,
16 MediaContentHeader,
17 },
18 };
19
20 /// Write a set of files as `pxar` archive to the tape
21 ///
22 /// This ignores file attributes like ACLs and xattrs.
23 ///
24 /// Returns `Ok(Some(content_uuid))` on succees, and `Ok(None)` if
25 /// `LEOM` was detected before all data was written. The stream is
26 /// marked inclomplete in that case and does not contain all data (The
27 /// backup task must rewrite the whole file on the next media).
28 pub fn tape_write_snapshot_archive<'a>(
29 writer: &mut (dyn TapeWrite + 'a),
30 snapshot_reader: &SnapshotReader,
31 ) -> Result<Option<Uuid>, std::io::Error> {
32
33 let snapshot = snapshot_reader.snapshot().to_string();
34 let file_list = snapshot_reader.file_list();
35
36 let header_data = snapshot.as_bytes().to_vec();
37
38 let header = MediaContentHeader::new(
39 PROXMOX_BACKUP_SNAPSHOT_ARCHIVE_MAGIC_1_0, header_data.len() as u32);
40 let content_uuid = header.uuid.into();
41
42 let root_metadata = pxar::Metadata::dir_builder(0o0664).build();
43
44 let mut file_copy_buffer = proxmox::tools::vec::undefined(PROXMOX_TAPE_BLOCK_SIZE);
45
46 let result: Result<(), std::io::Error> = proxmox::try_block!({
47
48 let leom = writer.write_header(&header, &header_data)?;
49 if leom {
50 return Err(std::io::Error::from_raw_os_error(nix::errno::Errno::ENOSPC as i32));
51 }
52
53 let mut encoder = pxar::encoder::sync::Encoder::new(PxarTapeWriter::new(writer), &root_metadata)?;
54
55 for filename in file_list.iter() {
56
57 let mut file = snapshot_reader.open_file(filename)
58 .map_err(|err| proxmox::io_format_err!("open file '{}' failed - {}", filename, err))?;
59 let metadata = file.metadata()?;
60 let file_size = metadata.len();
61
62 let metadata: pxar::Metadata = metadata.into();
63
64 if !metadata.is_regular_file() {
65 proxmox::io_bail!("file '{}' is not a regular file", filename);
66 }
67
68 let mut remaining = file_size;
69 let mut out = encoder.create_file(&metadata, filename, file_size)?;
70 while remaining != 0 {
71 let got = file.read(&mut file_copy_buffer[..])?;
72 if got as u64 > remaining {
73 proxmox::io_bail!("file '{}' changed while reading", filename);
74 }
75 out.write_all(&file_copy_buffer[..got])?;
76 remaining -= got as u64;
77
78 }
79 if remaining > 0 {
80 proxmox::io_bail!("file '{}' shrunk while reading", filename);
81 }
82 }
83 encoder.finish()?;
84 Ok(())
85 });
86
87 match result {
88 Ok(()) => {
89 writer.finish(false)?;
90 Ok(Some(content_uuid))
91 }
92 Err(err) => {
93 if err.is_errno(nix::errno::Errno::ENOSPC) && writer.logical_end_of_media() {
94 writer.finish(true)?; // mark as incomplete
95 Ok(None)
96 } else {
97 Err(err)
98 }
99 }
100 }
101 }
102
103 // Helper to create pxar archives on tape
104 //
105 // We generate and error at LEOM,
106 struct PxarTapeWriter<'a, T: TapeWrite + ?Sized> {
107 inner: &'a mut T,
108 }
109
110 impl<'a, T: TapeWrite + ?Sized> PxarTapeWriter<'a, T> {
111 pub fn new(inner: &'a mut T) -> Self {
112 Self { inner }
113 }
114 }
115
116 impl<'a, T: TapeWrite + ?Sized> pxar::encoder::SeqWrite for PxarTapeWriter<'a, T> {
117
118 fn poll_seq_write(
119 self: Pin<&mut Self>,
120 _cx: &mut Context,
121 buf: &[u8],
122 ) -> Poll<std::io::Result<usize>> {
123 let this = unsafe { self.get_unchecked_mut() };
124 Poll::Ready(match this.inner.write_all(buf) {
125 Ok(leom) => {
126 if leom {
127 Err(std::io::Error::from_raw_os_error(nix::errno::Errno::ENOSPC as i32))
128 } else {
129 Ok(buf.len())
130 }
131 }
132 Err(err) => Err(err),
133 })
134 }
135
136 fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context) -> Poll<std::io::Result<()>> {
137 Poll::Ready(Ok(()))
138 }
139 }