1use std::cell::RefCell;
25use std::collections::{BTreeMap, HashMap};
26use std::ffi::{CString};
27use std::fs::Metadata;
28use std::io::Read;
29use std::path::Path;
30use std::sync::{Mutex, RwLock};
31use std::time::SystemTime;
32use super::*;
33use walkdir::{DirEntry, WalkDir};
34
35#[repr(u32)]
40pub enum BlockFlags {
41 DontCompress = super::SQFS_BLK_FLAGS_SQFS_BLK_DONT_COMPRESS,
47
48 BlockAlign = super::SQFS_BLK_FLAGS_SQFS_BLK_ALIGN,
53
54 DontFragment = super::SQFS_BLK_FLAGS_SQFS_BLK_DONT_FRAGMENT,
62
63 DontDeduplicate = super::SQFS_BLK_FLAGS_SQFS_BLK_DONT_DEDUPLICATE,
69
70 IgnoreSparse = super::SQFS_BLK_FLAGS_SQFS_BLK_IGNORE_SPARSE,
76
77 DontHash = super::SQFS_BLK_FLAGS_SQFS_BLK_DONT_HASH,
81}
82
83pub enum SourceData {
88 File(Box<dyn Read + Sync + Send>),
93
94 Dir(Box<dyn Iterator<Item=(OsString, u32)> + Sync + Send>),
103
104 Symlink(PathBuf),
108
109 BlockDev(u32, u32),
111
112 CharDev(u32, u32),
114
115 Fifo,
117
118 Socket,
120}
121
122pub struct Source {
134 pub data: SourceData,
136
137 pub uid: u32,
139
140 pub gid: u32,
142
143 pub mode: u16,
145
146 pub modified: u32,
148
149 pub xattrs: HashMap<OsString, Vec<u8>>,
152
153 pub flags: u32,
156}
157
158fn file_xattrs(path: &Path) -> Result<HashMap<OsString, Vec<u8>>> {
159 xattr::list(path)?.map(|attr| {
160 let value = xattr::get(path, attr.clone()).map_err(|e| SquashfsError::Xattr(path.to_path_buf(), e))?
161 .expect(&format!("Could not retrieve xattr {:?} reported to be present", attr));
162 Ok((attr, value))
163 }).collect()
164}
165
166fn copy_metadata(src: &ManagedPointer<sqfs_inode_generic_t>, dst: &mut ManagedPointer<sqfs_inode_generic_t>) -> Result<()> {
167 let (src_base, dst_base) = unsafe { (&(***src).base, &mut (***dst).base) };
168 dst_base.mode = src_base.mode;
169 dst_base.uid_idx = src_base.uid_idx;
170 dst_base.gid_idx = src_base.gid_idx;
171 dst_base.mod_time = src_base.mod_time;
172 dst_base.inode_number = src_base.inode_number;
173 let mut xattr_idx: u32 = 0;
174 unsafe {
175 sfs_check(sqfs_inode_get_xattr_index(**src, &mut xattr_idx), "Couldn't get xattr index")?;
176 sfs_check(sqfs_inode_set_xattr_index(**dst, xattr_idx), "Couldn't set xattr index")?;
177 }
178 Ok(())
179}
180
181impl Source {
182 pub fn defaults(data: SourceData) -> Self {
184 Self { data: data, uid: 0, gid: 0, mode: 0x1ff, modified: 0, xattrs: HashMap::new(), flags: 0 }
185 }
186
187 fn devno(maj: u32, min: u32) -> u32 {
188 ((min & 0xfff00) << 20) | ((maj & 0xfff) << 8) | (min & 0xff)
189 }
190
191 unsafe fn to_inode(&self, link_count: u32) -> Result<ManagedPointer<sqfs_inode_generic_t>> {
192 unsafe fn create_inode(kind: SQFS_INODE_TYPE, extra: usize) -> ManagedPointer<sqfs_inode_generic_t> {
193 use std::alloc::{alloc, Layout};
194 use std::mem::{align_of, size_of};
195 let layout = Layout::from_size_align_unchecked(size_of::<sqfs_inode_generic_t>() + extra, align_of::<sqfs_inode_generic_t>());
196 let ret = alloc(layout) as *mut sqfs_inode_generic_t;
197 (*ret).base.type_ = kind as u16;
198 ManagedPointer::new(ret, rust_dealloc)
199 }
200 let ret = match &self.data {
201 SourceData::File(_) => create_inode(SQFS_INODE_TYPE_SQFS_INODE_FILE, 0),
202 SourceData::Dir(_) => {
203 let mut ret = create_inode(SQFS_INODE_TYPE_SQFS_INODE_DIR, 0);
204 (**ret).data.dir.nlink = link_count;
205 ret
206 },
207 SourceData::Symlink(dest_os) => {
208 let dest = os_to_string(dest_os.as_os_str())?.into_bytes();
209 let mut ret = create_inode(SQFS_INODE_TYPE_SQFS_INODE_SLINK, dest.len());
210 let data = &mut (**ret).data.slink;
211 data.nlink = link_count;
212 data.target_size = dest.len() as u32;
213 let dest_field = std::mem::transmute::<_, &mut [u8]>((**ret).extra.as_mut_slice(dest.len()));
214 dest_field.copy_from_slice(dest.as_slice());
215 ret
216 },
217 SourceData::BlockDev(maj, min) => {
218 let mut ret = create_inode(SQFS_INODE_TYPE_SQFS_INODE_BDEV, 0);
219 let data = &mut (**ret).data.dev;
220 data.nlink = link_count;
221 data.devno = Self::devno(*maj, *min);
222 ret
223 },
224 SourceData::CharDev(maj, min) => {
225 let mut ret = create_inode(SQFS_INODE_TYPE_SQFS_INODE_CDEV, 0);
226 let data = &mut (**ret).data.dev;
227 data.nlink = link_count;
228 data.devno = Self::devno(*maj, *min);
229 ret
230 },
231 SourceData::Fifo => {
232 let mut ret = create_inode(SQFS_INODE_TYPE_SQFS_INODE_FIFO, 0);
233 (**ret).data.ipc.nlink = link_count;
234 ret
235 },
236 SourceData::Socket => {
237 let mut ret = create_inode(SQFS_INODE_TYPE_SQFS_INODE_SOCKET, 0);
238 (**ret).data.ipc.nlink = link_count;
239 ret
240 },
241 };
242 Ok(ret)
243 }
244}
245
246struct IntermediateNode {
247 inode: Box<ManagedPointer<sqfs_inode_generic_t>>,
248 dir_children: Option<Box<dyn Iterator<Item=(OsString, u32)> + Sync + Send>>,
249 pos: u64,
250}
251
252pub struct SourceFile {
262 pub path: PathBuf,
263 pub content: Source,
264}
265
266impl SourceFile {
267 pub fn defaults(path: PathBuf, data: SourceData) -> Self {
272 Self { path: path, content: Source::defaults(data) }
273 }
274}
275
276pub struct Writer {
298 outfile: ManagedPointer<sqfs_file_t>,
299 #[allow(dead_code)] compressor_config: sqfs_compressor_config_t, compressor: ManagedPointer<sqfs_compressor_t>,
301 superblock: sqfs_super_t,
302 #[allow(dead_code)] block_writer: ManagedPointer<sqfs_block_writer_t>, block_processor: Mutex<ManagedPointer<sqfs_block_processor_t>>,
304 frag_table: ManagedPointer<sqfs_frag_table_t>,
305 id_table: Mutex<ManagedPointer<sqfs_id_table_t>>,
306 xattr_writer: Mutex<ManagedPointer<sqfs_xattr_writer_t>>,
307 inode_writer: ManagedPointer<sqfs_meta_writer_t>,
308 dirent_writer: ManagedPointer<sqfs_meta_writer_t>,
309 dir_writer: ManagedPointer<sqfs_dir_writer_t>,
310 nodes: Mutex<Vec<RefCell<IntermediateNode>>>,
311 finished: RwLock<bool>,
312}
313
314impl Writer {
315 pub fn open<T: AsRef<Path>>(path: T) -> Result<Self> {
319 let cpath = CString::new(os_to_string(path.as_ref().as_os_str())?)?;
320 let block_size = SQFS_DEFAULT_BLOCK_SIZE as usize;
321 let num_workers = num_cpus::get() as u32;
322 let compressor_id = SQFS_COMPRESSOR_SQFS_COMP_ZSTD;
323 let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?.as_secs() as u32;
324 let outfile = sfs_init_check_null(&|| unsafe {
325 sqfs_open_file(cpath.as_ptr(), SQFS_FILE_OPEN_FLAGS_SQFS_FILE_OPEN_OVERWRITE)
326 }, &format!("Couldn't open output file {}", path.as_ref().display()), sfs_destroy)?;
327 let compressor_config = sfs_init(&|x| unsafe {
328 sqfs_compressor_config_init(x, compressor_id, block_size, 0)
329 }, "Couldn't create compressor config")?;
330 let compressor = sfs_init_ptr(&|x| unsafe {
331 sqfs_compressor_create(&compressor_config, x)
332 }, "Couldn't create compressor", sfs_destroy)?;
333 let superblock = sfs_init(&|x| unsafe {
334 sqfs_super_init(x, block_size, now, compressor_id)
335 }, "Couldn't create superblock")?;
336 let frag_table = sfs_init_check_null(&|| unsafe {
337 sqfs_frag_table_create(0)
338 }, "Couldn't create fragment table", sfs_destroy)?;
339 let block_writer = sfs_init_check_null(&|| unsafe {
340 sqfs_block_writer_create(*outfile, 4096, 0)
341 }, "Couldn't create block writer", sfs_destroy)?;
342 let block_processor = Mutex::new(sfs_init_check_null(&|| unsafe {
343 sqfs_block_processor_create(block_size, *compressor, num_workers, 10 * num_workers as usize, *block_writer, *frag_table)
344 }, "Couldn't create block processor", sfs_destroy)?);
345 let id_table = Mutex::new(sfs_init_check_null(&|| unsafe {
346 sqfs_id_table_create(0)
347 }, "Couldn't create ID table", sfs_destroy)?);
348 let xattr_writer = Mutex::new(sfs_init_check_null(&|| unsafe {
349 sqfs_xattr_writer_create(0)
350 }, "Couldn't create xattr writer", sfs_destroy)?);
351 let inode_writer = sfs_init_check_null(&|| unsafe {
352 sqfs_meta_writer_create(*outfile, *compressor, 0)
353 }, "Couldn't create inode metadata writer", sfs_destroy)?;
354 let dirent_writer = sfs_init_check_null(&|| unsafe {
355 sqfs_meta_writer_create(*outfile, *compressor, SQFS_META_WRITER_FLAGS_SQFS_META_WRITER_KEEP_IN_MEMORY)
356 }, "Couldn't create directory entry metadata writer", sfs_destroy)?;
357 let dir_writer = sfs_init_check_null(&|| unsafe {
358 sqfs_dir_writer_create(*dirent_writer, SQFS_DIR_WRITER_CREATE_FLAGS_SQFS_DIR_WRITER_CREATE_EXPORT_TABLE)
359 }, "Couldn't create directory writer", sfs_destroy)?;
360 unsafe {
361 sfs_check(sqfs_super_write(&superblock, *outfile), "Couldn't write archive superblock")?;
362 sfs_check((**compressor).write_options.expect("Compressor doesn't provide write_options")(*compressor, *outfile), "Couldn't write compressor options")?;
363 }
364 Ok(Self {
365 outfile: outfile,
366 compressor_config: compressor_config,
367 compressor: compressor,
368 superblock: superblock,
369 block_writer: block_writer,
370 block_processor: block_processor,
371 frag_table: frag_table,
372 id_table: id_table,
373 xattr_writer: xattr_writer,
374 inode_writer: inode_writer,
375 dirent_writer: dirent_writer,
376 dir_writer: dir_writer,
377 nodes: Mutex::new(vec![]),
378 finished: RwLock::new(false),
379 })
380 }
381
382 fn mode_from_inode(inode: &ManagedPointer<sqfs_inode_generic_t>) -> u16 {
383 lazy_static! {
384 static ref TYPENUMS: HashMap<u32, u32> = vec![
385 (SQFS_INODE_TYPE_SQFS_INODE_DIR, S_IFDIR),
386 (SQFS_INODE_TYPE_SQFS_INODE_FILE, S_IFREG),
387 (SQFS_INODE_TYPE_SQFS_INODE_SLINK, S_IFLNK),
388 (SQFS_INODE_TYPE_SQFS_INODE_BDEV, S_IFBLK),
389 (SQFS_INODE_TYPE_SQFS_INODE_CDEV, S_IFCHR),
390 (SQFS_INODE_TYPE_SQFS_INODE_FIFO, S_IFIFO),
391 (SQFS_INODE_TYPE_SQFS_INODE_SOCKET, S_IFSOCK),
392 (SQFS_INODE_TYPE_SQFS_INODE_EXT_DIR, S_IFDIR),
393 (SQFS_INODE_TYPE_SQFS_INODE_EXT_FILE, S_IFREG),
394 (SQFS_INODE_TYPE_SQFS_INODE_EXT_SLINK, S_IFLNK),
395 (SQFS_INODE_TYPE_SQFS_INODE_EXT_BDEV, S_IFBLK),
396 (SQFS_INODE_TYPE_SQFS_INODE_EXT_CDEV, S_IFCHR),
397 (SQFS_INODE_TYPE_SQFS_INODE_EXT_FIFO, S_IFIFO),
398 (SQFS_INODE_TYPE_SQFS_INODE_EXT_SOCKET, S_IFSOCK),
399 ].into_iter().collect();
400 }
401 let base = unsafe { (***inode).base };
402 TYPENUMS[&(base.type_ as u32)] as u16 | base.mode
403 }
404
405 fn outfile_size(&self) -> u64 {
406 unsafe { (**self.outfile).get_size.expect("Superblock doesn't provide get_size")(*self.outfile) }
407 }
408
409 pub fn add(&mut self, mut source: Source) -> Result<u32> {
420 let finished = self.finished.read().expect("Poisoned lock");
421 if *finished { Err(SquashfsError::Finished)?; }
422 let flags = source.flags;
423 let nlink = 1; let mut inode = unsafe {
425 match source.data {
426 SourceData::File(ref mut reader) => {
427 let mut ret = Box::new(ManagedPointer::null(libc_free));
428 let block_processor = self.block_processor.lock().expect("Poisoned lock");
429 sfs_check(sqfs_block_processor_begin_file(**block_processor, &mut **ret, ptr::null_mut(), flags), "Couldn't begin writing file")?;
430 let mut buf = vec![0; BLOCK_BUF_SIZE];
431 loop {
432 let rdsize = reader.read(&mut buf)?;
433 if rdsize == 0 { break; }
434 sfs_check(sqfs_block_processor_append(**block_processor, &buf as &[u8] as *const [u8] as *const libc::c_void, rdsize), "Couldn't write file data block")?;
435 }
436 sfs_check(sqfs_block_processor_end_file(**block_processor), "Couldn't finish writing file")?;
437 ret
438 },
439 _ => Box::new(source.to_inode(nlink)?),
440 }
441 };
442 unsafe {
443 let xattr_writer = self.xattr_writer.lock().expect("Poisoned lock");
444 sfs_check(sqfs_xattr_writer_begin(**xattr_writer, 0), "Couldn't start writing xattrs")?;
445 for (key, value) in &source.xattrs {
446 let ckey = CString::new(os_to_string(key)?)?;
447 sfs_check(sqfs_xattr_writer_add(**xattr_writer, ckey.as_ptr() as *const libc::c_char, value.as_ptr() as *const libc::c_void, value.len()), "Couldn't add xattr")?;
448 }
449 let xattr_idx = sfs_init(&|x| sqfs_xattr_writer_end(**xattr_writer, x), "Couldn't finish writing xattrs")?;
450 let base = &mut (***inode).base;
451 base.mode = source.mode;
452 sqfs_inode_set_xattr_index(**inode, xattr_idx);
453 let id_table = self.id_table.lock().expect("Poisoned lock");
454 sfs_check(sqfs_id_table_id_to_index(**id_table, source.uid, &mut base.uid_idx), "Couldn't set inode UID")?;
455 sfs_check(sqfs_id_table_id_to_index(**id_table, source.gid, &mut base.gid_idx), "Couldn't set inode GID")?;
456 base.mod_time = source.modified;
457 }
458 let dir_children = match source.data {
459 SourceData::Dir(children) => Some(children),
460 _ => None,
461 };
462 let mut nodes = self.nodes.lock().expect("Poisoned lock");
463 let nodenum = nodes.len() as u32 + 1;
464 unsafe { (***inode).base.inode_number = nodenum; }
465 nodes.push(RefCell::new(IntermediateNode { inode: inode, dir_children: dir_children, pos: 0 }));
466 Ok(nodenum)
467 }
468
469 pub fn finish(&mut self) -> Result<()> {
473 *self.finished.write().expect("Poisoned lock") = true;
474 let nodes = self.nodes.lock().expect("Poisoned lock");
475 unsafe {
476 sfs_check(sqfs_block_processor_finish(**self.block_processor.lock().expect("Poisoned lock")), "Failed finishing block processing")?;
477 self.superblock.inode_table_start = self.outfile_size();
478 for raw_node in &*nodes {
479 let mut node = raw_node.borrow_mut();
480 let id = (***node.inode).base.inode_number;
481 if let Some(children) = node.dir_children.take() {
482 sfs_check(sqfs_dir_writer_begin(*self.dir_writer, 0), "Couldn't start writing directory")?;
483 for (name, child_id) in children { if child_id >= id { Err(SquashfsError::WriteOrder(child_id))?; }
486 let child_node = &nodes[child_id as usize - 1].borrow();
487 let child = child_node.inode.as_ref();
488 let child_ref = child_node.pos;
489 sfs_check(sqfs_dir_writer_add_entry(*self.dir_writer, CString::new(os_to_string(&name)?)?.as_ptr(), child_id, child_ref, Self::mode_from_inode(&child)), "Couldn't add directory entry")?;
490 }
491 sfs_check(sqfs_dir_writer_end(*self.dir_writer), "Couldn't finish writing directory")?;
492 let mut ret = Box::new(sfs_init_check_null(&|| {
493 sqfs_dir_writer_create_inode(*self.dir_writer, 0, 0, 0) }, "Couldn't get inode for directory", libc_free)?);
495 copy_metadata(&*node.inode, &mut ret)?;
496 node.inode = ret;
497 }
498 let (mut block, mut offset) = (0, 0);
499 sqfs_meta_writer_get_position(*self.inode_writer, &mut block, &mut offset);
500 node.pos = block << 16 | offset as u64;
501 sfs_check(sqfs_meta_writer_write_inode(*self.inode_writer, **node.inode), "Couldn't write inode")?;
502 }
503
504 let root_ref = nodes.last().ok_or(SquashfsError::Empty)?.borrow().pos;
505 self.superblock.root_inode_ref = root_ref;
506 sfs_check(sqfs_meta_writer_flush(*self.inode_writer), "Couldn't flush inodes")?;
507 sfs_check(sqfs_meta_writer_flush(*self.dirent_writer), "Couldn't flush directory entries")?;
508 self.superblock.directory_table_start = self.outfile_size();
509 sfs_check(sqfs_meta_write_write_to_file(*self.dirent_writer), "Couldn't write directory entries")?;
510 self.superblock.inode_count = nodes.len() as u32;
511 sfs_check(sqfs_frag_table_write(*self.frag_table, *self.outfile, &mut self.superblock, *self.compressor), "Couldn't write fragment table")?;
512 sfs_check(sqfs_dir_writer_write_export_table(*self.dir_writer, *self.outfile, *self.compressor, nodes.len() as u32, root_ref, &mut self.superblock), "Couldn't write export table")?;
513 sfs_check(sqfs_id_table_write(**self.id_table.lock().expect("Poisoned lock"), *self.outfile, &mut self.superblock, *self.compressor), "Couldn't write ID table")?;
514 sfs_check(sqfs_xattr_writer_flush(**self.xattr_writer.lock().expect("Poisoned lock"), *self.outfile, &mut self.superblock, *self.compressor), "Couldn't write xattr table")?;
515 self.superblock.bytes_used = self.outfile_size();
516 sfs_check(sqfs_super_write(&self.superblock, *self.outfile), "Couldn't rewrite archive superblock")?;
517 let padding: Vec<u8> = vec![0; PAD_TO - self.outfile_size() as usize % PAD_TO];
518 sfs_check((**self.outfile).write_at.expect("File does not provide write_at")(*self.outfile, self.outfile_size(), &padding as &[u8] as *const [u8] as *const libc::c_void, padding.len()), "Couldn't pad file")?;
519 }
520 Ok(())
521 }
522}
523
524unsafe impl Sync for Writer { }
525unsafe impl Send for Writer { }
526
527enum ChildMapEntry {
528 Accumulating(BTreeMap<OsString, u32>),
529 Done,
530}
531
532impl ChildMapEntry {
533 fn new() -> Self {
534 Self::Accumulating(BTreeMap::new())
535 }
536
537 fn add(&mut self, name: OsString, id: u32) -> Result<()> {
538 match self {
539 Self::Done => Err(SquashfsError::WriteOrder(id))?,
540 Self::Accumulating(children) => {
541 children.insert(name, id);
542 Ok(())
543 },
544 }
545 }
546
547 fn finish(&mut self) -> Result<BTreeMap<OsString, u32>> {
548 match std::mem::replace(self, Self::Done) {
549 Self::Done => Err(SquashfsError::Internal("Tried to finish directory in tree processor multiple times".to_string()))?,
550 Self::Accumulating(children) => Ok(children),
551 }
552 }
553}
554
555pub struct TreeProcessor {
585 writer: Mutex<Writer>,
586 childmap: Mutex<HashMap<PathBuf, ChildMapEntry>>,
587}
588
589impl TreeProcessor {
590 pub fn new<P: AsRef<Path>>(outfile: P) -> Result<Self> {
592 let writer = Writer::open(outfile)?;
593 Ok(Self { writer: Mutex::new(writer), childmap: Mutex::new(HashMap::new()) })
594 }
595
596 pub fn add(&self, mut source: SourceFile) -> Result<u32> {
600 let mut childmap = self.childmap.lock().expect("Poisoned lock");
601 if let SourceData::Dir(old_children) = &mut source.content.data {
602 let mut children = childmap.entry(source.path.clone()).or_insert(ChildMapEntry::new()).finish()?;
603 children.extend(old_children);
604 source.content.data = SourceData::Dir(Box::new(children.into_iter()));
605 }
606 let id = self.writer.lock().expect("Poisoned lock").add(source.content)?;
607 if let Some(parent) = source.path.parent() {
608 childmap.entry(parent.to_path_buf()).or_insert(ChildMapEntry::new()).add(source.path.file_name().expect("Path from walkdir has no file name").to_os_string(), id)?;
609 }
610 Ok(id)
611 }
612
613 #[cfg(target_os = "linux")]
614 fn apply_metadata(source: &mut Source, metadata: Metadata) {
615 use std::os::linux::fs::MetadataExt;
616 source.uid = metadata.st_uid();
617 source.gid = metadata.st_gid();
618 source.mode = (metadata.st_mode() & !S_IFMT) as u16;
619 }
620
621 #[cfg(target_os = "unix")]
622 fn apply_metadata(source: &mut Source, metadata: Metadata) {
623 use std::os::unix::fs::MetadataExt;
624 source.uid = metadata.uid();
625 source.gid = metadata.gid();
626 source.mode = (metadata.mode() & 0x1ff) as u16;
627 }
628
629 #[cfg(all(not(target_os = "linux"), not(target_os = "unix")))]
630 fn apply_metadata(_source: &mut Source, _metadata: Metadata) { }
631
632 pub fn finish(&self) -> Result<()> {
634 self.writer.lock().expect("Poisoned lock").finish()
635 }
636
637 fn make_source(&self, entry: DirEntry) -> Result<Source> {
638 let metadata = entry.metadata().unwrap();
639 let mtime = metadata.modified()?.duration_since(SystemTime::UNIX_EPOCH)?.as_secs() as u32;
640 let data = if metadata.file_type().is_dir() {
641 SourceData::Dir(Box::new(BTreeMap::new().into_iter()))
642 }
643 else if metadata.file_type().is_file() {
644 SourceData::File(Box::new(std::fs::File::open(entry.path())?))
645 }
646 else if metadata.file_type().is_symlink() {
647 SourceData::Symlink(std::fs::read_link(entry.path())?)
648 }
649 else {
650 Err(SquashfsError::WriteType(metadata.file_type()))?;
651 unreachable!();
652 };
653 let mut source = Source { data: data, xattrs: file_xattrs(entry.path())?, uid: 0, gid: 0, mode: 0x1ff, modified: mtime, flags: 0 };
654 Self::apply_metadata(&mut source, metadata);
655 Ok(source)
656 }
657
658 pub fn iter<'a, P: AsRef<Path>>(&'a self, root: P) -> TreeIterator<'a> {
661 let tree = WalkDir::new(root).follow_links(false).contents_first(true);
662 TreeIterator { processor: self, tree: tree.into_iter() }
663 }
664
665 pub fn process<P: AsRef<Path>>(self, root: P) -> Result<()> {
670 for entry in self.iter(root) { self.add(entry?)?; }
671 self.finish()?;
672 Ok(())
673 }
674}
675
676pub struct TreeIterator<'a> {
681 processor: &'a TreeProcessor,
682 tree: walkdir::IntoIter,
683}
684
685impl<'a> std::iter::Iterator for TreeIterator<'a> {
686 type Item = Result<SourceFile>;
687
688 fn next(&mut self) -> Option<Self::Item> {
689 match self.tree.next() {
690 None => None,
691 Some(Ok(entry)) => {
692 let path = entry.path().to_path_buf();
693 Some(self.processor.make_source(entry).map(|source| SourceFile { path: path, content: source }))
694 },
695 Some(Err(e)) => {
696 let path = e.path().map(|x| x.to_string_lossy().into_owned()).unwrap_or("(unknown)".to_string());
697 eprintln!("Not processing {}: {}", path, e.to_string());
698 self.next()
699 },
700 }
701 }
702}