rufs/ufs/
mod.rs

1use std::{
2	ffi::{OsStr, OsString},
3	fs::File,
4	io::{Cursor, Error as IoError, ErrorKind, Read, Result as IoResult, Seek, SeekFrom},
5	mem::size_of,
6	num::NonZeroU64,
7	os::unix::ffi::{OsStrExt, OsStringExt},
8	path::Path,
9};
10
11mod balloc;
12mod dir;
13mod ialloc;
14mod inode;
15mod symlink;
16mod xattr;
17
18use crate::{
19	blockreader::{Backend, BlockReader},
20	data::*,
21	decoder::{Config, Decoder},
22};
23
24/// (INTERNAL) Constructs an [`std::io::Error`] from an `errno`.
25#[macro_export]
26macro_rules! err {
27	($name:ident) => {
28		IoError::from_raw_os_error(libc::$name)
29	};
30}
31
32macro_rules! iobail {
33	($kind:expr, $($tk:tt)+) => {
34		return Err(IoError::new($kind, format!($($tk)+)))
35	};
36}
37
38/// Summary of filesystem statistics.
39#[derive(Debug, Clone)]
40#[doc(alias = "Statfs")]
41pub struct Info {
42	/// Number of blocks.
43	pub blocks: u64,
44
45	/// Number of free blocks.
46	pub bfree: u64,
47
48	/// Number of inodes (files).
49	pub files: u64,
50
51	/// Number of free inodes (files).
52	pub ffree: u64,
53
54	/// Block size.
55	pub bsize: u32,
56
57	/// Fragment size.
58	pub fsize: u32,
59}
60
61/// Berkley Unix (Fast) Filesystem v2
62pub struct Ufs<R: Backend> {
63	file:       Decoder<BlockReader<R>>,
64	superblock: Superblock,
65}
66
67impl Ufs<File> {
68	pub fn open(path: &Path, rw: bool) -> IoResult<Self> {
69		let file = BlockReader::open(path, rw)?;
70		Self::new(file)
71	}
72}
73
74impl<R: Backend> Ufs<R> {
75	pub fn new(mut file: BlockReader<R>) -> IoResult<Self> {
76		let pos = SBLOCK_UFS2 as u64 + MAGIC_OFFSET;
77		file.seek(SeekFrom::Start(pos))?;
78		let mut magic = [0u8; 4];
79		file.read_exact(&mut magic)?;
80
81		// magic: 0x19 54 01 19
82		let config = match magic {
83			[0x19, 0x01, 0x54, 0x19] => Config::little(),
84			[0x19, 0x54, 0x01, 0x19] => Config::big(),
85			_ => {
86				iobail!(
87					ErrorKind::InvalidInput,
88					"invalid superblock magic number: {magic:?}"
89				)
90			}
91		};
92		// FIXME: Choose based on hash of input or so, to excercise BE as well with introducing non-determinism
93
94		let mut file = Decoder::new(file, config);
95
96		let superblock: Superblock = file.decode_at(SBLOCK_UFS2 as u64)?;
97		if superblock.magic != FS_UFS2_MAGIC {
98			iobail!(
99				ErrorKind::InvalidInput,
100				"invalid superblock magic number: {}",
101				superblock.magic
102			);
103		}
104		let mut s = Self { file, superblock };
105		s.check()?;
106		Ok(s)
107	}
108
109	pub fn write_enabled(&self) -> bool {
110		self.file.inner().write_enabled()
111	}
112
113	fn assert_rw(&self) -> IoResult<()> {
114		if self.write_enabled() {
115			Ok(())
116		} else {
117			Err(err!(EROFS))
118		}
119	}
120
121	/// Get filesystem metadata.
122	#[doc(alias("statfs", "statvfs"))]
123	pub fn info(&self) -> Info {
124		let sb = &self.superblock;
125		let cst = &sb.cstotal;
126		Info {
127			blocks: sb.dsize as u64,
128			bfree:  (cst.nbfree * sb.frag as i64 + cst.nffree) as u64,
129			files:  (sb.ipg * sb.ncg) as u64,
130			ffree:  cst.nifree as u64,
131			bsize:  sb.bsize as u32,
132			fsize:  sb.fsize as u32,
133		}
134	}
135
136	fn check(&mut self) -> IoResult<()> {
137		let sb = &self.superblock;
138		log::debug!("Superblock: {sb:#?}");
139
140		log::info!("Summary:");
141		log::info!("Block Size: {}", sb.bsize);
142		log::info!("# Blocks: {}", sb.size);
143		log::info!("# Data Blocks: {}", sb.dsize);
144		log::info!("Fragment Size: {}", sb.fsize);
145		log::info!("Fragments per Block: {}", sb.frag);
146		log::info!("# Cylinder Groups: {}", sb.ncg);
147		log::info!("CG Size: {}MiB", sb.cgsize() / 1024 / 1024);
148
149		macro_rules! sbassert {
150			($e:expr) => {
151				if !($e) {
152					log::error!("superblock corrupted: {}", stringify!($e));
153					return Err(IoError::from_raw_os_error(libc::EIO));
154				}
155			};
156		}
157
158		sbassert!(sb.ncg > 0);
159		sbassert!(sb.ipg > 0);
160		sbassert!(sb.fpg > 0);
161		sbassert!(sb.frag > 0 && sb.frag <= 8);
162		sbassert!(sb.fsize == (sb.bsize / sb.frag));
163		// TODO: this looks ugly:
164		sbassert!(Some(sb.bsize) == 1i32.checked_shl(sb.bshift as u32));
165		sbassert!(Some(sb.fsize) == 1i32.checked_shl(sb.fshift as u32));
166		sbassert!(Some(sb.frag) == 1i32.checked_shl(sb.fragshift as u32));
167		sbassert!(sb.bsize == (!sb.bmask + 1));
168		sbassert!(sb.fsize == (!sb.fmask + 1));
169		sbassert!(sb.sbsize == sb.fsize);
170		sbassert!(sb.cgsize_struct() < sb.bsize as usize);
171
172		let fpg = sb.fpg as u64;
173		let sblkno = sb.sblkno as u64;
174		let fs = sb.fsize as u64;
175
176		// check that all superblocks are ok.
177		for i in 0..sb.ncg {
178			let addr = (i as u64 * fpg + sblkno) * fs;
179			let csb: Superblock = self.file.decode_at(addr).unwrap();
180			if csb.magic != FS_UFS2_MAGIC {
181				log::error!("CG{i} has invalid superblock magic: {:x}", csb.magic);
182				return Err(err!(EIO));
183			}
184		}
185
186		// check that all cylgroups are ok.
187		for i in 0..self.superblock.ncg {
188			let addr = self.cg_addr(i as u64);
189			let cg: CylGroup = self.file.decode_at(addr).unwrap();
190			if cg.magic != CG_MAGIC {
191				log::error!("CG{i} has invalid cg magic: {:x}", cg.magic);
192				return Err(err!(EIO));
193			}
194		}
195		log::info!("OK");
196		Ok(())
197	}
198
199	fn cg_addr(&self, idx: u64) -> u64 {
200		let sb = &self.superblock;
201		let fpg = sb.fpg as u64;
202		let cblkno = sb.cblkno as u64;
203		let fs = sb.fsize as u64;
204
205		(idx * fpg + cblkno) * fs
206	}
207
208	fn update_sb(&mut self, f: impl FnOnce(&mut Superblock)) -> IoResult<()> {
209		// Only update the first superblock, because we're lazy.
210		f(&mut self.superblock);
211		self.file.encode_at(SBLOCK_UFS2 as u64, &self.superblock)?;
212		Ok(())
213	}
214}
215
216fn check_name_is_legal(name: &OsStr, allow_special: bool) -> IoResult<()> {
217	let b = name.as_encoded_bytes();
218
219	let x = b.contains(&b'/') ||
220		(name == "." && !allow_special) ||
221		(name == ".." && !allow_special) ||
222		b.contains(&b'\0');
223
224	if x {
225		Err(err!(EINVAL))
226	} else {
227		Ok(())
228	}
229}