]>
Commit | Line | Data |
---|---|---|
70acf637 WB |
1 | //! The `pxar` encoder state machine. |
2 | //! | |
3 | //! This is the implementation used by both the synchronous and async pxar wrappers. | |
4 | ||
5 | use std::io; | |
6 | use std::mem::{forget, size_of, size_of_val, take}; | |
7 | use std::os::unix::ffi::OsStrExt; | |
8 | use std::path::Path; | |
9 | use std::pin::Pin; | |
10 | use std::task::{Context, Poll}; | |
11 | ||
12 | use endian_trait::Endian; | |
13 | ||
749855a4 | 14 | use crate::binary_tree_array; |
70acf637 WB |
15 | use crate::decoder::SeqRead; |
16 | use crate::format::{self, GoodbyeItem}; | |
17 | use crate::poll_fn::poll_fn; | |
18 | use crate::Metadata; | |
19 | ||
54109840 | 20 | pub mod aio; |
70acf637 WB |
21 | pub mod sync; |
22 | ||
23 | #[doc(inline)] | |
24 | pub use sync::Encoder; | |
25 | ||
26 | /// Sequential write interface used by the encoder's state machine. | |
27 | /// | |
28 | /// This is our internal writer trait which is available for `std::io::Write` types in the | |
29 | /// synchronous wrapper and for both `tokio` and `future` `AsyncWrite` types in the asynchronous | |
30 | /// wrapper. | |
31 | pub trait SeqWrite { | |
32 | fn poll_seq_write( | |
33 | self: Pin<&mut Self>, | |
34 | cx: &mut Context, | |
35 | buf: &[u8], | |
36 | ) -> Poll<io::Result<usize>>; | |
37 | ||
327e33f2 | 38 | fn poll_flush(self: Pin<&mut Self>, cx: &mut Context) -> Poll<io::Result<()>>; |
05356884 | 39 | |
327e33f2 | 40 | fn poll_close(self: Pin<&mut Self>, cx: &mut Context) -> Poll<io::Result<()>>; |
05356884 | 41 | |
70acf637 WB |
42 | /// While writing to a pxar archive we need to remember how much dat we've written to track some |
43 | /// offsets. Particularly items like the goodbye table need to be able to compute offsets to | |
44 | /// further back in the archive. | |
45 | fn poll_position(self: Pin<&mut Self>, cx: &mut Context) -> Poll<io::Result<u64>>; | |
46 | ||
47 | /// To avoid recursively borrowing each time we nest into a subdirectory we add this helper. | |
48 | /// Otherwise starting a subdirectory will get a trait object pointing to `T`, nesting another | |
49 | /// subdirectory in that would have a trait object pointing to the trait object, and so on. | |
50 | fn as_trait_object(&mut self) -> &mut dyn SeqWrite | |
51 | where | |
52 | Self: Sized, | |
53 | { | |
54 | self as &mut dyn SeqWrite | |
55 | } | |
56 | } | |
57 | ||
58 | /// Allow using trait objects for generics taking a `SeqWrite`. | |
59 | impl<'a> SeqWrite for &mut (dyn SeqWrite + 'a) { | |
60 | fn poll_seq_write( | |
61 | self: Pin<&mut Self>, | |
62 | cx: &mut Context, | |
63 | buf: &[u8], | |
64 | ) -> Poll<io::Result<usize>> { | |
65 | unsafe { | |
66 | self.map_unchecked_mut(|this| &mut **this) | |
67 | .poll_seq_write(cx, buf) | |
68 | } | |
69 | } | |
70 | ||
05356884 WB |
71 | fn poll_flush(self: Pin<&mut Self>, cx: &mut Context) -> Poll<io::Result<()>> { |
72 | unsafe { self.map_unchecked_mut(|this| &mut **this).poll_flush(cx) } | |
73 | } | |
74 | ||
75 | fn poll_close(self: Pin<&mut Self>, cx: &mut Context) -> Poll<io::Result<()>> { | |
76 | unsafe { self.map_unchecked_mut(|this| &mut **this).poll_close(cx) } | |
77 | } | |
78 | ||
70acf637 WB |
79 | fn poll_position(self: Pin<&mut Self>, cx: &mut Context) -> Poll<io::Result<u64>> { |
80 | unsafe { self.map_unchecked_mut(|this| &mut **this).poll_position(cx) } | |
81 | } | |
82 | ||
83 | fn as_trait_object(&mut self) -> &mut dyn SeqWrite | |
84 | where | |
85 | Self: Sized, | |
86 | { | |
87 | &mut **self | |
88 | } | |
89 | } | |
90 | ||
70acf637 WB |
91 | impl<'a> dyn SeqWrite + 'a { |
92 | /// awaitable version of `poll_position`. | |
93 | async fn position(&mut self) -> io::Result<u64> { | |
94 | poll_fn(|cx| unsafe { Pin::new_unchecked(&mut *self).poll_position(cx) }).await | |
95 | } | |
96 | ||
97 | /// awaitable verison of `poll_seq_write`. | |
98 | async fn seq_write(&mut self, buf: &[u8]) -> io::Result<usize> { | |
99 | poll_fn(|cx| unsafe { Pin::new_unchecked(&mut *self).poll_seq_write(cx, buf) }).await | |
100 | } | |
101 | ||
102 | /// Write the entire contents of a buffer, handling short writes. | |
103 | async fn seq_write_all(&mut self, mut buf: &[u8]) -> io::Result<()> { | |
104 | while !buf.is_empty() { | |
105 | let got = self.seq_write(buf).await?; | |
106 | buf = &buf[got..]; | |
107 | } | |
108 | Ok(()) | |
109 | } | |
110 | ||
111 | /// Write an endian-swappable struct. | |
112 | async fn seq_write_struct<E: Endian>(&mut self, data: E) -> io::Result<()> { | |
113 | let data = data.to_le(); | |
114 | self.seq_write_all(unsafe { | |
115 | std::slice::from_raw_parts(&data as *const E as *const u8, size_of_val(&data)) | |
116 | }) | |
117 | .await | |
118 | } | |
119 | ||
120 | /// Write a pxar entry. | |
121 | async fn seq_write_pxar_entry(&mut self, htype: u64, data: &[u8]) -> io::Result<()> { | |
122 | self.seq_write_struct(format::Header::with_content_size(htype, data.len() as u64)) | |
123 | .await?; | |
124 | self.seq_write_all(data).await | |
125 | } | |
126 | ||
127 | /// Write a pxar entry terminated by an additional zero which is not contained in the provided | |
128 | /// data buffer. | |
129 | async fn seq_write_pxar_entry_zero(&mut self, htype: u64, data: &[u8]) -> io::Result<()> { | |
130 | self.seq_write_struct(format::Header::with_content_size( | |
131 | htype, | |
132 | 1 + data.len() as u64, | |
133 | )) | |
134 | .await?; | |
135 | self.seq_write_all(data).await?; | |
136 | self.seq_write_all(&[0u8]).await | |
137 | } | |
138 | ||
139 | /// Write a pxar entry consiting of an endian-swappable struct. | |
b5a471a3 WB |
140 | async fn seq_write_pxar_struct_entry<E: Endian>( |
141 | &mut self, | |
142 | htype: u64, | |
143 | data: E, | |
144 | ) -> io::Result<()> { | |
70acf637 | 145 | let data = data.to_le(); |
b5a471a3 | 146 | self.seq_write_pxar_entry(htype, unsafe { |
70acf637 WB |
147 | std::slice::from_raw_parts(&data as *const E as *const u8, size_of_val(&data)) |
148 | }) | |
149 | .await | |
150 | } | |
151 | } | |
152 | ||
59e6f679 WB |
153 | /// Error conditions caused by wrong usage of this crate. |
154 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] | |
155 | pub enum EncodeError { | |
156 | /// The user dropped a `File` without without finishing writing all of its contents. | |
157 | /// | |
158 | /// This is required because the payload lengths is written out at the begining and decoding | |
159 | /// requires there to follow the right amount of data. | |
160 | IncompleteFile, | |
161 | ||
162 | /// The user dropped a directory without finalizing it. | |
163 | /// | |
164 | /// Finalizing is required to build the goodbye table at the end of a directory. | |
165 | IncompleteDirectory, | |
166 | } | |
167 | ||
70acf637 WB |
168 | #[derive(Default)] |
169 | struct EncoderState { | |
170 | /// Goodbye items for this directory, excluding the tail. | |
171 | items: Vec<GoodbyeItem>, | |
172 | ||
59e6f679 WB |
173 | /// User caused error conditions. |
174 | encode_error: Option<EncodeError>, | |
70acf637 WB |
175 | |
176 | /// Offset of this directory's ENTRY. | |
177 | entry_offset: u64, | |
178 | ||
179 | /// Offset to this directory's first FILENAME. | |
180 | files_offset: u64, | |
181 | ||
182 | /// If this is a subdirectory, this points to the this directory's FILENAME. | |
183 | file_offset: Option<u64>, | |
749855a4 WB |
184 | |
185 | /// If this is a subdirectory, this contains this directory's hash for the goodbye item. | |
186 | file_hash: u64, | |
70acf637 WB |
187 | } |
188 | ||
59e6f679 WB |
189 | impl EncoderState { |
190 | fn merge_error(&mut self, error: Option<EncodeError>) { | |
191 | // one error is enough: | |
192 | if self.encode_error.is_none() { | |
193 | self.encode_error = error; | |
194 | } | |
195 | } | |
196 | ||
197 | fn add_error(&mut self, error: EncodeError) { | |
198 | self.merge_error(Some(error)); | |
199 | } | |
200 | } | |
201 | ||
70acf637 WB |
202 | /// The encoder state machine implementation for a directory. |
203 | /// | |
204 | /// We use `async fn` to implement the encoder state machine so that we can easily plug in both | |
205 | /// synchronous or `async` I/O objects in as output. | |
206 | pub(crate) struct EncoderImpl<'a, T: SeqWrite + 'a> { | |
207 | output: T, | |
208 | state: EncoderState, | |
209 | parent: Option<&'a mut EncoderState>, | |
210 | finished: bool, | |
211 | } | |
212 | ||
213 | impl<'a, T: SeqWrite + 'a> Drop for EncoderImpl<'a, T> { | |
214 | fn drop(&mut self) { | |
215 | if let Some(ref mut parent) = self.parent { | |
216 | // propagate errors: | |
59e6f679 WB |
217 | parent.merge_error(self.state.encode_error); |
218 | if !self.finished { | |
219 | parent.add_error(EncodeError::IncompleteDirectory); | |
70acf637 WB |
220 | } |
221 | } else if !self.finished { | |
222 | // FIXME: how do we deal with this? | |
223 | eprintln!("Encoder dropped without finishing!"); | |
224 | } | |
225 | } | |
226 | } | |
227 | ||
228 | impl<'a, T: SeqWrite + 'a> EncoderImpl<'a, T> { | |
229 | pub async fn new(output: T, metadata: &Metadata) -> io::Result<EncoderImpl<'a, T>> { | |
230 | if !metadata.is_dir() { | |
231 | io_bail!("directory metadata must contain the directory mode flag"); | |
232 | } | |
233 | let mut this = Self { | |
234 | output, | |
235 | state: EncoderState::default(), | |
236 | parent: None, | |
237 | finished: false, | |
238 | }; | |
239 | ||
240 | this.encode_metadata(metadata).await?; | |
241 | this.state.files_offset = (&mut this.output as &mut dyn SeqWrite).position().await?; | |
242 | ||
243 | Ok(this) | |
244 | } | |
245 | ||
246 | fn check(&self) -> io::Result<()> { | |
59e6f679 WB |
247 | match self.state.encode_error { |
248 | Some(EncodeError::IncompleteFile) => io_bail!("incomplete file"), | |
249 | Some(EncodeError::IncompleteDirectory) => io_bail!("directory not finalized"), | |
250 | None => Ok(()) | |
70acf637 WB |
251 | } |
252 | } | |
253 | ||
254 | pub async fn create_file<'b>( | |
255 | &'b mut self, | |
256 | metadata: &Metadata, | |
257 | file_name: &Path, | |
258 | file_size: u64, | |
259 | ) -> io::Result<FileImpl<'b>> | |
260 | where | |
261 | 'a: 'b, | |
262 | { | |
263 | self.create_file_do(metadata, file_name.as_os_str().as_bytes(), file_size) | |
264 | .await | |
265 | } | |
266 | ||
267 | async fn create_file_do<'b>( | |
268 | &'b mut self, | |
269 | metadata: &Metadata, | |
270 | file_name: &[u8], | |
271 | file_size: u64, | |
272 | ) -> io::Result<FileImpl<'b>> | |
273 | where | |
274 | 'a: 'b, | |
275 | { | |
276 | self.check()?; | |
277 | ||
278 | let file_offset = (&mut self.output as &mut dyn SeqWrite).position().await?; | |
279 | self.start_file_do(metadata, file_name).await?; | |
280 | ||
281 | (&mut self.output as &mut dyn SeqWrite) | |
282 | .seq_write_struct(format::Header::with_content_size( | |
283 | format::PXAR_PAYLOAD, | |
284 | file_size, | |
285 | )) | |
286 | .await?; | |
287 | ||
288 | let payload_data_offset = (&mut self.output as &mut dyn SeqWrite).position().await?; | |
289 | ||
290 | let meta_size = payload_data_offset - file_offset; | |
291 | ||
292 | Ok(FileImpl { | |
293 | output: &mut self.output, | |
294 | goodbye_item: GoodbyeItem { | |
295 | hash: format::hash_filename(file_name), | |
296 | offset: file_offset, | |
297 | size: file_size + meta_size, | |
298 | }, | |
299 | remaining_size: file_size, | |
300 | parent: &mut self.state, | |
301 | }) | |
302 | } | |
303 | ||
304 | pub async fn add_file( | |
305 | &mut self, | |
306 | metadata: &Metadata, | |
307 | file_name: &Path, | |
308 | file_size: u64, | |
309 | content: &mut dyn SeqRead, | |
310 | ) -> io::Result<()> { | |
311 | let mut file = self.create_file(metadata, file_name, file_size).await?; | |
312 | let mut buf = crate::util::vec_new(4096); | |
313 | loop { | |
314 | let got = content.seq_read(&mut buf).await?; | |
315 | if got == 0 { | |
316 | break; | |
317 | } else { | |
318 | file.write_all(&buf[..got]).await?; | |
319 | } | |
320 | } | |
321 | Ok(()) | |
322 | } | |
323 | ||
2fb73e7b WB |
324 | pub async fn add_symlink( |
325 | &mut self, | |
326 | metadata: &Metadata, | |
327 | file_name: &Path, | |
328 | target: &Path, | |
0abc4121 WB |
329 | ) -> io::Result<()> { |
330 | self.add_link(metadata, file_name, target, format::PXAR_SYMLINK) | |
331 | .await | |
332 | } | |
333 | ||
334 | pub async fn add_hardlink( | |
335 | &mut self, | |
336 | metadata: &Metadata, | |
337 | file_name: &Path, | |
338 | target: &Path, | |
339 | ) -> io::Result<()> { | |
340 | self.add_link(metadata, file_name, target, format::PXAR_HARDLINK) | |
341 | .await | |
342 | } | |
343 | ||
344 | async fn add_link( | |
345 | &mut self, | |
346 | metadata: &Metadata, | |
347 | file_name: &Path, | |
348 | target: &Path, | |
349 | htype: u64, | |
3fe26522 WB |
350 | ) -> io::Result<()> { |
351 | self.add_file_entry(metadata, file_name, htype, target.as_os_str().as_bytes()) | |
352 | .await | |
353 | } | |
354 | ||
355 | pub async fn add_device( | |
356 | &mut self, | |
357 | metadata: &Metadata, | |
358 | file_name: &Path, | |
359 | device: format::Device, | |
360 | ) -> io::Result<()> { | |
361 | let device = device.to_le(); | |
362 | let device = unsafe { | |
363 | std::slice::from_raw_parts( | |
364 | &device as *const format::Device as *const u8, | |
365 | size_of::<format::Device>(), | |
366 | ) | |
367 | }; | |
368 | self.add_file_entry(metadata, file_name, format::PXAR_DEVICE, device) | |
369 | .await | |
370 | } | |
371 | ||
372 | async fn add_file_entry( | |
373 | &mut self, | |
374 | metadata: &Metadata, | |
375 | file_name: &Path, | |
376 | htype: u64, | |
377 | entry_data: &[u8], | |
2fb73e7b | 378 | ) -> io::Result<()> { |
05356884 | 379 | self.check()?; |
2fb73e7b WB |
380 | |
381 | let file_offset = (&mut self.output as &mut dyn SeqWrite).position().await?; | |
382 | ||
383 | let file_name = file_name.as_os_str().as_bytes(); | |
2fb73e7b WB |
384 | |
385 | self.start_file_do(metadata, file_name).await?; | |
386 | (&mut self.output as &mut dyn SeqWrite) | |
3fe26522 | 387 | .seq_write_pxar_entry_zero(htype, entry_data) |
2fb73e7b WB |
388 | .await?; |
389 | ||
390 | let end_offset = (&mut self.output as &mut dyn SeqWrite).position().await?; | |
391 | ||
392 | self.state.items.push(GoodbyeItem { | |
393 | hash: format::hash_filename(file_name), | |
394 | offset: file_offset, | |
395 | size: end_offset - file_offset, | |
396 | }); | |
397 | ||
398 | Ok(()) | |
399 | } | |
400 | ||
70acf637 WB |
401 | /// Helper |
402 | #[inline] | |
403 | async fn position(&mut self) -> io::Result<u64> { | |
404 | (&mut self.output as &mut dyn SeqWrite).position().await | |
405 | } | |
406 | ||
407 | pub async fn create_directory<'b>( | |
408 | &'b mut self, | |
409 | file_name: &Path, | |
410 | metadata: &Metadata, | |
411 | ) -> io::Result<EncoderImpl<'b, &'b mut dyn SeqWrite>> | |
412 | where | |
413 | 'a: 'b, | |
414 | { | |
415 | self.check()?; | |
416 | ||
417 | if !metadata.is_dir() { | |
418 | io_bail!("directory metadata must contain the directory mode flag"); | |
419 | } | |
420 | ||
421 | let file_name = file_name.as_os_str().as_bytes(); | |
749855a4 | 422 | let file_hash = format::hash_filename(file_name); |
70acf637 WB |
423 | |
424 | let file_offset = self.position().await?; | |
425 | self.encode_filename(file_name).await?; | |
426 | ||
427 | let entry_offset = self.position().await?; | |
428 | self.encode_metadata(&metadata).await?; | |
429 | ||
430 | let files_offset = self.position().await?; | |
431 | ||
432 | Ok(EncoderImpl { | |
433 | output: self.output.as_trait_object(), | |
434 | state: EncoderState { | |
435 | entry_offset, | |
436 | files_offset, | |
437 | file_offset: Some(file_offset), | |
749855a4 | 438 | file_hash: file_hash, |
70acf637 WB |
439 | ..Default::default() |
440 | }, | |
441 | parent: Some(&mut self.state), | |
442 | finished: false, | |
443 | }) | |
444 | } | |
445 | ||
446 | async fn start_file_do(&mut self, metadata: &Metadata, file_name: &[u8]) -> io::Result<()> { | |
447 | self.encode_filename(file_name).await?; | |
448 | self.encode_metadata(&metadata).await?; | |
449 | Ok(()) | |
450 | } | |
451 | ||
452 | async fn encode_metadata(&mut self, metadata: &Metadata) -> io::Result<()> { | |
453 | (&mut self.output as &mut dyn SeqWrite) | |
b9122056 | 454 | .seq_write_pxar_struct_entry(format::PXAR_ENTRY, metadata.stat.clone()) |
70acf637 | 455 | .await?; |
b5a471a3 WB |
456 | |
457 | for xattr in &metadata.xattrs { | |
458 | self.write_xattr(xattr).await?; | |
459 | } | |
460 | ||
461 | self.write_acls(&metadata.acl).await?; | |
462 | ||
463 | if let Some(fcaps) = &metadata.fcaps { | |
464 | self.write_file_capabilities(fcaps).await?; | |
465 | } | |
466 | ||
467 | if let Some(qpid) = &metadata.quota_project_id { | |
468 | self.write_quota_project_id(qpid).await?; | |
469 | } | |
470 | ||
471 | Ok(()) | |
472 | } | |
473 | ||
474 | async fn write_xattr(&mut self, xattr: &format::XAttr) -> io::Result<()> { | |
475 | (&mut self.output as &mut dyn SeqWrite) | |
476 | .seq_write_pxar_entry(format::PXAR_XATTR, &xattr.data) | |
477 | .await | |
478 | } | |
479 | ||
480 | async fn write_acls(&mut self, acl: &crate::Acl) -> io::Result<()> { | |
481 | for acl in &acl.users { | |
482 | (&mut self.output as &mut dyn SeqWrite) | |
483 | .seq_write_pxar_struct_entry(format::PXAR_ACL_USER, acl.clone()) | |
484 | .await?; | |
485 | } | |
486 | ||
487 | for acl in &acl.groups { | |
488 | (&mut self.output as &mut dyn SeqWrite) | |
489 | .seq_write_pxar_struct_entry(format::PXAR_ACL_GROUP, acl.clone()) | |
490 | .await?; | |
491 | } | |
492 | ||
493 | if let Some(acl) = &acl.group_obj { | |
494 | (&mut self.output as &mut dyn SeqWrite) | |
495 | .seq_write_pxar_struct_entry(format::PXAR_ACL_GROUP_OBJ, acl.clone()) | |
496 | .await?; | |
497 | } | |
498 | ||
499 | if let Some(acl) = &acl.default { | |
500 | (&mut self.output as &mut dyn SeqWrite) | |
501 | .seq_write_pxar_struct_entry(format::PXAR_ACL_DEFAULT, acl.clone()) | |
502 | .await?; | |
503 | } | |
504 | ||
505 | for acl in &acl.default_users { | |
506 | (&mut self.output as &mut dyn SeqWrite) | |
507 | .seq_write_pxar_struct_entry(format::PXAR_ACL_DEFAULT_USER, acl.clone()) | |
508 | .await?; | |
509 | } | |
510 | ||
511 | for acl in &acl.default_groups { | |
512 | (&mut self.output as &mut dyn SeqWrite) | |
513 | .seq_write_pxar_struct_entry(format::PXAR_ACL_DEFAULT_GROUP, acl.clone()) | |
514 | .await?; | |
515 | } | |
516 | ||
70acf637 WB |
517 | Ok(()) |
518 | } | |
519 | ||
b5a471a3 WB |
520 | async fn write_file_capabilities(&mut self, fcaps: &format::FCaps) -> io::Result<()> { |
521 | (&mut self.output as &mut dyn SeqWrite) | |
522 | .seq_write_pxar_entry(format::PXAR_FCAPS, &fcaps.data) | |
523 | .await | |
524 | } | |
525 | ||
526 | async fn write_quota_project_id( | |
527 | &mut self, | |
528 | quota_project_id: &format::QuotaProjectId, | |
529 | ) -> io::Result<()> { | |
530 | (&mut self.output as &mut dyn SeqWrite) | |
b9122056 | 531 | .seq_write_pxar_struct_entry(format::PXAR_QUOTA_PROJID, quota_project_id.clone()) |
b5a471a3 WB |
532 | .await |
533 | } | |
534 | ||
70acf637 WB |
535 | async fn encode_filename(&mut self, file_name: &[u8]) -> io::Result<()> { |
536 | (&mut self.output as &mut dyn SeqWrite) | |
537 | .seq_write_pxar_entry_zero(format::PXAR_FILENAME, file_name) | |
538 | .await | |
539 | } | |
540 | ||
541 | pub async fn finish(mut self) -> io::Result<()> { | |
542 | let tail_bytes = self.finish_goodbye_table().await?; | |
543 | (&mut self.output as &mut dyn SeqWrite) | |
544 | .seq_write_pxar_entry(format::PXAR_GOODBYE, &tail_bytes) | |
545 | .await?; | |
749855a4 WB |
546 | if let Some(parent) = &mut self.parent { |
547 | let file_offset = self | |
548 | .state | |
549 | .file_offset | |
550 | .expect("internal error: parent set but no file_offset?"); | |
551 | ||
552 | let end_offset = (&mut self.output as &mut dyn SeqWrite).position().await?; | |
553 | ||
554 | parent.items.push(GoodbyeItem { | |
555 | hash: self.state.file_hash, | |
556 | offset: file_offset, | |
557 | size: end_offset - file_offset, | |
558 | }); | |
559 | } | |
70acf637 WB |
560 | self.finished = true; |
561 | Ok(()) | |
562 | } | |
563 | ||
564 | async fn finish_goodbye_table(&mut self) -> io::Result<Vec<u8>> { | |
565 | let goodbye_offset = (&mut self.output as &mut dyn SeqWrite).position().await?; | |
566 | ||
567 | // "take" out the tail (to not leave an array of endian-swapped structs in `self`) | |
568 | let mut tail = take(&mut self.state.items); | |
569 | let tail_size = (tail.len() + 1) * size_of::<GoodbyeItem>(); | |
570 | let goodbye_size = tail_size as u64 + size_of::<format::Header>() as u64; | |
571 | ||
749855a4 WB |
572 | // sort, then create a BST |
573 | tail.sort_unstable_by(|a, b| a.hash.cmp(&b.hash)); | |
70acf637 | 574 | |
749855a4 WB |
575 | let mut bst = Vec::with_capacity(tail.len() + 1); |
576 | unsafe { | |
577 | bst.set_len(tail.len()); | |
70acf637 | 578 | } |
749855a4 WB |
579 | binary_tree_array::copy(tail.len(), |src, dest| { |
580 | let mut item = tail[src].clone(); | |
581 | // fixup the goodbye table offsets to be relative and with the right endianess | |
582 | item.offset = goodbye_offset - item.offset; | |
583 | unsafe { | |
584 | std::ptr::write(&mut bst[dest], item.to_le()); | |
585 | } | |
586 | }); | |
587 | drop(tail); | |
588 | ||
589 | bst.push( | |
590 | GoodbyeItem { | |
591 | hash: format::PXAR_GOODBYE_TAIL_MARKER, | |
592 | offset: goodbye_offset - self.state.entry_offset, | |
593 | size: goodbye_size, | |
594 | } | |
595 | .to_le(), | |
596 | ); | |
70acf637 WB |
597 | |
598 | // turn this into a byte vector since after endian-swapping we can no longer guarantee that | |
599 | // the items make sense: | |
749855a4 WB |
600 | let data = bst.as_mut_ptr() as *mut u8; |
601 | let capacity = bst.capacity() * size_of::<GoodbyeItem>(); | |
602 | forget(bst); | |
70acf637 WB |
603 | Ok(unsafe { Vec::from_raw_parts(data, tail_size, capacity) }) |
604 | } | |
605 | } | |
606 | ||
607 | /// Writer for a file object in a directory. | |
608 | pub struct FileImpl<'a> { | |
609 | output: &'a mut dyn SeqWrite, | |
610 | ||
611 | /// This file's `GoodbyeItem`. FIXME: We currently don't touch this, can we just push it | |
612 | /// directly instead of on Drop of FileImpl? | |
613 | goodbye_item: GoodbyeItem, | |
614 | ||
615 | /// While writing data to this file, this is how much space we still have left, this must reach | |
616 | /// exactly zero. | |
617 | remaining_size: u64, | |
618 | ||
59e6f679 | 619 | /// The directory containing this file. This is where we propagate the `IncompleteFile` error |
70acf637 WB |
620 | /// to, and where we insert our `GoodbyeItem`. |
621 | parent: &'a mut EncoderState, | |
622 | } | |
623 | ||
624 | impl<'a> Drop for FileImpl<'a> { | |
625 | fn drop(&mut self) { | |
626 | if self.remaining_size != 0 { | |
59e6f679 | 627 | self.parent.add_error(EncodeError::IncompleteFile); |
70acf637 WB |
628 | } |
629 | ||
630 | self.parent.items.push(self.goodbye_item.clone()); | |
631 | } | |
632 | } | |
633 | ||
634 | impl<'a> FileImpl<'a> { | |
635 | fn check_remaining(&self, size: usize) -> io::Result<()> { | |
636 | if size as u64 > self.remaining_size { | |
637 | io_bail!("attempted to write more than previously allocated"); | |
638 | } else { | |
639 | Ok(()) | |
640 | } | |
641 | } | |
642 | ||
05356884 WB |
643 | /// Poll write interface to more easily connect to tokio/futures. |
644 | #[cfg(any(feature = "tokio-io", feature = "futures-io"))] | |
645 | pub fn poll_write( | |
646 | self: Pin<&mut Self>, | |
647 | cx: &mut Context, | |
648 | data: &[u8], | |
649 | ) -> Poll<io::Result<usize>> { | |
650 | let this = self.get_mut(); | |
651 | this.check_remaining(data.len())?; | |
652 | let output = unsafe { Pin::new_unchecked(&mut *this.output) }; | |
653 | match output.poll_seq_write(cx, data) { | |
654 | Poll::Ready(Ok(put)) => { | |
655 | this.remaining_size -= put as u64; | |
656 | Poll::Ready(Ok(put)) | |
657 | } | |
658 | other => other, | |
659 | } | |
660 | } | |
661 | ||
662 | /// Poll flush interface to more easily connect to tokio/futures. | |
663 | #[cfg(any(feature = "tokio-io", feature = "futures-io"))] | |
664 | pub fn poll_flush(self: Pin<&mut Self>, cx: &mut Context) -> Poll<io::Result<()>> { | |
665 | unsafe { | |
327e33f2 WB |
666 | self.map_unchecked_mut(|this| &mut this.output) |
667 | .poll_flush(cx) | |
05356884 WB |
668 | } |
669 | } | |
670 | ||
671 | /// Poll close/shutdown interface to more easily connect to tokio/futures. | |
672 | #[cfg(any(feature = "tokio-io", feature = "futures-io"))] | |
673 | pub fn poll_close(self: Pin<&mut Self>, cx: &mut Context) -> Poll<io::Result<()>> { | |
674 | unsafe { | |
327e33f2 WB |
675 | self.map_unchecked_mut(|this| &mut this.output) |
676 | .poll_close(cx) | |
05356884 WB |
677 | } |
678 | } | |
679 | ||
70acf637 WB |
680 | /// Write file data for the current file entry in a pxar archive. |
681 | /// | |
682 | /// This forwards to the output's `SeqWrite::poll_seq_write` and may write fewer bytes than | |
683 | /// requested. Check the return value for how many. There's also a `write_all` method available | |
684 | /// for convenience. | |
685 | pub async fn write(&mut self, data: &[u8]) -> io::Result<usize> { | |
686 | self.check_remaining(data.len())?; | |
687 | let put = self.output.seq_write(data).await?; | |
688 | self.remaining_size -= put as u64; | |
689 | Ok(put) | |
690 | } | |
691 | ||
692 | /// Completely write file data for the current file entry in a pxar archive. | |
693 | pub async fn write_all(&mut self, data: &[u8]) -> io::Result<()> { | |
694 | self.check_remaining(data.len())?; | |
695 | self.output.seq_write_all(data).await?; | |
696 | self.remaining_size -= data.len() as u64; | |
697 | Ok(()) | |
698 | } | |
699 | } | |
05356884 WB |
700 | |
701 | #[cfg(feature = "tokio-io")] | |
702 | impl<'a> tokio::io::AsyncWrite for FileImpl<'a> { | |
327e33f2 | 703 | fn poll_write(self: Pin<&mut Self>, cx: &mut Context, buf: &[u8]) -> Poll<io::Result<usize>> { |
05356884 WB |
704 | FileImpl::poll_write(self, cx, buf) |
705 | } | |
706 | ||
707 | fn poll_flush(self: Pin<&mut Self>, cx: &mut Context) -> Poll<io::Result<()>> { | |
708 | FileImpl::poll_flush(self, cx) | |
709 | } | |
710 | ||
711 | fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context) -> Poll<io::Result<()>> { | |
712 | FileImpl::poll_close(self, cx) | |
713 | } | |
714 | } | |
715 | ||
716 | #[cfg(feature = "futures-io")] | |
717 | impl<'a> futures::io::AsyncWrite for FileImpl<'a> { | |
327e33f2 | 718 | fn poll_write(self: Pin<&mut Self>, cx: &mut Context, buf: &[u8]) -> Poll<io::Result<usize>> { |
05356884 WB |
719 | FileImpl::poll_write(self, cx, buf) |
720 | } | |
721 | ||
722 | fn poll_flush(self: Pin<&mut Self>, cx: &mut Context) -> Poll<io::Result<()>> { | |
723 | FileImpl::poll_flush(self, cx) | |
724 | } | |
725 | ||
726 | fn poll_close(self: Pin<&mut Self>, cx: &mut Context) -> Poll<io::Result<()>> { | |
727 | FileImpl::poll_close(self, cx) | |
728 | } | |
729 | } |