squashfs_ng/
write.rs

1//! Facilities for writing SquashFS archives.
2//!
3//! The most straightforward way to write a SquashFS file from a directory tree on-disk is to use a
4//! [`TreeProcessor`].  This provides the ability to make "last-minute" modifications to the files
5//! that are added, such as skipping certain files or modifying metadata.
6//!
7//! To create a totally "synthetic" SquashFS file that is not built from files in a filesystem,
8//! open a [`Writer`] and feed [`Source`]s to it.
9//!
10//! # Limitations
11//!
12//! This library does not yet handle hard links; files with multiple hard links will be archived as
13//! separate files with identical contents (which should be deduplicated and end up taking up
14//! little additional space).
15//!
16//! The SquashFS specification includes a field in directory inodes for the parent inode number,
17//! presumably to make `..` directory entries work.  This is one factor that makes it impossible to
18//! build a SquashFS file without building out the entire directory tree to be archived in memory.
19//! I have tried as hard as poassible to reduce the amount of data that must be stored for each
20//! node added, and this architecture makes it infeasible to store parent inodes in directory
21//! entries.  I hope to fix this some day, and in the meantime it has not caused problems in the
22//! ways I have used the resultant files.
23
24use 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/// Flags to fine-tune how an entry is added to the archive.
36///
37/// These valued can be ORed together and passed in the [`flags`](Source::flags) field of a
38/// [`Source`] object.
39#[repr(u32)]
40pub enum BlockFlags {
41	/// Don't compress file data.
42	///
43	/// By default, files are compressed, and the compressed version is stored in the archive if it
44	/// is smaller than the uncompressed version.  Setting this flag will force the file to be
45	/// stored uncompressed.
46	DontCompress = super::SQFS_BLK_FLAGS_SQFS_BLK_DONT_COMPRESS,
47
48	/// Align the file data to device blocks.
49	///
50	/// If set, padding will be added before and after this file's data blocks so that it is
51	/// aligned to the blocks of the underlying disk.
52	BlockAlign = super::SQFS_BLK_FLAGS_SQFS_BLK_ALIGN,
53
54	/// Store the tail of the file in a regular data block rather than a fragment block.
55	///
56	/// The compressed content of a file to be written to an archive is split into equally-sized
57	/// blocks and stored as "data blocks".  The final chunk is usually smaller than the rest, so
58	/// these final chunks are collected from multiple files are collected and stored together in
59	/// separate "fragment blocks" as an optimization.  If there is a reason for the entire file's
60	/// contents to be stored together, fragmentation can be disabled using this flag.
61	DontFragment = super::SQFS_BLK_FLAGS_SQFS_BLK_DONT_FRAGMENT,
62
63	/// Don't deduplicated data blocks for this file.
64	///
65	/// If two files contain an identical data block, the block will be stored only once and both
66	/// files' block indices will point to this single block.  The user can force all blocks of a
67	/// file to be stored by setting this flag.
68	DontDeduplicate = super::SQFS_BLK_FLAGS_SQFS_BLK_DONT_DEDUPLICATE,
69
70	/// Don't elide sparse blocks.
71	///
72	/// If a block of a file contains only zeros, it will not be stored at all and the file's block
73	/// index will mark that the block is all-zero.  This behavior can be disabled so that a zero
74	/// block will be written by setting this flag.
75	IgnoreSparse = super::SQFS_BLK_FLAGS_SQFS_BLK_IGNORE_SPARSE,
76
77	/// Don't compute block checksums for this file.
78	///
79	/// Each data block is checksummed to verify data integrity unless this flag is set.
80	DontHash = super::SQFS_BLK_FLAGS_SQFS_BLK_DONT_HASH,
81}
82
83/// Represents the data of a filesystem object that can be added to an archive.
84///
85/// When creating the archive, this object is read from a [`Source`] (which additionally describes
86/// the filesystem attributes of the node) and used to set the type and contents of the node.
87pub enum SourceData {
88	/// Create a file with the provided contents.
89	///
90	/// The contained object will be read and its contents placed in the file written to the
91	/// archive.
92	File(Box<dyn Read + Sync + Send>),
93
94	/// Create a directory with the given chidren.
95	///
96	/// The creator must provide an iterator over [`OsString`] and `u32`, which respectively
97	/// represent the name and inode number of each child of this directory.  This is one of the
98	/// hardest parts about writing archive contents -- all children of each directory must be
99	/// written before the directory itself, so that the inode numbers of the children are known.
100	/// [`TreeProcessor`] facilitates this by performing a post-order traversal of a filesystem,
101	/// ensuring that files are written in the correct order.
102	Dir(Box<dyn Iterator<Item=(OsString, u32)> + Sync + Send>),
103
104	/// Create a symbolic link to the given path.
105	///
106	/// It is not required for the target of the symlink to exist.
107	Symlink(PathBuf),
108
109	/// Create a block device file with the given major and minor device numbers.
110	BlockDev(u32, u32),
111
112	/// Create a character device file with the given major and minor device numbers.
113	CharDev(u32, u32),
114
115	/// Create a named pipe.
116	Fifo,
117
118	/// Create a socket.
119	Socket,
120}
121
122/// A single node to be added to the SquashFS archive.
123///
124/// This contains a [`SourceData`] instance containing the actual data of the node, along with
125/// metadata such as permissions and extended attributes.  The path to the node is not part of this
126/// object, because all information necessary to reconstruct the directory tree is contained in the
127/// directory iterators.  However, for higher-level mechanisms that abstract away details such as
128/// inode numbers, it is helpful to associate a path with each `Source`; [`SourceFile`] is used for
129/// this purpose.
130///
131/// This object is designed to be constructed by the user by setting all fields to the appropriate
132/// values.
133pub struct Source {
134	/// The type of the node and the data it contains.
135	pub data: SourceData,
136
137	/// The UID of the file.
138	pub uid: u32,
139
140	/// The GID of the file.
141	pub gid: u32,
142
143	/// The file mode.
144	pub mode: u16,
145
146	/// The modification time of the file as a Unix timestamp.
147	pub modified: u32,
148
149	/// Extended attributes on the node.  Each one must start with a valid xattr namespace (such as
150	/// "user.", and the values can be arbitrary byte strings.
151	pub xattrs: HashMap<OsString, Vec<u8>>,
152
153	/// [`BlockFlags`] to set on the node to control how its contents are archived.  Multiple flags
154	/// can be combined using `|`.
155	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	/// Construct a `Source` from a `SourceData`, using defaults for all metadata fields.
183	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
252/// A [`Source`] bundled with the path where it should be located.
253///
254/// While the path of a `Source` is not strictly necessary to build the directory tree, it is a
255/// useful way for automatic archive builders like [`TreeProcessor`] to keep track of files as they
256/// are being added.
257///
258/// For purposes for which the metadata stored in [`Source`], like permissions and xattrs, are
259/// unnecessary, [`defaults`](Self::defaults) can be used to conveniently construct a `FileSource`
260/// from a [`PathBuf`] and [`SourceData`].
261pub struct SourceFile {
262	pub path: PathBuf,
263	pub content: Source,
264}
265
266impl SourceFile {
267	/// Wrap a `SourceData` in a new `Source`, using defaults for all metadata fields.
268	///
269	/// This sets UID and GID to 0 and permissions to 0o777, gives a null modification time and no
270	/// xattrs, and sets no flags.
271	pub fn defaults(path: PathBuf, data: SourceData) -> Self {
272		Self { path: path, content: Source::defaults(data) }
273	}
274}
275
276/// A basic SquashFS writer.
277///
278/// This provides a simple interface for writing archives.  The user calls [`open`](Self::open),
279/// [`add`](Self::add) to add each node, and [`finish`](Self::finish) to finish writing.  This is
280/// intended for writing archives that are generated by code or otherwise not reflected by files in
281/// a file system -- if you want to archive a tree of files from disk, [`TreeProcessor`] handles
282/// directory tracking so that you don't have to do it yourself.
283///
284/// **Each node must be written before its parent**, and an error will be raised if this invariant
285/// is not maintained -- however, this is not detected until `finish` is called.
286///
287///     let writer = Writer::open("archive.sfs")?;
288///     let mut ids = HashMap::new();
289///     for i in 0..5 {
290///         let mut content = format!("This is the content of file {}.txt.", i).as_bytes();
291///         let source = Source::defaults(SourceData::File(Box::new(content)));
292///         let id = writer.add(source)?;
293///         ids.insert(OsString::from(format!("{}.txt", i)), id);
294///     }
295///     writer.add(Source::defaults(SourceData::Dir(Box::new(ids.into_iter()))))?;
296///     writer.finish()?;
297pub struct Writer {
298	outfile: ManagedPointer<sqfs_file_t>,
299	#[allow(dead_code)] compressor_config: sqfs_compressor_config_t, // Referenced by `compressor`
300	compressor: ManagedPointer<sqfs_compressor_t>,
301	superblock: sqfs_super_t,
302	#[allow(dead_code)] block_writer: ManagedPointer<sqfs_block_writer_t>, // Referenced by `block_processor`
303	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	/// Open a new output file for writing.
316	///
317	/// If the file exists, it will be overwritten.
318	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	/// Add the provided `Source` to the archive.
410	///
411	/// This writes file data and xattrs to the archive directly, while storing directory tree
412	/// information to write when `finish` is called.
413	///
414	/// The returned value is the inode number of the added `Source`.  If the file is to be added
415	/// to a directory (that is, almost always), this number needs to be stored so that it can be
416	/// provided when the directory is added.  In the current implementation, inode numbers start
417	/// at 1 for the first file and count steadily upward, but this behavior may change without
418	/// warning.
419	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; // TODO Handle hard links
424		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	/// Finish writing the archive and flush all contents to disk.
470	///
471	/// It is an error to call `add` after this has been run.
472	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 each child, need: name, ID, reference, mode
484					for (name, child_id) in children { // On disk children need to be sorted -- I think the library takes care of this
485						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) // TODO Populate the parent inode number (how?)
494					}, "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
555/// Tool to help create an archive from a directory in the filesystem.
556///
557/// This wraps a [`Writer`] and takes care of tracking the directory hierarchy as files are added,
558/// populating the iterators of [`SourceData::Dir`]s as necessary.
559///
560/// To simply create a SquashFS file from a chosen directory, call [`process`](Self::process):
561///
562///     TreeProcessor::new("archive.sfs")?.process("/home/me/test")?;
563///
564/// For more control over the addition process -- for example, to exclude certain files, add
565/// extended attributes, ignore errors, or print files as they are added -- use
566/// [`iter`](Self::iter) to get an iterator over the directory tree, and then call
567/// [`add`](Self::add) on each `SourceFile` yielded after applying any desired transformations.
568/// After the iterator finishes, remember to call [`finish`](Self::finish).
569///
570///     let processor = TreeProcessor::new("archive.sfs")?;
571///     for mut entry in processor.iter("/home/me/test") {
572///         entry.content.mode = 0x1ff; // Set all nodes to be read/writable by anyone
573///         match processor.add(entry) {
574///             Ok(id) => println!("{}: {}", id, entry.path),
575///             Err(_) => println!("Failed adding {}", entry.path),
576///         }
577///     }
578///     processor.finish()?;
579///
580/// It is safe to process the tree using multiple threads, but it is *the caller's responsibility*
581/// to ensure that any out-of-order execution does not cause child nodes to be `add`ed after their
582/// parent directories.  If this happens, [`WriteOrder`](SquashfsError::WriteOrder) will be
583/// raised and the node will not be added.
584pub struct TreeProcessor {
585	writer: Mutex<Writer>,
586	childmap: Mutex<HashMap<PathBuf, ChildMapEntry>>,
587}
588
589impl TreeProcessor {
590	/// Create a new `TreeProcessor` for an output file.
591	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	/// Add a new file to the archive.
597	///
598	/// It is not recommended to call this on `SourceFile`s that were not yielded by `iter`.
599	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	/// Finish writing the archive.
633	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	/// Create an iterator over a directory tree, yielding them in a form suitable to pass to
659	/// `add`.
660	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	/// Add an entire directory tree to the archive, then finish it.
666	///
667	/// This is the most basic, bare-bones way to create a full archive from an existing directory
668	/// tree.  This offers no way to customize the archive or handle errors gracefully.
669	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
676/// An iterator yielding the nodes in a directory tree in a way suitable for archiving.
677///
678/// This is created by a [`TreeProcessor`] and the items yielded are intended to be
679/// [`add`](TreeProcessor::add)ed to it.
680pub 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}