1use std::collections::HashSet;
24use std::ffi::{CStr, OsStr};
25use std::fs::{self, File};
26use std::io::{self, Read, Write};
27use std::os::fd::{AsRawFd, OwnedFd, RawFd};
28use std::os::unix::ffi::OsStrExt;
29use std::os::unix::fs::MetadataExt;
30use std::path::{Component, Path, PathBuf};
31use std::sync::{mpsc, Arc, Condvar, Mutex};
32use std::thread;
33
34use crate::beneath::{
35 cstr, cvt, escape, fstat, fstatat, is, is_escape, list, openat, remove_at, Root,
36};
37use crate::decode::{open_layer, Decoder};
38use crate::error::Context;
39use crate::{Error, Result};
40
41pub const SMALL_FILE: u64 = 1 << 20;
44pub const IN_FLIGHT: usize = 16 << 20;
46pub const OPEN_FILES: usize = 256;
48const CHUNK: usize = 1 << 20;
50
51#[derive(Debug, Clone)]
53pub struct UnpackOptions {
54 pub threads: usize,
57 pub parallel_gzip: bool,
60 pub same_owner: bool,
62 pub layout: Layout,
65 pub devices: Devices,
68}
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
72pub enum Devices {
73 #[default]
77 Skip,
78 Make,
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
85pub enum Layout {
86 #[default]
88 Flatten,
89 Overlay(OverlayXattr),
93}
94
95#[derive(Debug, Clone, Copy, PartialEq, Eq)]
97pub enum OverlayXattr {
98 Trusted,
100 User,
102}
103
104impl Default for UnpackOptions {
105 fn default() -> Self {
106 Self {
107 threads: 0,
108 parallel_gzip: true,
109 same_owner: fs::metadata("/proc/self").is_ok_and(|m| m.uid() == 0),
110 layout: Layout::Flatten,
111 devices: Devices::Skip,
112 }
113 }
114}
115
116impl UnpackOptions {
117 pub fn threads(&self) -> usize {
119 if self.threads > 0 {
120 self.threads
121 } else {
122 thread::available_parallelism().map_or(1, |n| n.get())
123 }
124 }
125}
126
127#[derive(Debug, Clone, PartialEq, Eq)]
129pub struct UnpackStats {
130 pub decoder: Decoder,
131 pub entries: u64,
133 pub files: u64,
135 pub bytes: u64,
137 pub whiteouts: u64,
139 pub skipped_devices: u64,
141}
142
143pub fn apply_layer_file(blob: &Path, rootfs: &Path, opts: &UnpackOptions) -> Result<UnpackStats> {
145 let (reader, decoder, _) = open_layer(blob, opts, false)?;
146 let mut stats = apply_layer(reader, rootfs, opts)?;
147 stats.decoder = decoder;
148 Ok(stats)
149}
150
151pub fn apply_layer_file_digest(
155 blob: &Path,
156 rootfs: &Path,
157 opts: &UnpackOptions,
158) -> Result<(UnpackStats, crate::Digest)> {
159 let (reader, decoder, digest) = open_layer(blob, opts, true)?;
160 let mut stats = apply_layer(reader, rootfs, opts)?;
161 stats.decoder = decoder;
162 let digest = digest
163 .finish()
164 .ok_or_else(|| Error::Invalid(format!("hashing layer {} failed", blob.display())))?;
165 Ok((stats, digest))
166}
167
168pub fn apply_layer<R: Read + Send>(
174 tar: R,
175 rootfs: &Path,
176 opts: &UnpackOptions,
177) -> Result<UnpackStats> {
178 let threads = opts.threads();
179 let root = Root::open(rootfs).ctx(|| format!("rootfs {}", rootfs.display()))?;
180 let mut w = Writer {
181 root: &root,
182 same_owner: opts.same_owner,
183 layout: opts.layout,
184 devices: opts.devices,
185 ours: HashSet::new(),
186 dirs: Vec::new(),
187 stats: UnpackStats {
188 decoder: Decoder::Plain,
189 entries: 0,
190 files: 0,
191 bytes: 0,
192 whiteouts: 0,
193 skipped_devices: 0,
194 },
195 };
196
197 thread::scope(|s| -> Result<()> {
198 let reader: Box<dyn Read> = if threads > 1 {
199 Box::new(read_ahead(s, tar))
200 } else {
201 Box::new(tar)
202 };
203 let mut pool = Pool::new(s, threads.saturating_sub(1), opts.same_owner);
204 let mut archive = tar::Archive::new(reader);
205 let r = (|| -> Result<()> {
206 for entry in archive.entries().ctx(|| "reading layer".into())? {
207 let mut entry = entry.ctx(|| "reading layer".into())?;
208 w.entry(&mut entry, &mut pool)?;
209 }
210 Ok(())
211 })();
212 let done = pool.finish();
214 r?;
215 done?;
216 let mut rest = archive.into_inner();
218 io::copy(&mut rest, &mut io::sink()).ctx(|| "reading the layer to its end".into())?;
219 Ok(())
220 })?;
221
222 for (rel, mtime) in w.dirs.iter().rev() {
224 if let Ok(Some(d)) = w.open_dir(rel, false) {
225 let t = times(*mtime);
226 unsafe { libc::futimens(d.as_raw_fd(), t.as_ptr()) };
227 }
228 }
229 Ok(w.stats)
230}
231
232struct FileMeta {
234 mode: u32,
235 uid: u64,
236 gid: u64,
237 mtime: u64,
238}
239
240fn times(mtime: u64) -> [libc::timespec; 2] {
241 let t = libc::timespec {
242 tv_sec: mtime as libc::time_t,
243 tv_nsec: 0,
244 };
245 [t, t]
246}
247
248fn finish_file(f: &File, meta: &FileMeta, same_owner: bool) -> io::Result<()> {
250 let fd = f.as_raw_fd();
251 if same_owner {
252 cvt(unsafe { libc::fchown(fd, meta.uid as u32, meta.gid as u32) })?;
253 }
254 cvt(unsafe { libc::fchmod(fd, meta.mode) })?;
255 let t = times(meta.mtime);
256 cvt(unsafe { libc::futimens(fd, t.as_ptr()) })?;
257 Ok(())
258}
259
260fn err(e: io::Error, rel: &Path) -> Error {
263 if is_escape(&e) {
264 Error::Escape(rel.to_path_buf())
265 } else {
266 Error::Io {
267 context: format!("unpacking {}", rel.display()),
268 source: e,
269 }
270 }
271}
272
273fn split(rel: &Path) -> (&Path, &OsStr) {
274 (
275 rel.parent().unwrap_or(Path::new("")),
276 rel.file_name().unwrap_or_default(),
277 )
278}
279
280struct Writer<'r> {
281 root: &'r Root,
282 same_owner: bool,
283 layout: Layout,
284 devices: Devices,
285 ours: HashSet<PathBuf>,
288 dirs: Vec<(PathBuf, u64)>,
289 stats: UnpackStats,
290}
291
292impl Writer<'_> {
293 fn parent(&self, rel: &Path, create: bool) -> Result<Option<OwnedFd>> {
296 match self.root.dir(split(rel).0, create) {
297 Ok(fd) => Ok(Some(fd)),
298 Err(e) if !create && matches!(e.raw_os_error(), Some(libc::ENOENT | libc::ENOTDIR)) => {
299 Ok(None)
300 }
301 Err(e) => Err(err(e, rel)),
302 }
303 }
304
305 fn open_dir(&self, rel: &Path, create: bool) -> Result<Option<OwnedFd>> {
308 let p = match self.root.dir(rel, create) {
309 Ok(p) => p,
310 Err(e) if !create && matches!(e.raw_os_error(), Some(libc::ENOENT | libc::ENOTDIR)) => {
311 return Ok(None)
312 }
313 Err(e) => return Err(err(e, rel)),
314 };
315 openat(p.as_raw_fd(), c".", libc::O_RDONLY | libc::O_DIRECTORY, 0)
316 .map(Some)
317 .map_err(|e| err(e, rel))
318 }
319
320 fn hide(&self, rel: &Path) -> Result<()> {
323 let Some(pfd) = self.parent(rel, false)? else {
324 return Ok(());
325 };
326 let c = cstr(split(rel).1).map_err(|e| err(e, rel))?;
327 if !self.ours.contains(rel) {
328 return remove_at(pfd.as_raw_fd(), &c).map_err(|e| err(e, rel));
329 }
330 let st = fstatat(pfd.as_raw_fd(), &c).map_err(|e| err(e, rel))?;
331 if st.is_some_and(|st| is(&st, libc::S_IFDIR)) {
332 let d = openat(
333 pfd.as_raw_fd(),
334 &c,
335 libc::O_RDONLY | libc::O_DIRECTORY | libc::O_NOFOLLOW,
336 0,
337 )
338 .map_err(|e| err(e, rel))?;
339 for child in list(d.as_raw_fd()).map_err(|e| err(e, rel))? {
340 self.hide(&rel.join(OsStr::from_bytes(child.to_bytes())))?;
341 }
342 }
343 Ok(())
344 }
345
346 fn whiteout(&mut self, parent: &Path, hidden: Option<&str>) -> Result<()> {
347 self.stats.whiteouts += 1;
348 match (self.layout, hidden) {
349 (Layout::Flatten, None) => {
350 if let Some(d) = self.open_dir(parent, false)? {
351 for child in list(d.as_raw_fd()).map_err(|e| err(e, parent))? {
352 self.hide(&parent.join(OsStr::from_bytes(child.to_bytes())))?;
353 }
354 }
355 }
356 (Layout::Flatten, Some(h)) => self.hide(&parent.join(h))?,
357 (Layout::Overlay(xattr), None) => {
358 let d = self.open_dir(parent, true)?.expect("created");
359 let name: &CStr = match xattr {
360 OverlayXattr::Trusted => c"trusted.overlay.opaque",
361 OverlayXattr::User => c"user.overlay.opaque",
362 };
363 cvt(unsafe {
364 libc::fsetxattr(d.as_raw_fd(), name.as_ptr(), b"y".as_ptr().cast(), 1, 0)
365 })
366 .map_err(|e| err(e, parent))?;
367 add_with_parents(&mut self.ours, parent);
368 }
369 (Layout::Overlay(_), Some(h)) => {
370 let rel = parent.join(h);
371 let pfd = self.parent(&rel, true)?.expect("created");
372 let c = cstr(OsStr::new(h)).map_err(|e| err(e, &rel))?;
373 remove_at(pfd.as_raw_fd(), &c).map_err(|e| err(e, &rel))?;
374 cvt(unsafe { libc::mknodat(pfd.as_raw_fd(), c.as_ptr(), libc::S_IFCHR, 0) })
376 .map_err(|e| err(e, &rel))?;
377 add_with_parents(&mut self.ours, &rel);
378 }
379 }
380 Ok(())
381 }
382
383 fn special_meta(&self, pfd: RawFd, name: &CStr, m: &FileMeta) -> io::Result<()> {
387 let fd = openat(pfd, name, libc::O_PATH | libc::O_NOFOLLOW, 0)?;
388 if is(&fstat(fd.as_raw_fd())?, libc::S_IFLNK) {
389 return Err(escape());
390 }
391 if self.same_owner {
392 cvt(unsafe {
393 libc::fchownat(
394 fd.as_raw_fd(),
395 c"".as_ptr(),
396 m.uid as u32,
397 m.gid as u32,
398 libc::AT_EMPTY_PATH,
399 )
400 })?;
401 }
402 let proc = cstr(OsStr::new(&format!("/proc/self/fd/{}", fd.as_raw_fd())))?;
403 cvt(unsafe { libc::chmod(proc.as_ptr(), m.mode) })?;
404 let t = times(m.mtime);
405 cvt(unsafe { libc::utimensat(libc::AT_FDCWD, proc.as_ptr(), t.as_ptr(), 0) })?;
406 Ok(())
407 }
408
409 fn entry<R: Read>(&mut self, entry: &mut tar::Entry<'_, R>, pool: &mut Pool) -> Result<()> {
410 self.stats.entries += 1;
411 let raw = entry.path().ctx(|| "entry path".into())?.into_owned();
412 let Some(rel) = entry_path(&raw) else {
413 if raw
415 .components()
416 .any(|c| matches!(c, Component::ParentDir | Component::Prefix(_)))
417 {
418 return Err(Error::Escape(raw));
419 }
420 return Ok(());
421 };
422 let (parent, name) = split(&rel);
423 let name_s = name.to_str().unwrap_or("");
424 if name_s == ".wh..wh..opq" {
425 return self.whiteout(parent, None);
426 }
427 if let Some(hidden) = name_s.strip_prefix(".wh.") {
428 if matches!(hidden, "" | "." | "..") {
431 return Err(Error::Escape(rel));
432 }
433 return self.whiteout(parent, Some(hidden));
434 }
435
436 let h = entry.header();
437 let kind = h.entry_type();
438 let known = kind.is_dir()
439 || kind.is_file()
440 || kind == tar::EntryType::Continuous
441 || kind.is_symlink()
442 || kind.is_hard_link()
443 || kind.is_fifo()
444 || kind.is_character_special()
445 || kind.is_block_special();
446 if !known {
447 return Ok(());
448 }
449 let m = FileMeta {
450 mode: h.mode().unwrap_or(0o644) & 0o7777,
451 uid: h.uid().unwrap_or(0),
452 gid: h.gid().unwrap_or(0),
453 mtime: h.mtime().unwrap_or(0),
454 };
455 let pfd = self.parent(&rel, true)?.expect("created");
456 let p = pfd.as_raw_fd();
457 let c = cstr(name).map_err(|e| err(e, &rel))?;
458 let io = |e: io::Error| err(e, &rel);
459
460 if let Some(st) = fstatat(p, &c).map_err(io)? {
462 if !(is(&st, libc::S_IFDIR) && kind.is_dir()) {
463 remove_at(p, &c).map_err(io)?;
464 }
465 }
466
467 if kind.is_dir() {
468 match cvt(unsafe { libc::mkdirat(p, c.as_ptr(), 0o700) }) {
469 Ok(_) => {}
470 Err(e) if e.raw_os_error() == Some(libc::EEXIST) => {}
471 Err(e) => return Err(io(e)),
472 }
473 let d = openat(
474 p,
475 &c,
476 libc::O_RDONLY | libc::O_DIRECTORY | libc::O_NOFOLLOW,
477 0,
478 )
479 .map_err(io)?;
480 if self.same_owner {
481 cvt(unsafe { libc::fchown(d.as_raw_fd(), m.uid as u32, m.gid as u32) })
482 .map_err(io)?;
483 }
484 cvt(unsafe { libc::fchmod(d.as_raw_fd(), m.mode) }).map_err(io)?;
485 self.dirs.push((rel.clone(), m.mtime));
486 } else if kind.is_file() || kind == tar::EntryType::Continuous {
487 let fd = openat(
488 p,
489 &c,
490 libc::O_WRONLY | libc::O_CREAT | libc::O_EXCL | libc::O_NOFOLLOW,
491 0o600,
492 )
493 .map_err(io)?;
494 let mut f = File::from(fd);
495 let size = entry.size();
496 if pool.active() && size > 0 && size <= SMALL_FILE {
497 let mut data = Vec::with_capacity(size as usize);
498 entry
499 .read_to_end(&mut data)
500 .ctx(|| format!("reading {}", rel.display()))?;
501 pool.submit(Job {
502 file: f,
503 data,
504 meta: m,
505 rel: rel.clone(),
506 })?;
507 } else {
508 io::copy(entry, &mut f).ctx(|| format!("writing {}", rel.display()))?;
509 finish_file(&f, &m, self.same_owner).map_err(io)?;
510 }
511 self.stats.files += 1;
512 self.stats.bytes += size;
513 } else if kind.is_symlink() {
514 let target = entry
515 .link_name()
516 .ctx(|| format!("link of {}", rel.display()))?
517 .ok_or_else(|| {
518 Error::Invalid(format!("symlink {} has no target", rel.display()))
519 })?;
520 let t = cstr(target.as_os_str()).map_err(io)?;
521 cvt(unsafe { libc::symlinkat(t.as_ptr(), p, c.as_ptr()) }).map_err(io)?;
522 if self.same_owner {
523 cvt(unsafe {
524 libc::fchownat(
525 p,
526 c.as_ptr(),
527 m.uid as u32,
528 m.gid as u32,
529 libc::AT_SYMLINK_NOFOLLOW,
530 )
531 })
532 .map_err(io)?;
533 }
534 let ts = times(m.mtime);
535 cvt(unsafe { libc::utimensat(p, c.as_ptr(), ts.as_ptr(), libc::AT_SYMLINK_NOFOLLOW) })
536 .map_err(io)?;
537 } else if kind.is_hard_link() {
538 let target = entry
539 .link_name()
540 .ctx(|| format!("link of {}", rel.display()))?
541 .ok_or_else(|| {
542 Error::Invalid(format!("hard link {} has no target", rel.display()))
543 })?;
544 let src = entry_path(&target).ok_or_else(|| Error::Escape(target.to_path_buf()))?;
545 let spfd = self
546 .root
547 .dir(split(&src).0, false)
548 .map_err(|e| err(e, &src))?;
549 let sc = cstr(split(&src).1).map_err(io)?;
550 cvt(unsafe { libc::linkat(spfd.as_raw_fd(), sc.as_ptr(), p, c.as_ptr(), 0) })
552 .map_err(io)?;
553 } else if !kind.is_fifo() && self.devices == Devices::Skip {
554 self.stats.skipped_devices += 1;
555 return Ok(());
556 } else {
557 let (t, dev) = if kind.is_fifo() {
558 (libc::S_IFIFO, 0)
559 } else {
560 let maj = h.device_major().ok().flatten().unwrap_or(0);
561 let min = h.device_minor().ok().flatten().unwrap_or(0);
562 let t = if kind.is_character_special() {
563 libc::S_IFCHR
564 } else {
565 libc::S_IFBLK
566 };
567 (t, libc::makedev(maj, min))
568 };
569 match cvt(unsafe { libc::mknodat(p, c.as_ptr(), t | 0o600, dev) }) {
570 Ok(_) => self.special_meta(p, &c, &m).map_err(io)?,
571 Err(_) if !kind.is_fifo() => {
574 self.stats.skipped_devices += 1;
575 return Ok(());
576 }
577 Err(e) => return Err(io(e)),
578 }
579 }
580 add_with_parents(&mut self.ours, &rel);
581 Ok(())
582 }
583}
584
585pub enum Change<'a, 'b, R: Read> {
587 Opaque(PathBuf),
589 Whiteout(PathBuf),
591 Entry(PathBuf, &'a mut tar::Entry<'b, R>),
593}
594
595pub fn changes<R: Read>(
600 tar: R,
601 mut f: impl FnMut(Change<'_, '_, R>) -> io::Result<()>,
602) -> Result<()> {
603 let mut archive = tar::Archive::new(tar);
604 for entry in archive.entries().ctx(|| "reading layer".into())? {
605 let mut entry = entry.ctx(|| "reading layer".into())?;
606 let raw = entry.path().ctx(|| "entry path".into())?.into_owned();
607 let Some(rel) = entry_path(&raw) else {
608 continue;
609 };
610 let name = rel.file_name().and_then(|n| n.to_str()).unwrap_or("");
611 let parent = rel.parent().unwrap_or(Path::new("")).to_path_buf();
612 if name
613 .strip_prefix(".wh.")
614 .is_some_and(|h| matches!(h, "" | "." | ".."))
615 {
616 continue;
617 }
618 let change = if name == ".wh..wh..opq" {
619 Change::Opaque(parent)
620 } else if let Some(hidden) = name.strip_prefix(".wh.") {
621 Change::Whiteout(parent.join(hidden))
622 } else {
623 Change::Entry(rel, &mut entry)
624 };
625 f(change).ctx(|| "layer visitor".into())?;
626 }
627 Ok(())
628}
629
630pub fn entry_path(path: &Path) -> Option<PathBuf> {
633 let mut out = PathBuf::new();
634 for c in path.components() {
635 match c {
636 Component::Normal(p) => out.push(p),
637 Component::CurDir | Component::RootDir => {}
638 Component::ParentDir | Component::Prefix(_) => return None,
639 }
640 }
641 (!out.as_os_str().is_empty()).then_some(out)
642}
643
644fn add_with_parents(set: &mut HashSet<PathBuf>, rel: &Path) {
645 let mut p = Some(rel);
646 while let Some(q) = p.filter(|q| !q.as_os_str().is_empty()) {
647 if !set.insert(q.to_path_buf()) {
648 break;
649 }
650 p = q.parent();
651 }
652}
653
654struct Job {
656 file: File,
657 data: Vec<u8>,
658 meta: FileMeta,
659 rel: PathBuf,
660}
661
662#[derive(Default)]
663struct State {
664 pending: usize,
665 bytes: usize,
666 error: Option<Error>,
667}
668
669#[derive(Default)]
670struct Shared {
671 state: Mutex<State>,
672 cv: Condvar,
673}
674
675struct Pool {
677 tx: Option<mpsc::Sender<Job>>,
678 shared: Arc<Shared>,
679}
680
681impl Pool {
682 fn new<'s>(s: &'s thread::Scope<'s, '_>, workers: usize, same_owner: bool) -> Self {
683 let shared = Arc::new(Shared::default());
684 if workers == 0 {
685 return Self { tx: None, shared };
686 }
687 let (tx, rx) = mpsc::channel::<Job>();
688 let rx = Arc::new(Mutex::new(rx));
689 for _ in 0..workers {
690 let (rx, shared) = (rx.clone(), shared.clone());
691 s.spawn(move || loop {
694 let job = rx.lock().unwrap().recv();
695 let Ok(mut job) = job else { return };
696 let len = job.data.len();
697 let r = job
698 .file
699 .write_all(&job.data)
700 .and_then(|()| finish_file(&job.file, &job.meta, same_owner))
701 .ctx(|| format!("writing {}", job.rel.display()));
702 drop(job);
703 let mut st = shared.state.lock().unwrap();
704 st.pending -= 1;
705 st.bytes -= len;
706 if let Err(e) = r {
707 st.error.get_or_insert(e);
708 }
709 shared.cv.notify_all();
710 });
711 }
712 Self {
713 tx: Some(tx),
714 shared,
715 }
716 }
717
718 fn active(&self) -> bool {
719 self.tx.is_some()
720 }
721
722 fn submit(&mut self, job: Job) -> Result<()> {
723 let len = job.data.len();
724 let mut st = self.shared.state.lock().unwrap();
725 while st.pending > 0
726 && (st.bytes + len > IN_FLIGHT || st.pending >= OPEN_FILES)
727 && st.error.is_none()
728 {
729 st = self.shared.cv.wait(st).unwrap();
730 }
731 if let Some(e) = st.error.take() {
732 return Err(e);
733 }
734 st.pending += 1;
735 st.bytes += len;
736 drop(st);
737 self.tx
738 .as_ref()
739 .unwrap()
740 .send(job)
741 .expect("writer threads live");
742 Ok(())
743 }
744
745 fn finish(mut self) -> Result<()> {
747 let mut st = self.shared.state.lock().unwrap();
748 while st.pending > 0 {
749 st = self.shared.cv.wait(st).unwrap();
750 }
751 let r = st.error.take().map_or(Ok(()), Err);
752 drop(st);
753 self.tx = None;
754 r
755 }
756}
757
758fn read_ahead<'s, R: Read + Send + 's>(
760 s: &'s thread::Scope<'s, '_>,
761 mut inner: R,
762) -> ChannelReader {
763 let (tx, rx) = mpsc::sync_channel::<io::Result<Vec<u8>>>(4);
764 s.spawn(move || loop {
767 let mut buf = vec![0u8; CHUNK];
768 let mut n = 0;
769 while n < CHUNK {
770 match inner.read(&mut buf[n..]) {
771 Ok(0) => break,
772 Ok(k) => n += k,
773 Err(e) if e.kind() == io::ErrorKind::Interrupted => {}
774 Err(e) => {
775 let _ = tx.send(Err(e));
776 return;
777 }
778 }
779 }
780 buf.truncate(n);
781 let done = n < CHUNK;
782 if (n > 0 && tx.send(Ok(buf)).is_err()) || done {
783 return;
784 }
785 });
786 ChannelReader {
787 rx,
788 buf: Vec::new(),
789 pos: 0,
790 }
791}
792
793struct ChannelReader {
794 rx: mpsc::Receiver<io::Result<Vec<u8>>>,
795 buf: Vec<u8>,
796 pos: usize,
797}
798
799impl Read for ChannelReader {
800 fn read(&mut self, out: &mut [u8]) -> io::Result<usize> {
801 while self.pos == self.buf.len() {
802 match self.rx.recv() {
803 Ok(chunk) => {
804 self.buf = chunk?;
805 self.pos = 0;
806 }
807 Err(_) => return Ok(0),
808 }
809 }
810 let n = out.len().min(self.buf.len() - self.pos);
811 out[..n].copy_from_slice(&self.buf[self.pos..self.pos + n]);
812 self.pos += n;
813 Ok(n)
814 }
815}