3 use std
::io
::prelude
::*;
7 use {EntryType, Header, other}
;
8 use header
::{bytes2path, HeaderMode, path2bytes}
;
10 /// A structure for building archives
12 /// This structure has methods for building up an archive from scratch into any
14 pub struct Builder
<W
: Write
> {
21 impl<W
: Write
> Builder
<W
> {
22 /// Create a new archive builder with the underlying object as the
23 /// destination of all data written. The builder will use
24 /// `HeaderMode::Complete` by default.
25 pub fn new(obj
: W
) -> Builder
<W
> {
27 mode
: HeaderMode
::Complete
,
34 fn inner(&mut self) -> &mut W
{
35 self.obj
.as_mut().unwrap()
38 /// Changes the HeaderMode that will be used when reading fs Metadata for
39 /// methods that implicitly read metadata for an input Path. Notably, this
40 /// does _not_ apply to `append(Header)`.
41 pub fn mode(&mut self, mode
: HeaderMode
) {
45 /// Follow symlinks, archiving the contents of the file they point to rather
46 /// than adding a symlink to the archive. Defaults to true.
47 pub fn follow_symlinks(&mut self, follow
: bool
) {
51 /// Unwrap this archive, returning the underlying object.
53 /// This function will finish writing the archive if the `finish` function
54 /// hasn't yet been called, returning any I/O error which happens during
56 pub fn into_inner(mut self) -> io
::Result
<W
> {
60 Ok(self.obj
.take().unwrap())
63 /// Adds a new entry to this archive.
65 /// This function will append the header specified, followed by contents of
66 /// the stream specified by `data`. To produce a valid archive the `size`
67 /// field of `header` must be the same as the length of the stream that's
68 /// being written. Additionally the checksum for the header should have been
69 /// set via the `set_cksum` method.
71 /// Note that this will not attempt to seek the archive to a valid position,
72 /// so if the archive is in the middle of a read or some other similar
73 /// operation then this may corrupt the archive.
75 /// Also note that after all entries have been written to an archive the
76 /// `finish` function needs to be called to finish writing the archive.
80 /// This function will return an error for any intermittent I/O error which
81 /// occurs when either reading or writing.
86 /// use tar::{Builder, Header};
88 /// let mut header = Header::new_gnu();
89 /// header.set_path("foo").unwrap();
90 /// header.set_size(4);
91 /// header.set_cksum();
93 /// let mut data: &[u8] = &[1, 2, 3, 4];
95 /// let mut ar = Builder::new(Vec::new());
96 /// ar.append(&header, data).unwrap();
97 /// let data = ar.into_inner().unwrap();
99 pub fn append
<R
: Read
>(&mut self, header
: &Header
, mut data
: R
)
101 append(self.inner(), header
, &mut data
)
104 /// Adds a new entry to this archive with the specified path.
106 /// This function will set the specified path in the given header, which may
107 /// require appending a GNU long-name extension entry to the archive first.
108 /// The checksum for the header will be automatically updated via the
109 /// `set_cksum` method after setting the path. No other metadata in the
110 /// header will be modified.
112 /// Then it will append the header, followed by contents of the stream
113 /// specified by `data`. To produce a valid archive the `size` field of
114 /// `header` must be the same as the length of the stream that's being
117 /// Note that this will not attempt to seek the archive to a valid position,
118 /// so if the archive is in the middle of a read or some other similar
119 /// operation then this may corrupt the archive.
121 /// Also note that after all entries have been written to an archive the
122 /// `finish` function needs to be called to finish writing the archive.
126 /// This function will return an error for any intermittent I/O error which
127 /// occurs when either reading or writing.
132 /// use tar::{Builder, Header};
134 /// let mut header = Header::new_gnu();
135 /// header.set_size(4);
136 /// header.set_cksum();
138 /// let mut data: &[u8] = &[1, 2, 3, 4];
140 /// let mut ar = Builder::new(Vec::new());
141 /// ar.append_data(&mut header, "really/long/path/to/foo", data).unwrap();
142 /// let data = ar.into_inner().unwrap();
144 pub fn append_data
<P
: AsRef
<Path
>, R
: Read
>(&mut self, header
: &mut Header
, path
: P
, data
: R
)
146 prepare_header(self.inner(), header
, path
.as_ref())?
;
148 self.append(&header
, data
)
151 /// Adds a file on the local filesystem to this archive.
153 /// This function will open the file specified by `path` and insert the file
154 /// into the archive with the appropriate metadata set, returning any I/O
155 /// error which occurs while writing. The path name for the file inside of
156 /// this archive will be the same as `path`, and it is required that the
157 /// path is a relative path.
159 /// Note that this will not attempt to seek the archive to a valid position,
160 /// so if the archive is in the middle of a read or some other similar
161 /// operation then this may corrupt the archive.
163 /// Also note that after all files have been written to an archive the
164 /// `finish` function needs to be called to finish writing the archive.
169 /// use tar::Builder;
171 /// let mut ar = Builder::new(Vec::new());
173 /// ar.append_path("foo/bar.txt").unwrap();
175 pub fn append_path
<P
: AsRef
<Path
>>(&mut self, path
: P
) -> io
::Result
<()> {
176 let mode
= self.mode
.clone();
177 let follow
= self.follow
;
178 append_path(self.inner(), path
.as_ref(), mode
, follow
)
181 /// Adds a file to this archive with the given path as the name of the file
184 /// This will use the metadata of `file` to populate a `Header`, and it will
185 /// then append the file to the archive with the name `path`.
187 /// Note that this will not attempt to seek the archive to a valid position,
188 /// so if the archive is in the middle of a read or some other similar
189 /// operation then this may corrupt the archive.
191 /// Also note that after all files have been written to an archive the
192 /// `finish` function needs to be called to finish writing the archive.
197 /// use std::fs::File;
198 /// use tar::Builder;
200 /// let mut ar = Builder::new(Vec::new());
202 /// // Open the file at one location, but insert it into the archive with a
203 /// // different name.
204 /// let mut f = File::open("foo/bar/baz.txt").unwrap();
205 /// ar.append_file("bar/baz.txt", &mut f).unwrap();
207 pub fn append_file
<P
: AsRef
<Path
>>(&mut self, path
: P
, file
: &mut fs
::File
)
209 let mode
= self.mode
.clone();
210 append_file(self.inner(), path
.as_ref(), file
, mode
)
213 /// Adds a directory to this archive with the given path as the name of the
214 /// directory in the archive.
216 /// This will use `stat` to populate a `Header`, and it will then append the
217 /// directory to the archive with the name `path`.
219 /// Note that this will not attempt to seek the archive to a valid position,
220 /// so if the archive is in the middle of a read or some other similar
221 /// operation then this may corrupt the archive.
223 /// Also note that after all files have been written to an archive the
224 /// `finish` function needs to be called to finish writing the archive.
230 /// use tar::Builder;
232 /// let mut ar = Builder::new(Vec::new());
234 /// // Use the directory at one location, but insert it into the archive
235 /// // with a different name.
236 /// ar.append_dir("bardir", ".").unwrap();
238 pub fn append_dir
<P
, Q
>(&mut self, path
: P
, src_path
: Q
) -> io
::Result
<()>
239 where P
: AsRef
<Path
>, Q
: AsRef
<Path
>
241 let mode
= self.mode
.clone();
242 append_dir(self.inner(), path
.as_ref(), src_path
.as_ref(), mode
)
245 /// Adds a directory and all of its contents (recursively) to this archive
246 /// with the given path as the name of the directory in the archive.
248 /// Note that this will not attempt to seek the archive to a valid position,
249 /// so if the archive is in the middle of a read or some other similar
250 /// operation then this may corrupt the archive.
252 /// Also note that after all files have been written to an archive the
253 /// `finish` function needs to be called to finish writing the archive.
259 /// use tar::Builder;
261 /// let mut ar = Builder::new(Vec::new());
263 /// // Use the directory at one location, but insert it into the archive
264 /// // with a different name.
265 /// ar.append_dir_all("bardir", ".").unwrap();
267 pub fn append_dir_all
<P
, Q
>(&mut self, path
: P
, src_path
: Q
) -> io
::Result
<()>
268 where P
: AsRef
<Path
>, Q
: AsRef
<Path
>
270 let mode
= self.mode
.clone();
271 let follow
= self.follow
;
272 append_dir_all(self.inner(), path
.as_ref(), src_path
.as_ref(), mode
, follow
)
275 /// Finish writing this archive, emitting the termination sections.
277 /// This function should only be called when the archive has been written
278 /// entirely and if an I/O error happens the underlying object still needs
281 /// In most situations the `into_inner` method should be preferred.
282 pub fn finish(&mut self) -> io
::Result
<()> {
286 self.finished
= true;
287 self.inner().write_all(&[0; 1024])
291 fn append(mut dst
: &mut Write
,
293 mut data
: &mut Read
) -> io
::Result
<()> {
294 dst
.write_all(header
.as_bytes())?
;
295 let len
= io
::copy(&mut data
, &mut dst
)?
;
297 // Pad with zeros if necessary.
299 let remaining
= 512 - (len
% 512);
301 dst
.write_all(&buf
[..remaining
as usize])?
;
307 fn append_path(dst
: &mut Write
, path
: &Path
, mode
: HeaderMode
, follow
: bool
) -> io
::Result
<()> {
308 let stat
= if follow
{
309 fs
::metadata(path
).map_err(|err
| io
::Error
::new(
311 format
!("{} when getting metadata for {}", err
, path
.display()),
314 fs
::symlink_metadata(path
).map_err(|err
| io
::Error
::new(
316 format
!("{} when getting metadata for {}", err
, path
.display()),
320 append_fs(dst
, path
, &stat
, &mut fs
::File
::open(path
)?
, mode
, None
)
321 } else if stat
.is_dir() {
322 append_fs(dst
, path
, &stat
, &mut io
::empty(), mode
, None
)
323 } else if stat
.file_type().is_symlink() {
324 let link_name
= fs
::read_link(path
)?
;
325 append_fs(dst
, path
, &stat
, &mut io
::empty(), mode
, Some(&link_name
))
327 Err(other(&format
!("{} has unknown file type", path
.display())))
331 fn append_file(dst
: &mut Write
, path
: &Path
, file
: &mut fs
::File
, mode
: HeaderMode
)
333 let stat
= file
.metadata()?
;
334 append_fs(dst
, path
, &stat
, file
, mode
, None
)
337 fn append_dir(dst
: &mut Write
, path
: &Path
, src_path
: &Path
, mode
: HeaderMode
) -> io
::Result
<()> {
338 let stat
= fs
::metadata(src_path
)?
;
339 append_fs(dst
, path
, &stat
, &mut io
::empty(), mode
, None
)
342 fn prepare_header(dst
: &mut Write
, header
: &mut Header
, path
: &Path
) -> io
::Result
<()> {
343 // Try to encode the path directly in the header, but if it ends up not
344 // working (e.g. it's too long) then use the GNU-specific long name
345 // extension by emitting an entry which indicates that it's the filename
346 if let Err(e
) = header
.set_path(path
) {
347 let data
= path2bytes(&path
)?
;
348 let max
= header
.as_old().name
.len();
349 if data
.len() < max
{
352 let mut header2
= Header
::new_gnu();
353 header2
.as_gnu_mut().unwrap().name
[..13].clone_from_slice(b
"././@LongLink");
354 header2
.set_mode(0o644);
357 header2
.set_mtime(0);
358 header2
.set_size((data
.len() + 1) as u64);
359 header2
.set_entry_type(EntryType
::new(b'L'
));
361 let mut data2
= data
.chain(io
::repeat(0).take(0));
362 append(dst
, &header2
, &mut data2
)?
;
363 // Truncate the path to store in the header we're about to emit to
364 // ensure we've got something at least mentioned.
365 let path
= bytes2path(Cow
::Borrowed(&data
[..max
]))?
;
366 header
.set_path(&path
)?
;
371 fn append_fs(dst
: &mut Write
,
376 link_name
: Option
<&Path
>) -> io
::Result
<()> {
377 let mut header
= Header
::new_gnu();
379 prepare_header(dst
, &mut header
, path
)?
;
380 header
.set_metadata_in_mode(meta
, mode
);
381 if let Some(link_name
) = link_name
{
382 header
.set_link_name(link_name
)?
;
385 append(dst
, &header
, read
)
388 fn append_dir_all(dst
: &mut Write
, path
: &Path
, src_path
: &Path
, mode
: HeaderMode
, follow
: bool
) -> io
::Result
<()> {
389 let mut stack
= vec
![(src_path
.to_path_buf(), true, false)];
390 while let Some((src
, is_dir
, is_symlink
)) = stack
.pop() {
391 let dest
= path
.join(src
.strip_prefix(&src_path
).unwrap());
393 for entry
in fs
::read_dir(&src
)?
{
395 let file_type
= entry
.file_type()?
;
396 stack
.push((entry
.path(), file_type
.is_dir(), file_type
.is_symlink()));
398 if dest
!= Path
::new("") {
399 append_dir(dst
, &dest
, &src
, mode
)?
;
401 } else if !follow
&& is_symlink
{
402 let stat
= fs
::symlink_metadata(&src
)?
;
403 let link_name
= fs
::read_link(&src
)?
;
404 append_fs(dst
, &dest
, &stat
, &mut io
::empty(), mode
, Some(&link_name
))?
;
406 append_file(dst
, &dest
, &mut fs
::File
::open(src
)?
, mode
)?
;
412 impl<W
: Write
> Drop
for Builder
<W
> {
414 let _
= self.finish();