Skip to content

Commit b0acd44

Browse files
tree: change approach to mtime of root directory
Instead of hardcoding a bunch of weird values for various parameters in the root directory of the filesystem, handle the presence of an explicit 'stat' value more explicitly by keeping a separate boolean. This fixes one of our testcases: in case of an empty filesystem the sentinel value was slipping through which means we'd end up with an unusual mtime on our root directory of -1, which showed up in the test snapshots as 18446744073709551615 (0xffffffffffffffff). Signed-off-by: Allison Karlitskaya <[email protected]>
1 parent c1f3b32 commit b0acd44

File tree

6 files changed

+56
-53
lines changed

6 files changed

+56
-53
lines changed

src/fs.rs

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use std::{
22
cell::RefCell,
3-
cmp::max,
43
collections::{BTreeMap, HashMap},
54
ffi::{CStr, OsStr},
65
fs::File,
@@ -145,7 +144,6 @@ pub fn write_to_path<ObjectID: FsVerityHashValue>(
145144
pub struct FilesystemReader<'repo, ObjectID: FsVerityHashValue> {
146145
repo: Option<&'repo Repository<ObjectID>>,
147146
inodes: HashMap<(u64, u64), Rc<Leaf<ObjectID>>>,
148-
root_mtime: Option<i64>,
149147
}
150148

151149
impl<ObjectID: FsVerityHashValue> FilesystemReader<'_, ObjectID> {
@@ -269,6 +267,7 @@ impl<ObjectID: FsVerityHashValue> FilesystemReader<'_, ObjectID> {
269267
&mut self,
270268
dirfd: impl AsFd,
271269
name: &OsStr,
270+
stat_self: bool,
272271
) -> Result<Directory<ObjectID>> {
273272
let fd = openat(
274273
dirfd,
@@ -277,8 +276,12 @@ impl<ObjectID: FsVerityHashValue> FilesystemReader<'_, ObjectID> {
277276
Mode::empty(),
278277
)?;
279278

280-
let (_, stat) = Self::stat(&fd, FileType::Directory)?;
281-
let mut directory = Directory::new(stat);
279+
let mut directory = if stat_self {
280+
let (_, stat) = Self::stat(&fd, FileType::Directory)?;
281+
Directory::new(stat)
282+
} else {
283+
Directory::default()
284+
};
282285

283286
for item in Dir::read_from(&fd)? {
284287
let entry = item?;
@@ -289,7 +292,6 @@ impl<ObjectID: FsVerityHashValue> FilesystemReader<'_, ObjectID> {
289292
}
290293

291294
let inode = self.read_inode(&fd, name, entry.file_type())?;
292-
self.root_mtime = max(self.root_mtime, Some(inode.stat().st_mtim_sec));
293295
directory.insert(name, inode);
294296
}
295297

@@ -303,7 +305,7 @@ impl<ObjectID: FsVerityHashValue> FilesystemReader<'_, ObjectID> {
303305
ifmt: FileType,
304306
) -> Result<Inode<ObjectID>> {
305307
if ifmt == FileType::Directory {
306-
let dir = self.read_directory(dirfd, name)?;
308+
let dir = self.read_directory(dirfd, name, true)?;
307309
Ok(Inode::Directory(Box::new(dir)))
308310
} else {
309311
let leaf = self.read_leaf(dirfd, name, ifmt)?;
@@ -315,19 +317,20 @@ impl<ObjectID: FsVerityHashValue> FilesystemReader<'_, ObjectID> {
315317
pub fn read_from_path<ObjectID: FsVerityHashValue>(
316318
path: &Path,
317319
repo: Option<&Repository<ObjectID>>,
320+
stat_root: bool,
318321
) -> Result<FileSystem<ObjectID>> {
319322
let mut reader = FilesystemReader {
320323
repo,
321324
inodes: HashMap::new(),
322-
root_mtime: None,
323325
};
326+
327+
let root = reader.read_directory(CWD, path.as_os_str(), stat_root)?;
328+
324329
let mut fs = FileSystem {
325-
root: reader.read_directory(CWD, path.as_os_str())?,
330+
root,
331+
have_root_stat: stat_root,
326332
};
327333

328-
// A filesystem with no files ends up in the 1970s...
329-
fs.root.stat.st_mtim_sec = reader.root_mtime.unwrap_or(0);
330-
331334
// We can only relabel if we have the repo because we need to read the config and policy files
332335
if let Some(repo) = repo {
333336
selabel(&mut fs, repo)?;
@@ -340,7 +343,7 @@ pub fn create_image<ObjectID: FsVerityHashValue>(
340343
path: &Path,
341344
repo: Option<&Repository<ObjectID>>,
342345
) -> Result<ObjectID> {
343-
let fs = read_from_path(path, repo)?;
346+
let fs = read_from_path(path, repo, false)?;
344347
let image = crate::erofs::writer::mkfs_erofs(&fs);
345348
if let Some(repo) = repo {
346349
Ok(repo.write_image(None, &image)?)
@@ -350,7 +353,7 @@ pub fn create_image<ObjectID: FsVerityHashValue>(
350353
}
351354

352355
pub fn create_dumpfile<ObjectID: FsVerityHashValue>(path: &Path) -> Result<()> {
353-
let fs = read_from_path::<ObjectID>(path, None)?;
356+
let fs = read_from_path::<ObjectID>(path, None, false)?;
354357
super::dumpfile::write_dumpfile(&mut std::io::stdout(), &fs)
355358
}
356359

src/oci/image.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ pub fn compose_filesystem<ObjectID: FsVerityHashValue>(
5353
repo: &Repository<ObjectID>,
5454
layers: &[String],
5555
) -> Result<FileSystem<ObjectID>> {
56-
let mut filesystem = FileSystem::<ObjectID>::new();
56+
let mut filesystem = FileSystem::default();
5757

5858
for layer in layers {
5959
let mut split_stream = repo.open_stream(layer, None)?;
@@ -63,7 +63,7 @@ pub fn compose_filesystem<ObjectID: FsVerityHashValue>(
6363
}
6464

6565
selabel(&mut filesystem, repo)?;
66-
filesystem.done();
66+
filesystem.ensure_root_stat();
6767

6868
Ok(filesystem)
6969
}
@@ -81,7 +81,7 @@ pub fn create_image<ObjectID: FsVerityHashValue>(
8181
name: Option<&str>,
8282
verity: Option<&ObjectID>,
8383
) -> Result<ObjectID> {
84-
let mut filesystem = FileSystem::new();
84+
let mut filesystem = FileSystem::default();
8585

8686
let mut config_stream = repo.open_stream(config, verity)?;
8787
let config = ImageConfiguration::from_reader(&mut config_stream)?;
@@ -97,7 +97,7 @@ pub fn create_image<ObjectID: FsVerityHashValue>(
9797
}
9898

9999
selabel(&mut filesystem, repo)?;
100-
filesystem.done();
100+
filesystem.ensure_root_stat();
101101

102102
let erofs = mkfs_erofs(&filesystem);
103103
repo.write_image(name, &erofs)
@@ -155,7 +155,7 @@ mod test {
155155

156156
#[test]
157157
fn test_process_entry() -> Result<()> {
158-
let mut fs = FileSystem::<Sha256HashValue>::new();
158+
let mut fs = FileSystem::<Sha256HashValue>::default();
159159

160160
// both with and without leading slash should be supported
161161
process_entry(&mut fs, dir_entry("/a"))?;

src/oci/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ pub fn prepare_boot<ObjectID: FsVerityHashValue>(
391391
.with_context(|| "Attachments layer {layer} is not connected to image {name}")?;
392392

393393
// read the layer into a FileSystem object
394-
let mut filesystem = crate::tree::FileSystem::new();
394+
let mut filesystem = crate::tree::FileSystem::default();
395395
let mut split_stream = repo.open_stream(&hex::encode(layer_sha256), Some(layer_verity))?;
396396
while let Some(entry) = tar::get_entry(&mut split_stream)? {
397397
image::process_entry(&mut filesystem, entry)?;

src/tree.rs

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,22 @@ impl<ObjectID: FsVerityHashValue> Inode<ObjectID> {
7676
}
7777
}
7878

79+
// For some reason #[derive(Default)] doesn't work, so let's DIY
80+
impl<ObjectID: FsVerityHashValue> Default for Directory<ObjectID> {
81+
fn default() -> Self {
82+
Self {
83+
stat: Stat {
84+
st_uid: 0,
85+
st_gid: 0,
86+
st_mode: 0o555,
87+
st_mtim_sec: 0,
88+
xattrs: Default::default(),
89+
},
90+
entries: BTreeMap::default(),
91+
}
92+
}
93+
}
94+
7995
impl<ObjectID: FsVerityHashValue> Directory<ObjectID> {
8096
pub fn new(stat: Stat) -> Self {
8197
Self {
@@ -97,7 +113,7 @@ impl<ObjectID: FsVerityHashValue> Directory<ObjectID> {
97113
///
98114
/// ```
99115
/// use composefs::{tree::FileSystem, fsverity::Sha256HashValue};
100-
/// let fs = FileSystem::<Sha256HashValue>::new();
116+
/// let fs = FileSystem::<Sha256HashValue>::default();
101117
///
102118
/// // populate the fs...
103119
///
@@ -358,7 +374,7 @@ impl<ObjectID: FsVerityHashValue> Directory<ObjectID> {
358374
self.entries.clear();
359375
}
360376

361-
fn newest_file(&self) -> i64 {
377+
pub fn newest_file(&self) -> i64 {
362378
let mut newest = self.stat.st_mtim_sec;
363379
for inode in self.entries.values() {
364380
let mtime = match inode {
@@ -376,43 +392,28 @@ impl<ObjectID: FsVerityHashValue> Directory<ObjectID> {
376392
#[derive(Debug)]
377393
pub struct FileSystem<ObjectID: FsVerityHashValue> {
378394
pub root: Directory<ObjectID>,
395+
pub have_root_stat: bool,
379396
}
380397

381398
impl<ObjectID: FsVerityHashValue> Default for FileSystem<ObjectID> {
382399
fn default() -> Self {
383-
Self::new()
400+
Self {
401+
root: Directory::default(),
402+
have_root_stat: false,
403+
}
384404
}
385405
}
386406

387407
impl<ObjectID: FsVerityHashValue> FileSystem<ObjectID> {
388-
pub fn new() -> Self {
389-
Self {
390-
root: Directory::new(Stat {
391-
st_mode: u32::MAX, // assigned later
392-
st_uid: u32::MAX, // assigned later
393-
st_gid: u32::MAX, // assigned later
394-
st_mtim_sec: -1, // assigned later
395-
xattrs: RefCell::new(BTreeMap::new()),
396-
}),
397-
}
408+
pub fn set_root_stat(&mut self, stat: Stat) {
409+
self.have_root_stat = true;
410+
self.root.stat = stat;
398411
}
399412

400-
pub fn done(&mut self) {
401-
// We need to look at the root entry and deal with the "assign later" fields
402-
let stat = &mut self.root.stat;
403-
404-
if stat.st_mode == u32::MAX {
405-
stat.st_mode = 0o555;
406-
}
407-
if stat.st_uid == u32::MAX {
408-
stat.st_uid = 0;
409-
}
410-
if stat.st_gid == u32::MAX {
411-
stat.st_gid = 0;
412-
}
413-
if stat.st_mtim_sec == -1 {
414-
// write this in full to avoid annoying the borrow checker
413+
pub fn ensure_root_stat(&mut self) {
414+
if !self.have_root_stat {
415415
self.root.stat.st_mtim_sec = self.root.newest_file();
416+
self.have_root_stat = true;
416417
}
417418
}
418419
}

tests/mkfs.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use composefs::{
1818
};
1919

2020
fn debug_fs(mut fs: FileSystem<impl FsVerityHashValue>) -> String {
21-
fs.done();
21+
fs.ensure_root_stat();
2222
let image = mkfs_erofs(&fs);
2323
let mut output = vec![];
2424
debug_img(&mut output, &image).unwrap();
@@ -29,7 +29,7 @@ fn empty(_fs: &mut FileSystem<impl FsVerityHashValue>) {}
2929

3030
#[test]
3131
fn test_empty() {
32-
let mut fs = FileSystem::<Sha256HashValue>::new();
32+
let mut fs = FileSystem::<Sha256HashValue>::default();
3333
empty(&mut fs);
3434
insta::assert_snapshot!(debug_fs(fs));
3535
}
@@ -82,16 +82,16 @@ fn simple(fs: &mut FileSystem<Sha256HashValue>) {
8282

8383
#[test]
8484
fn test_simple() {
85-
let mut fs = FileSystem::<Sha256HashValue>::new();
85+
let mut fs = FileSystem::<Sha256HashValue>::default();
8686
simple(&mut fs);
8787
insta::assert_snapshot!(debug_fs(fs));
8888
}
8989

9090
fn foreach_case(f: fn(&FileSystem<Sha256HashValue>)) {
9191
for case in [empty, simple] {
92-
let mut fs = FileSystem::new();
92+
let mut fs = FileSystem::default();
9393
case(&mut fs);
94-
fs.done();
94+
fs.ensure_root_stat();
9595
f(&fs);
9696
}
9797
}

tests/snapshots/mkfs__empty.snap

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ snapshot_kind: text
2626
+4 mode: 0040555 (directory)
2727
+8 size: U64(27)
2828
+14 ino: U32(36)
29-
+20 mtime: U64(18446744073709551615)
3029
+2c nlink: U32(2)
3130
+40 --- inline directory entries ---
3231
+0 inode_offset: U64(36)

0 commit comments

Comments
 (0)