]> git.proxmox.com Git - rustc.git/blob - src/vendor/tar/src/builder.rs
New upstream version 1.31.0+dfsg1
[rustc.git] / src / vendor / tar / src / builder.rs
1 use std::io;
2 use std::path::Path;
3 use std::io::prelude::*;
4 use std::fs;
5 use std::borrow::Cow;
6
7 use {EntryType, Header, other};
8 use header::{bytes2path, HeaderMode, path2bytes};
9
10 /// A structure for building archives
11 ///
12 /// This structure has methods for building up an archive from scratch into any
13 /// arbitrary writer.
14 pub struct Builder<W: Write> {
15 mode: HeaderMode,
16 follow: bool,
17 finished: bool,
18 obj: Option<W>,
19 }
20
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> {
26 Builder {
27 mode: HeaderMode::Complete,
28 follow: true,
29 finished: false,
30 obj: Some(obj),
31 }
32 }
33
34 fn inner(&mut self) -> &mut W {
35 self.obj.as_mut().unwrap()
36 }
37
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) {
42 self.mode = mode;
43 }
44
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) {
48 self.follow = follow;
49 }
50
51 /// Unwrap this archive, returning the underlying object.
52 ///
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
55 /// that operation.
56 pub fn into_inner(mut self) -> io::Result<W> {
57 if !self.finished {
58 self.finish()?;
59 }
60 Ok(self.obj.take().unwrap())
61 }
62
63 /// Adds a new entry to this archive.
64 ///
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.
70 ///
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.
74 ///
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.
77 ///
78 /// # Errors
79 ///
80 /// This function will return an error for any intermittent I/O error which
81 /// occurs when either reading or writing.
82 ///
83 /// # Examples
84 ///
85 /// ```
86 /// use tar::{Builder, Header};
87 ///
88 /// let mut header = Header::new_gnu();
89 /// header.set_path("foo").unwrap();
90 /// header.set_size(4);
91 /// header.set_cksum();
92 ///
93 /// let mut data: &[u8] = &[1, 2, 3, 4];
94 ///
95 /// let mut ar = Builder::new(Vec::new());
96 /// ar.append(&header, data).unwrap();
97 /// let data = ar.into_inner().unwrap();
98 /// ```
99 pub fn append<R: Read>(&mut self, header: &Header, mut data: R)
100 -> io::Result<()> {
101 append(self.inner(), header, &mut data)
102 }
103
104 /// Adds a new entry to this archive with the specified path.
105 ///
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.
111 ///
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
115 /// written.
116 ///
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.
120 ///
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.
123 ///
124 /// # Errors
125 ///
126 /// This function will return an error for any intermittent I/O error which
127 /// occurs when either reading or writing.
128 ///
129 /// # Examples
130 ///
131 /// ```
132 /// use tar::{Builder, Header};
133 ///
134 /// let mut header = Header::new_gnu();
135 /// header.set_size(4);
136 /// header.set_cksum();
137 ///
138 /// let mut data: &[u8] = &[1, 2, 3, 4];
139 ///
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();
143 /// ```
144 pub fn append_data<P: AsRef<Path>, R: Read>(&mut self, header: &mut Header, path: P, data: R)
145 -> io::Result<()> {
146 prepare_header(self.inner(), header, path.as_ref())?;
147 header.set_cksum();
148 self.append(&header, data)
149 }
150
151 /// Adds a file on the local filesystem to this archive.
152 ///
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.
158 ///
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.
162 ///
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.
165 ///
166 /// # Examples
167 ///
168 /// ```no_run
169 /// use tar::Builder;
170 ///
171 /// let mut ar = Builder::new(Vec::new());
172 ///
173 /// ar.append_path("foo/bar.txt").unwrap();
174 /// ```
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)
179 }
180
181 /// Adds a file to this archive with the given path as the name of the file
182 /// in the archive.
183 ///
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`.
186 ///
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.
190 ///
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.
193 ///
194 /// # Examples
195 ///
196 /// ```no_run
197 /// use std::fs::File;
198 /// use tar::Builder;
199 ///
200 /// let mut ar = Builder::new(Vec::new());
201 ///
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();
206 /// ```
207 pub fn append_file<P: AsRef<Path>>(&mut self, path: P, file: &mut fs::File)
208 -> io::Result<()> {
209 let mode = self.mode.clone();
210 append_file(self.inner(), path.as_ref(), file, mode)
211 }
212
213 /// Adds a directory to this archive with the given path as the name of the
214 /// directory in the archive.
215 ///
216 /// This will use `stat` to populate a `Header`, and it will then append the
217 /// directory to the archive with the name `path`.
218 ///
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.
222 ///
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.
225 ///
226 /// # Examples
227 ///
228 /// ```
229 /// use std::fs;
230 /// use tar::Builder;
231 ///
232 /// let mut ar = Builder::new(Vec::new());
233 ///
234 /// // Use the directory at one location, but insert it into the archive
235 /// // with a different name.
236 /// ar.append_dir("bardir", ".").unwrap();
237 /// ```
238 pub fn append_dir<P, Q>(&mut self, path: P, src_path: Q) -> io::Result<()>
239 where P: AsRef<Path>, Q: AsRef<Path>
240 {
241 let mode = self.mode.clone();
242 append_dir(self.inner(), path.as_ref(), src_path.as_ref(), mode)
243 }
244
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.
247 ///
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.
251 ///
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.
254 ///
255 /// # Examples
256 ///
257 /// ```
258 /// use std::fs;
259 /// use tar::Builder;
260 ///
261 /// let mut ar = Builder::new(Vec::new());
262 ///
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();
266 /// ```
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>
269 {
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)
273 }
274
275 /// Finish writing this archive, emitting the termination sections.
276 ///
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
279 /// to be acquired.
280 ///
281 /// In most situations the `into_inner` method should be preferred.
282 pub fn finish(&mut self) -> io::Result<()> {
283 if self.finished {
284 return Ok(())
285 }
286 self.finished = true;
287 self.inner().write_all(&[0; 1024])
288 }
289 }
290
291 fn append(mut dst: &mut Write,
292 header: &Header,
293 mut data: &mut Read) -> io::Result<()> {
294 dst.write_all(header.as_bytes())?;
295 let len = io::copy(&mut data, &mut dst)?;
296
297 // Pad with zeros if necessary.
298 let buf = [0; 512];
299 let remaining = 512 - (len % 512);
300 if remaining < 512 {
301 dst.write_all(&buf[..remaining as usize])?;
302 }
303
304 Ok(())
305 }
306
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(
310 err.kind(),
311 format!("{} when getting metadata for {}", err, path.display()),
312 ))?
313 } else {
314 fs::symlink_metadata(path).map_err(|err| io::Error::new(
315 err.kind(),
316 format!("{} when getting metadata for {}", err, path.display()),
317 ))?
318 };
319 if stat.is_file() {
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))
326 } else {
327 Err(other(&format!("{} has unknown file type", path.display())))
328 }
329 }
330
331 fn append_file(dst: &mut Write, path: &Path, file: &mut fs::File, mode: HeaderMode)
332 -> io::Result<()> {
333 let stat = file.metadata()?;
334 append_fs(dst, path, &stat, file, mode, None)
335 }
336
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)
340 }
341
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 {
350 return Err(e)
351 }
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);
355 header2.set_uid(0);
356 header2.set_gid(0);
357 header2.set_mtime(0);
358 header2.set_size((data.len() + 1) as u64);
359 header2.set_entry_type(EntryType::new(b'L'));
360 header2.set_cksum();
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)?;
367 }
368 Ok(())
369 }
370
371 fn append_fs(dst: &mut Write,
372 path: &Path,
373 meta: &fs::Metadata,
374 read: &mut Read,
375 mode: HeaderMode,
376 link_name: Option<&Path>) -> io::Result<()> {
377 let mut header = Header::new_gnu();
378
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)?;
383 }
384 header.set_cksum();
385 append(dst, &header, read)
386 }
387
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());
392 if is_dir {
393 for entry in fs::read_dir(&src)? {
394 let entry = entry?;
395 let file_type = entry.file_type()?;
396 stack.push((entry.path(), file_type.is_dir(), file_type.is_symlink()));
397 }
398 if dest != Path::new("") {
399 append_dir(dst, &dest, &src, mode)?;
400 }
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))?;
405 } else {
406 append_file(dst, &dest, &mut fs::File::open(src)?, mode)?;
407 }
408 }
409 Ok(())
410 }
411
412 impl<W: Write> Drop for Builder<W> {
413 fn drop(&mut self) {
414 let _ = self.finish();
415 }
416 }