Skip to content

Commit 67d1f35

Browse files
cfsctl: Add StatFile command
Change the previous `GetFileObjectRef` to `StatFile` which now also outputs file metadata along with what type of file it is, eg. regular file, symlink, socket etc Signed-off-by: Pragyan Poudyal <[email protected]>
1 parent 08caf94 commit 67d1f35

6 files changed

Lines changed: 74 additions & 29 deletions

File tree

crates/cfsctl/src/main.rs

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use composefs::{
2424
fsverity::{FsVerityHashValue, Sha256HashValue, Sha512HashValue},
2525
generic_tree::FileSystem,
2626
repository::Repository,
27-
tree::RegularFile,
27+
tree::{Leaf, LeafContent, RegularFile, Stat},
2828
};
2929
use serde::Serialize;
3030

@@ -172,7 +172,7 @@ enum OciCommand {
172172
},
173173
/// Given a file path, get the file corresponding to it in the object store
174174
/// If the file if stored inline, returns an empty string
175-
GetFileObjectRef {
175+
StatFile {
176176
#[clap(flatten)]
177177
config_opts: OCIConfigFilesystemOptions,
178178
/// The absolute path of the file to be found
@@ -275,7 +275,7 @@ enum Command {
275275
},
276276
/// Given a file path, get the file corresponding to it in the object store
277277
/// If the file if stored inline, returns an empty string
278-
GetFileObjectRef {
278+
StatFile {
279279
#[clap(flatten)]
280280
fs_opts: FsReadOptions,
281281
/// The absolute path of the file to be found
@@ -299,13 +299,39 @@ enum Command {
299299

300300
/// The return object of GetFileObjectRef cmd
301301
#[derive(Debug, Serialize)]
302-
pub struct ObjectRef {
302+
pub struct ObjectRef<T: FsVerityHashValue> {
303303
/// Whether the file is stored inline in the EROFS image
304304
pub stored_inline: bool,
305305
/// FsVerity digest of the file
306306
pub digest: Option<String>,
307307
/// Path to the file relative to the object store
308308
pub path: Option<String>,
309+
/// File metadata
310+
pub stat: Stat,
311+
/// Whether the file is a regular file, symlink, etc.
312+
pub kind: LeafContent<T>,
313+
}
314+
315+
impl<T: FsVerityHashValue> From<&Leaf<T>> for ObjectRef<T> {
316+
fn from(leaf: &Leaf<T>) -> Self {
317+
let (stored_inline, digest, path) = match &leaf.content {
318+
LeafContent::Regular(file) => match file {
319+
RegularFile::Inline(..) => (true, None, None),
320+
RegularFile::External(id, _) => {
321+
(false, Some(id.to_hex()), Some(id.to_object_pathname()))
322+
}
323+
},
324+
_ => (true, None, None),
325+
};
326+
327+
Self {
328+
stored_inline,
329+
digest,
330+
path,
331+
stat: leaf.stat.clone(),
332+
kind: leaf.content.clone(),
333+
}
334+
}
309335
}
310336

311337
fn verity_opt<ObjectID>(opt: &Option<String>) -> Result<Option<ObjectID>>
@@ -385,26 +411,10 @@ fn load_filesystem_from_ondisk_fs<ObjectID: FsVerityHashValue>(
385411
fn get_obj_ref_for_filesystem<ObjectID: FsVerityHashValue>(
386412
fs: &FileSystem<RegularFile<ObjectID>>,
387413
file_path: &OsStr,
388-
) -> Result<ObjectRef> {
414+
) -> Result<ObjectRef<ObjectID>> {
389415
let (dir, file) = fs.root.split(file_path)?;
390-
391-
let obj_ref = dir.get_file(file)?;
392-
393-
let obj_ref = match obj_ref {
394-
RegularFile::Inline(..) => ObjectRef {
395-
stored_inline: true,
396-
digest: None,
397-
path: None,
398-
},
399-
400-
RegularFile::External(id, _) => ObjectRef {
401-
stored_inline: false,
402-
digest: Some(id.to_hex()),
403-
path: Some(id.to_object_pathname()),
404-
},
405-
};
406-
407-
Ok(obj_ref)
416+
let leaf = dir.ref_leaf(file)?;
417+
Ok(ObjectRef::from(leaf.as_ref()))
408418
}
409419

410420
async fn run_cmd_with_repo<ObjectID>(repo: Repository<ObjectID>, args: App) -> Result<()>
@@ -443,7 +453,7 @@ where
443453
let fs = load_filesystem_from_oci_image(&repo, config_opts)?;
444454
fs.print_dumpfile()?;
445455
}
446-
OciCommand::GetFileObjectRef {
456+
OciCommand::StatFile {
447457
config_opts,
448458
file_path,
449459
} => {
@@ -644,7 +654,7 @@ where
644654
let id = fs.compute_image_id();
645655
println!("{}", id.to_hex());
646656
}
647-
Command::GetFileObjectRef { fs_opts, file_path } => {
657+
Command::StatFile { fs_opts, file_path } => {
648658
let fs = load_filesystem_from_ondisk_fs(&fs_opts, &repo)?;
649659
let obj_ref = get_obj_ref_for_filesystem(&fs, file_path.as_os_str())?;
650660
serde_json::to_writer(std::io::stdout().lock(), &obj_ref)?;

crates/composefs/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ xxhash-rust = { version = "0.8.2", default-features = false, features = ["xxh32"
3131
zerocopy = { version = "0.8.0", default-features = false, features = ["derive", "std"] }
3232
zstd = { version = "0.13.0", default-features = false }
3333
rand = { version = "0.9.1", default-features = true }
34+
serde = { version = "1.0", features = ["derive"] }
35+
serde_json = { version = "1.0", default-features = false, features = ["std"] }
3436

3537
[dev-dependencies]
3638
insta = "1.42.2"

crates/composefs/src/fsverity/hashvalue.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use core::{fmt, hash::Hash};
88

99
use hex::FromHexError;
10+
use serde::{Serialize, Serializer};
1011
use sha2::{digest::FixedOutputReset, digest::Output, Digest, Sha256, Sha512};
1112
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned};
1213

@@ -22,6 +23,7 @@ where
2223
Self: Hash + Eq,
2324
Self: fmt::Debug,
2425
Self: Send + Sync + Unpin + 'static,
26+
Self: Serialize,
2527
{
2628
/// The underlying hash digest algorithm type.
2729
type Digest: Digest + FixedOutputReset + fmt::Debug;
@@ -177,6 +179,15 @@ impl FsVerityHashValue for Sha256HashValue {
177179
const ID: &str = "sha256";
178180
}
179181

182+
impl Serialize for Sha256HashValue {
183+
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
184+
where
185+
S: Serializer,
186+
{
187+
s.serialize_str(&self.to_hex())
188+
}
189+
}
190+
180191
/// A SHA-512 hash value for fs-verity operations.
181192
///
182193
/// This is a 64-byte hash value using the SHA-512 algorithm.
@@ -190,6 +201,15 @@ impl From<Output<Sha512>> for Sha512HashValue {
190201
}
191202
}
192203

204+
impl Serialize for Sha512HashValue {
205+
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
206+
where
207+
S: Serializer,
208+
{
209+
s.serialize_str(&self.to_hex())
210+
}
211+
}
212+
193213
impl FsVerityHashValue for Sha512HashValue {
194214
type Digest = Sha512;
195215
const ALGORITHM: u8 = 2;

crates/composefs/src/generic_tree.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ use std::{
99
rc::Rc,
1010
};
1111

12+
use serde::Serialize;
1213
use thiserror::Error;
1314

1415
/// File metadata similar to `struct stat` from POSIX.
15-
#[derive(Debug)]
16+
#[derive(Debug, Clone, Serialize)]
1617
pub struct Stat {
1718
/// File mode and permissions bits.
1819
pub st_mode: u32,
@@ -47,7 +48,8 @@ impl Stat {
4748
}
4849

4950
/// Content types for leaf nodes (non-directory files).
50-
#[derive(Debug)]
51+
#[derive(Debug, Clone, Serialize)]
52+
#[serde(rename_all = "camelCase")]
5153
pub enum LeafContent<T> {
5254
/// A regular file with content of type `T`.
5355
Regular(T),
@@ -60,9 +62,17 @@ pub enum LeafContent<T> {
6062
/// A Unix domain socket.
6163
Socket,
6264
/// A symbolic link pointing to the given target path.
65+
#[serde(serialize_with = "serialize_osstr")]
6366
Symlink(Box<OsStr>),
6467
}
6568

69+
fn serialize_osstr<S>(v: &OsStr, s: S) -> Result<S::Ok, S::Error>
70+
where
71+
S: serde::Serializer,
72+
{
73+
s.serialize_str(v.to_string_lossy().as_ref())
74+
}
75+
6676
/// A leaf node representing a non-directory file.
6777
#[derive(Debug)]
6878
pub struct Leaf<T> {

crates/composefs/src/tree.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
//! of inlining small files, and having an external fsverity reference for
33
//! larger ones.
44
5+
use serde::Serialize;
6+
57
use crate::fsverity::FsVerityHashValue;
68

79
pub use crate::generic_tree::{self, ImageError, Stat};
@@ -10,7 +12,8 @@ pub use crate::generic_tree::{self, ImageError, Stat};
1012
///
1113
/// Files can be stored inline for small content or externally referenced
1214
/// for larger files using fsverity hashing.
13-
#[derive(Debug, Clone)]
15+
#[derive(Debug, Clone, Serialize)]
16+
#[serde(rename_all = "camelCase")]
1417
pub enum RegularFile<ObjectID: FsVerityHashValue> {
1518
/// File content stored inline as raw bytes.
1619
Inline(Box<[u8]>),

crates/integration-tests/src/tests/cli.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -504,7 +504,7 @@ fn test_get_file_obj_ref() -> Result<()> {
504504

505505
let out_json = cmd!(
506506
sh,
507-
"{cfsctl} --insecure --repo {repo} get-file-object-ref {rootfs} --file-path usr/lib/readme.txt"
507+
"{cfsctl} --insecure --repo {repo} stat-file {rootfs} --file-path usr/lib/readme.txt"
508508
)
509509
.read()?;
510510

0 commit comments

Comments
 (0)