3 use super::format_definition
::*;
6 use std
::os
::unix
::io
::AsRawFd
;
7 use std
::os
::unix
::ffi
::OsStrExt
;
8 use std
::os
::unix
::io
::RawFd
;
9 use std
::path
::{Path, PathBuf}
;
14 use nix
::fcntl
::OFlag
;
15 use nix
::sys
::stat
::Mode
;
16 use nix
::errno
::Errno
;
17 use nix
::sys
::stat
::FileStat
;
19 use siphasher
::sip
::SipHasher24
;
21 const FILE_COPY_BUFFER_SIZE
: usize = 1024*1024;
23 pub struct CaTarEncoder
<W
: Write
> {
24 current_path
: PathBuf
, // used for error reporting
28 file_copy_buffer
: Vec
<u8>,
32 impl <W
: Write
> CaTarEncoder
<W
> {
34 pub fn encode(path
: PathBuf
, dir
: &mut nix
::dir
::Dir
, writer
: W
) -> Result
<(), Error
> {
36 let mut file_copy_buffer
= Vec
::with_capacity(FILE_COPY_BUFFER_SIZE
);
37 unsafe { file_copy_buffer.set_len(FILE_COPY_BUFFER_SIZE); }
47 // todo: use scandirat??
53 fn write(&mut self, buf
: &[u8]) -> Result
<(), Error
> {
54 self.writer
.write(buf
)?
;
55 self.writer_pos
+= buf
.len();
59 fn flush_copy_buffer(&mut self, size
: usize) -> Result
<(), Error
> {
60 self.writer
.write(&self.file_copy_buffer
[..size
])?
;
61 self.writer_pos
+= size
;
65 fn write_header(&mut self, htype
: u64, size
: u64) -> Result
<(), Error
> {
67 let mut buffer
= [0u8; std
::mem
::size_of
::<CaFormatHeader
>()];
68 let mut header
= crate::tools
::map_struct_mut
::<CaFormatHeader
>(&mut buffer
)?
;
69 header
.size
= u64::to_le((std
::mem
::size_of
::<CaFormatHeader
>() as u64) + size
);
70 header
.htype
= u64::to_le(htype
);
77 fn write_filename(&mut self, name
: &CStr
) -> Result
<(), Error
> {
79 let buffer
= name
.to_bytes_with_nul();
80 self.write_header(CA_FORMAT_FILENAME
, buffer
.len() as u64)?
;
86 fn write_entry(&mut self, stat
: &FileStat
) -> Result
<(), Error
> {
88 let mut buffer
= [0u8; std
::mem
::size_of
::<CaFormatHeader
>() + std
::mem
::size_of
::<CaFormatEntry
>()];
89 let mut header
= crate::tools
::map_struct_mut
::<CaFormatHeader
>(&mut buffer
)?
;
90 header
.size
= u64::to_le((std
::mem
::size_of
::<CaFormatHeader
>() + std
::mem
::size_of
::<CaFormatEntry
>()) as u64);
91 header
.htype
= u64::to_le(CA_FORMAT_ENTRY
);
93 let mut entry
= crate::tools
::map_struct_mut
::<CaFormatEntry
>(&mut buffer
[std
::mem
::size_of
::<CaFormatHeader
>()..])?
;
95 entry
.feature_flags
= u64::to_le(CA_FORMAT_FEATURE_FLAGS_MAX
);
97 if (stat
.st_mode
& libc
::S_IFMT
) == libc
::S_IFLNK
{
98 entry
.mode
= u64::to_le((libc
::S_IFLNK
| 0o777) as u64);
100 let mode
= stat
.st_mode
& (libc
::S_IFMT
| 0o7777);
101 entry
.mode
= u64::to_le(mode
as u64);
104 entry
.flags
= 0; // todo: CHATTR, FAT_ATTRS, subvolume?
106 entry
.uid
= u64::to_le(stat
.st_uid
as u64);
107 entry
.gid
= u64::to_le(stat
.st_gid
as u64);
109 let mtime
= stat
.st_mtime
* 1_000_000_000 + stat
.st_mtime_nsec
;
110 if mtime
> 0 { entry.mtime = mtime as u64 }
;
112 self.write(&buffer
)?
;
117 fn encode_dir(&mut self, dir
: &mut nix
::dir
::Dir
) -> Result
<(), Error
> {
119 println
!("encode_dir: {:?} start {}", self.current_path
, self.writer_pos
);
121 let mut name_list
= vec
![];
123 let rawfd
= dir
.as_raw_fd();
125 let dir_stat
= match nix
::sys
::stat
::fstat(rawfd
) {
127 Err(err
) => bail
!("fstat {:?} failed - {}", self.current_path
, err
),
130 if (dir_stat
.st_mode
& libc
::S_IFMT
) != libc
::S_IFDIR
{
131 bail
!("got unexpected file type {:?} (not a directory)", self.current_path
);
134 let dir_start_pos
= self.writer_pos
;
136 self.write_entry(&dir_stat
)?
;
138 for entry
in dir
.iter() {
139 let entry
= match entry
{
141 Err(err
) => bail
!("readir {:?} failed - {}", self.current_path
, err
),
143 let filename
= entry
.file_name().to_owned();
145 let name
= filename
.to_bytes_with_nul();
146 let name_len
= name
.len();
147 if name_len
== 2 && name
[0] == b'
.'
&& name
[1] == 0u8 { continue; }
148 if name_len
== 3 && name
[0] == b'
.'
&& name
[1] == b'
.'
&& name
[2] == 0u8 { continue; }
150 match nix
::sys
::stat
::fstatat(rawfd
, filename
.as_ref(), nix
::fcntl
::AtFlags
::AT_SYMLINK_NOFOLLOW
) {
152 name_list
.push((filename
, stat
));
154 Err(nix
::Error
::Sys(Errno
::ENOENT
)) => self.report_vanished_file(&self.current_path
)?
,
155 Err(err
) => bail
!("fstat {:?} failed - {}", self.current_path
, err
),
159 name_list
.sort_unstable_by(|a
, b
| a
.0.cmp(&b
.0));
161 let mut goodby_items
= vec
![];
163 for (filename
, stat
) in &name_list
{
164 self.current_path
.push(std
::ffi
::OsStr
::from_bytes(filename
.as_bytes()));
166 let start_pos
= self.writer_pos
;
168 self.write_filename(&filename
)?
;
170 if (stat
.st_mode
& libc
::S_IFMT
) == libc
::S_IFDIR
{
172 match nix
::dir
::Dir
::openat(rawfd
, filename
.as_ref(), OFlag
::O_NOFOLLOW
, Mode
::empty()) {
173 Ok(mut dir
) => self.encode_dir(&mut dir
)?
,
174 Err(nix
::Error
::Sys(Errno
::ENOENT
)) => self.report_vanished_file(&self.current_path
)?
,
175 Err(err
) => bail
!("open dir {:?} failed - {}", self.current_path
, err
),
178 } else if (stat
.st_mode
& libc
::S_IFMT
) == libc
::S_IFREG
{
179 match nix
::fcntl
::openat(rawfd
, filename
.as_ref(), OFlag
::O_NOFOLLOW
, Mode
::empty()) {
181 let res
= self.encode_file(filefd
);
182 let _
= nix
::unistd
::close(filefd
); // ignore close errors
185 Err(nix
::Error
::Sys(Errno
::ENOENT
)) => self.report_vanished_file(&self.current_path
)?
,
186 Err(err
) => bail
!("open file {:?} failed - {}", self.current_path
, err
),
188 } else if (stat
.st_mode
& libc
::S_IFMT
) == libc
::S_IFLNK
{
189 let mut buffer
= [0u8; libc
::PATH_MAX
as usize];
191 let res
= filename
.with_nix_path(|cstr
| {
192 unsafe { libc::readlinkat(rawfd, cstr.as_ptr(), buffer.as_mut_ptr() as *mut libc::c_char, buffer.len()-1) }
195 match Errno
::result(res
) {
197 buffer
[len
as usize] = 0u8; // add Nul byte
198 self.encode_symlink(&buffer
[..((len
+1) as usize)], &stat
)?
200 Err(nix
::Error
::Sys(Errno
::ENOENT
)) => self.report_vanished_file(&self.current_path
)?
,
201 Err(err
) => bail
!("readlink {:?} failed - {}", self.current_path
, err
),
204 bail
!("unsupported file type (mode {:o} {:?})", stat
.st_mode
, self.current_path
);
207 let end_pos
= self.writer_pos
;
209 goodby_items
.push(CaFormatGoodbyeItem
{
210 offset
: start_pos
as u64,
211 size
: (end_pos
- start_pos
) as u64,
212 hash
: compute_goodby_hash(&filename
),
215 self.current_path
.pop();
218 println
!("encode_dir: {:?} end {}", self.current_path
, self.writer_pos
);
220 let goodby_start
= self.writer_pos
as u64;
221 let goodby_table_size
= (goodby_items
.len() + 1)*std
::mem
::size_of
::<CaFormatGoodbyeItem
>();
223 for item
in &mut goodby_items
{
224 item
.offset
= goodby_start
- item
.offset
;
227 // fixme: sort goodby_items (BST)
229 let goodby_offset
= self.writer_pos
- dir_start_pos
;
231 // append CaFormatGoodbyeTail as last item
232 goodby_items
.push(CaFormatGoodbyeItem
{
233 offset
: goodby_offset
as u64,
234 size
: (goodby_table_size
+ std
::mem
::size_of
::<CaFormatHeader
>()) as u64,
235 hash
: CA_FORMAT_GOODBYE_TAIL_MARKER
,
238 self.write_header(CA_FORMAT_GOODBYE
, goodby_table_size
as u64)?
;
240 if goodby_table_size
> FILE_COPY_BUFFER_SIZE
{
241 bail
!("goodby table too large ({} > {})", goodby_table_size
, FILE_COPY_BUFFER_SIZE
);
244 let buffer
= &mut self.file_copy_buffer
;
245 let buffer_ptr
= buffer
.as_ptr();
246 for (i
, item
) in goodby_items
.iter().enumerate() {
248 *(buffer_ptr
.add(i
*std
::mem
::size_of
::<CaFormatGoodbyeItem
>()) as *mut u64) = u64::to_le(item
.offset
);
249 *(buffer_ptr
.add(i
*std
::mem
::size_of
::<CaFormatGoodbyeItem
>()+8) as *mut u64) = u64::to_le(item
.size
);
250 *(buffer_ptr
.add(i
*std
::mem
::size_of
::<CaFormatGoodbyeItem
>()+16) as *mut u64) = u64::to_le(item
.hash
);
254 self.flush_copy_buffer(goodby_table_size
)?
;
256 println
!("encode_dir: {:?} end1 {}", self.current_path
, self.writer_pos
);
260 fn encode_file(&mut self, filefd
: RawFd
) -> Result
<(), Error
> {
262 println
!("encode_file: {:?}", self.current_path
);
264 let stat
= match nix
::sys
::stat
::fstat(filefd
) {
266 Err(err
) => bail
!("fstat {:?} failed - {}", self.current_path
, err
),
269 if (stat
.st_mode
& libc
::S_IFMT
) != libc
::S_IFREG
{
270 bail
!("got unexpected file type {:?} (not a regular file)", self.current_path
);
273 self.write_entry(&stat
)?
;
275 let size
= stat
.st_size
as u64;
277 self.write_header(CA_FORMAT_PAYLOAD
, size
)?
;
279 let mut pos
: u64 = 0;
281 let n
= match nix
::unistd
::read(filefd
, &mut self.file_copy_buffer
) {
283 Err(nix
::Error
::Sys(Errno
::EINTR
)) => continue /* try again */,
284 Err(err
) => bail
!("read {:?} failed - {}", self.current_path
, err
),
288 // Note:: casync format cannot handle that
289 bail
!("detected shrinked file {:?} ({} < {})", self.current_path
, pos
, size
);
294 let mut next
= pos
+ (n
as u64);
296 if next
> size { next = size; }
298 let count
= (next
- pos
) as usize;
300 self.flush_copy_buffer(count
)?
;
304 if pos
>= size { break; }
310 fn encode_symlink(&mut self, target
: &[u8], stat
: &FileStat
) -> Result
<(), Error
> {
312 println
!("encode_symlink: {:?} -> {:?}", self.current_path
, target
);
314 self.write_entry(stat
)?
;
316 self.write_header(CA_FORMAT_SYMLINK
, target
.len() as u64)?
;
322 // the report_XXX method may raise and error - depending on encoder configuration
324 fn report_vanished_file(&self, path
: &Path
) -> Result
<(), Error
> {
326 eprintln
!("WARNING: detected vanished file {:?}", path
);
332 fn compute_goodby_hash(name
: &CStr
) -> u64 {
334 use std
::hash
::Hasher
;
335 let mut hasher
= SipHasher24
::new_with_keys(0x8574442b0f1d84b3, 0x2736ed30d1c22ec1);
336 hasher
.write(name
.to_bytes());