1 //! *catar* format encoder.
3 //! This module contain the code to generate *catar* archive files.
7 use super::format_definition
::*;
8 use super::binary_search_tree
::*;
11 use std
::os
::unix
::io
::AsRawFd
;
12 use std
::os
::unix
::ffi
::OsStrExt
;
13 use std
::os
::unix
::io
::RawFd
;
14 use std
::path
::{Path, PathBuf}
;
19 use nix
::fcntl
::OFlag
;
20 use nix
::sys
::stat
::Mode
;
21 use nix
::errno
::Errno
;
22 use nix
::sys
::stat
::FileStat
;
24 /// The format requires to build sorted directory lookup tables in
25 /// memory, so we restrict the number of allowed entries to limit
26 /// maximum memory usage.
27 pub const MAX_DIRECTORY_ENTRIES
: usize = 256*1024;
29 pub struct CaTarEncoder
<W
: Write
> {
30 current_path
: PathBuf
, // used for error reporting
34 file_copy_buffer
: Vec
<u8>,
38 impl <W
: Write
> CaTarEncoder
<W
> {
40 pub fn encode(path
: PathBuf
, dir
: &mut nix
::dir
::Dir
, writer
: W
) -> Result
<(), Error
> {
42 const FILE_COPY_BUFFER_SIZE
: usize = 1024*1024;
44 let mut file_copy_buffer
= Vec
::with_capacity(FILE_COPY_BUFFER_SIZE
);
45 unsafe { file_copy_buffer.set_len(FILE_COPY_BUFFER_SIZE); }
55 // todo: use scandirat??
61 fn write(&mut self, buf
: &[u8]) -> Result
<(), Error
> {
62 self.writer
.write(buf
)?
;
63 self.writer_pos
+= buf
.len();
67 fn flush_copy_buffer(&mut self, size
: usize) -> Result
<(), Error
> {
68 self.writer
.write(&self.file_copy_buffer
[..size
])?
;
69 self.writer_pos
+= size
;
73 fn write_header(&mut self, htype
: u64, size
: u64) -> Result
<(), Error
> {
75 let mut buffer
= [0u8; std
::mem
::size_of
::<CaFormatHeader
>()];
76 let mut header
= crate::tools
::map_struct_mut
::<CaFormatHeader
>(&mut buffer
)?
;
77 header
.size
= u64::to_le((std
::mem
::size_of
::<CaFormatHeader
>() as u64) + size
);
78 header
.htype
= u64::to_le(htype
);
85 fn write_filename(&mut self, name
: &CStr
) -> Result
<(), Error
> {
87 let buffer
= name
.to_bytes_with_nul();
88 self.write_header(CA_FORMAT_FILENAME
, buffer
.len() as u64)?
;
94 fn write_entry(&mut self, stat
: &FileStat
) -> Result
<(), Error
> {
96 let mut buffer
= [0u8; std
::mem
::size_of
::<CaFormatHeader
>() + std
::mem
::size_of
::<CaFormatEntry
>()];
97 let mut header
= crate::tools
::map_struct_mut
::<CaFormatHeader
>(&mut buffer
)?
;
98 header
.size
= u64::to_le((std
::mem
::size_of
::<CaFormatHeader
>() + std
::mem
::size_of
::<CaFormatEntry
>()) as u64);
99 header
.htype
= u64::to_le(CA_FORMAT_ENTRY
);
101 let mut entry
= crate::tools
::map_struct_mut
::<CaFormatEntry
>(&mut buffer
[std
::mem
::size_of
::<CaFormatHeader
>()..])?
;
103 entry
.feature_flags
= u64::to_le(CA_FORMAT_FEATURE_FLAGS_MAX
);
105 if (stat
.st_mode
& libc
::S_IFMT
) == libc
::S_IFLNK
{
106 entry
.mode
= u64::to_le((libc
::S_IFLNK
| 0o777) as u64);
108 let mode
= stat
.st_mode
& (libc
::S_IFMT
| 0o7777);
109 entry
.mode
= u64::to_le(mode
as u64);
112 entry
.flags
= 0; // todo: CHATTR, FAT_ATTRS, subvolume?
114 entry
.uid
= u64::to_le(stat
.st_uid
as u64);
115 entry
.gid
= u64::to_le(stat
.st_gid
as u64);
117 let mtime
= stat
.st_mtime
* 1_000_000_000 + stat
.st_mtime_nsec
;
118 if mtime
> 0 { entry.mtime = mtime as u64 }
;
120 self.write(&buffer
)?
;
125 fn write_goodbye_table(&mut self, goodbye_offset
: usize, goodbye_items
: &[CaFormatGoodbyeItem
]) -> Result
<(), Error
> {
127 let item_count
= goodbye_items
.len();
129 let goodbye_table_size
= (item_count
+ 1)*std
::mem
::size_of
::<CaFormatGoodbyeItem
>();
131 self.write_header(CA_FORMAT_GOODBYE
, goodbye_table_size
as u64)?
;
133 if self.file_copy_buffer
.capacity() < goodbye_table_size
{
134 let need
= goodbye_table_size
- self.file_copy_buffer
.capacity();
135 self.file_copy_buffer
.reserve(need
);
136 unsafe { self.file_copy_buffer.set_len(self.file_copy_buffer.capacity()); }
139 let buffer
= &mut self.file_copy_buffer
;
141 copy_binary_search_tree(item_count
, |s
, d
| {
142 let item
= &goodbye_items
[s
];
143 let offset
= d
*std
::mem
::size_of
::<CaFormatGoodbyeItem
>();
144 let dest
= crate::tools
::map_struct_mut
::<CaFormatGoodbyeItem
>(&mut buffer
[offset
..]).unwrap();
145 dest
.offset
= u64::to_le(item
.offset
);
146 dest
.size
= u64::to_le(item
.size
);
147 dest
.hash
= u64::to_le(item
.hash
);
150 // append CaFormatGoodbyeTail as last item
151 let offset
= item_count
*std
::mem
::size_of
::<CaFormatGoodbyeItem
>();
152 let dest
= crate::tools
::map_struct_mut
::<CaFormatGoodbyeItem
>(&mut buffer
[offset
..]).unwrap();
153 dest
.offset
= u64::to_le(goodbye_offset
as u64);
154 dest
.size
= u64::to_le((goodbye_table_size
+ std
::mem
::size_of
::<CaFormatHeader
>()) as u64);
155 dest
.hash
= u64::to_le(CA_FORMAT_GOODBYE_TAIL_MARKER
);
157 self.flush_copy_buffer(goodbye_table_size
)?
;
162 fn encode_dir(&mut self, dir
: &mut nix
::dir
::Dir
) -> Result
<(), Error
> {
164 println
!("encode_dir: {:?} start {}", self.current_path
, self.writer_pos
);
166 let mut name_list
= vec
![];
168 let rawfd
= dir
.as_raw_fd();
170 let dir_stat
= match nix
::sys
::stat
::fstat(rawfd
) {
172 Err(err
) => bail
!("fstat {:?} failed - {}", self.current_path
, err
),
175 if (dir_stat
.st_mode
& libc
::S_IFMT
) != libc
::S_IFDIR
{
176 bail
!("got unexpected file type {:?} (not a directory)", self.current_path
);
179 let dir_start_pos
= self.writer_pos
;
181 self.write_entry(&dir_stat
)?
;
183 let mut dir_count
= 0;
185 for entry
in dir
.iter() {
187 if dir_count
> MAX_DIRECTORY_ENTRIES
{
188 bail
!("too many directory items in {:?} (> {})",
189 self.current_path
, MAX_DIRECTORY_ENTRIES
);
192 let entry
= match entry
{
194 Err(err
) => bail
!("readir {:?} failed - {}", self.current_path
, err
),
196 let filename
= entry
.file_name().to_owned();
198 let name
= filename
.to_bytes_with_nul();
199 let name_len
= name
.len();
200 if name_len
== 2 && name
[0] == b'
.'
&& name
[1] == 0u8 { continue; }
201 if name_len
== 3 && name
[0] == b'
.'
&& name
[1] == b'
.'
&& name
[2] == 0u8 { continue; }
203 match nix
::sys
::stat
::fstatat(rawfd
, filename
.as_ref(), nix
::fcntl
::AtFlags
::AT_SYMLINK_NOFOLLOW
) {
205 name_list
.push((filename
, stat
));
207 Err(nix
::Error
::Sys(Errno
::ENOENT
)) => self.report_vanished_file(&self.current_path
)?
,
208 Err(err
) => bail
!("fstat {:?} failed - {}", self.current_path
, err
),
212 name_list
.sort_unstable_by(|a
, b
| a
.0.cmp(&b
.0));
214 let mut goodbye_items
= vec
![];
216 for (filename
, stat
) in &name_list
{
217 self.current_path
.push(std
::ffi
::OsStr
::from_bytes(filename
.as_bytes()));
219 let start_pos
= self.writer_pos
;
221 self.write_filename(&filename
)?
;
223 if (stat
.st_mode
& libc
::S_IFMT
) == libc
::S_IFDIR
{
225 match nix
::dir
::Dir
::openat(rawfd
, filename
.as_ref(), OFlag
::O_NOFOLLOW
, Mode
::empty()) {
226 Ok(mut dir
) => self.encode_dir(&mut dir
)?
,
227 Err(nix
::Error
::Sys(Errno
::ENOENT
)) => self.report_vanished_file(&self.current_path
)?
,
228 Err(err
) => bail
!("open dir {:?} failed - {}", self.current_path
, err
),
231 } else if (stat
.st_mode
& libc
::S_IFMT
) == libc
::S_IFREG
{
232 match nix
::fcntl
::openat(rawfd
, filename
.as_ref(), OFlag
::O_NOFOLLOW
, Mode
::empty()) {
234 let res
= self.encode_file(filefd
);
235 let _
= nix
::unistd
::close(filefd
); // ignore close errors
238 Err(nix
::Error
::Sys(Errno
::ENOENT
)) => self.report_vanished_file(&self.current_path
)?
,
239 Err(err
) => bail
!("open file {:?} failed - {}", self.current_path
, err
),
241 } else if (stat
.st_mode
& libc
::S_IFMT
) == libc
::S_IFLNK
{
242 let mut buffer
= [0u8; libc
::PATH_MAX
as usize];
244 let res
= filename
.with_nix_path(|cstr
| {
245 unsafe { libc::readlinkat(rawfd, cstr.as_ptr(), buffer.as_mut_ptr() as *mut libc::c_char, buffer.len()-1) }
248 match Errno
::result(res
) {
250 buffer
[len
as usize] = 0u8; // add Nul byte
251 self.encode_symlink(&buffer
[..((len
+1) as usize)], &stat
)?
253 Err(nix
::Error
::Sys(Errno
::ENOENT
)) => self.report_vanished_file(&self.current_path
)?
,
254 Err(err
) => bail
!("readlink {:?} failed - {}", self.current_path
, err
),
257 bail
!("unsupported file type (mode {:o} {:?})", stat
.st_mode
, self.current_path
);
260 let end_pos
= self.writer_pos
;
262 goodbye_items
.push(CaFormatGoodbyeItem
{
263 offset
: start_pos
as u64,
264 size
: (end_pos
- start_pos
) as u64,
265 hash
: compute_goodbye_hash(filename
.to_bytes()),
268 self.current_path
.pop();
271 println
!("encode_dir: {:?} end {}", self.current_path
, self.writer_pos
);
273 // fixup goodby item offsets
274 let goodbye_start
= self.writer_pos
as u64;
275 for item
in &mut goodbye_items
{
276 item
.offset
= goodbye_start
- item
.offset
;
279 let goodbye_offset
= self.writer_pos
- dir_start_pos
;
281 self.write_goodbye_table(goodbye_offset
, &goodbye_items
)?
;
283 println
!("encode_dir: {:?} end1 {}", self.current_path
, self.writer_pos
);
287 fn encode_file(&mut self, filefd
: RawFd
) -> Result
<(), Error
> {
289 println
!("encode_file: {:?}", self.current_path
);
291 let stat
= match nix
::sys
::stat
::fstat(filefd
) {
293 Err(err
) => bail
!("fstat {:?} failed - {}", self.current_path
, err
),
296 if (stat
.st_mode
& libc
::S_IFMT
) != libc
::S_IFREG
{
297 bail
!("got unexpected file type {:?} (not a regular file)", self.current_path
);
300 self.write_entry(&stat
)?
;
302 let size
= stat
.st_size
as u64;
304 self.write_header(CA_FORMAT_PAYLOAD
, size
)?
;
306 let mut pos
: u64 = 0;
308 let n
= match nix
::unistd
::read(filefd
, &mut self.file_copy_buffer
) {
310 Err(nix
::Error
::Sys(Errno
::EINTR
)) => continue /* try again */,
311 Err(err
) => bail
!("read {:?} failed - {}", self.current_path
, err
),
315 // Note:: casync format cannot handle that
316 bail
!("detected shrinked file {:?} ({} < {})", self.current_path
, pos
, size
);
321 let mut next
= pos
+ (n
as u64);
323 if next
> size { next = size; }
325 let count
= (next
- pos
) as usize;
327 self.flush_copy_buffer(count
)?
;
331 if pos
>= size { break; }
337 fn encode_symlink(&mut self, target
: &[u8], stat
: &FileStat
) -> Result
<(), Error
> {
339 println
!("encode_symlink: {:?} -> {:?}", self.current_path
, target
);
341 self.write_entry(stat
)?
;
343 self.write_header(CA_FORMAT_SYMLINK
, target
.len() as u64)?
;
349 // the report_XXX method may raise and error - depending on encoder configuration
351 fn report_vanished_file(&self, path
: &Path
) -> Result
<(), Error
> {
353 eprintln
!("WARNING: detected vanished file {:?}", path
);