Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
Signed-off-by: Colin Walters <[email protected]>
  • Loading branch information
cgwalters committed Dec 5, 2023
1 parent 0415f12 commit db9f589
Show file tree
Hide file tree
Showing 4 changed files with 329 additions and 1 deletion.
321 changes: 321 additions & 0 deletions lib/src/composefs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,321 @@
//! # Creating composefs images
//!
//! This code wraps `mkcomposefs` from the composefs project.
use std::ffi::OsString;
use std::fmt::Display;
use std::fmt::Write as WriteFmt;
use std::io::Write;
use std::os::unix::ffi::{OsStrExt, OsStringExt};
use std::path::{Path, PathBuf};
use std::str::FromStr;

use anyhow::{anyhow, Context, Result};

struct Xattr {
key: OsString,
value: Vec<u8>,
}
type Xattrs = Vec<Xattr>;

struct Mtime {
sec: u64,
nsec: u64,
}

struct Entry {
path: PathBuf,
uid: u32,
gid: u32,
mode: u32,
mtime: Mtime,
item: Item,
xattrs: Xattrs,
}

enum Item {
Regular {
size: u64,
nlink: u32,
inline_content: Option<Vec<u8>>,
fsverity_digest: String,
},
Device {
nlink: u32,
rdev: u32,
},
Symlink {
nlink: u32,
target: PathBuf,
},
Hardlink {
target: PathBuf,
},
Directory {},
}

fn unescape(s: &str) -> Result<Vec<u8>> {
let mut it = s.chars();
let mut r = Vec::new();
while let Some(c) = it.next() {
if c != '\\' {
write!(r, "{c}").unwrap();
continue;
}
let c = it.next().ok_or_else(|| anyhow!("Unterminated escape"))?;
let c = match c {
'\\' => '\\' as u8,
'n' => '\n' as u8,
'r' => '\r' as u8,
't' => '\t' as u8,
'x' => {
let mut s = String::new();
s.push(
it.next()
.ok_or_else(|| anyhow!("Unterminated hex escape"))?,
);
s.push(
it.next()
.ok_or_else(|| anyhow!("Unterminated hex escape"))?,
);
let v = u8::from_str_radix(&s, 16)?;
v
}
o => anyhow::bail!("Invalid escape {o}"),
};
}
Ok(r)
}

fn escape(s: &[u8]) -> String {
let mut r = String::new();
for c in s.iter().copied() {
if c != b'\\' && (c.is_ascii_alphanumeric() || c.is_ascii_punctuation()) {
r.push(c as char);
} else {
match c {
b'\\' => r.push_str(r"\\"),
b'\n' => r.push_str(r"\n"),
b'\t' => r.push_str(r"\t"),
o => {
write!(r, "\\x{:x}", o).unwrap();
}
}
}
}
r
}

fn optional_str(s: &str) -> Option<&str> {
match s {
"-" => None,
o => Some(o),
}
}

impl FromStr for Mtime {
type Err = anyhow::Error;

fn from_str(s: &str) -> Result<Self> {
let (sec, nsec) = s
.split_once('.')
.ok_or_else(|| anyhow!("Missing . in mtime"))?;
Ok(Self {
sec: u64::from_str(sec)?,
nsec: u64::from_str(nsec)?,
})
}
}

impl FromStr for Xattr {
type Err = anyhow::Error;

fn from_str(s: &str) -> Result<Self> {
let (key, value) = s
.split_once('=')
.ok_or_else(|| anyhow!("Missing = in xattrs"))?;
let key = OsString::from_vec(unescape(key)?);
let value = unescape(value)?;
Ok(Self { key, value })
}
}

impl std::str::FromStr for Entry {
type Err = anyhow::Error;

fn from_str(s: &str) -> Result<Self> {
let mut components = s.split(' ');
let mut next = |name: &str| components.next().ok_or_else(|| anyhow!("Missing {name}"));
let path = next("path")?;
let path: PathBuf = OsString::from_vec(unescape(path)?).into();
let size = u64::from_str(next("size")?)?;
let modeval = next("mode")?;
let (is_hardlink, mode) = if let Some((_, rest)) = modeval.split_once('@') {
(true, u32::from_str_radix(rest, 8)?)
} else {
(false, u32::from_str_radix(modeval, 8)?)
};
let nlink = u32::from_str(next("nlink")?)?;
let uid = u32::from_str(next("uid")?)?;
let gid = u32::from_str(next("gid")?)?;
let rdev = u32::from_str(next("rdev")?)?;
let mtime = Mtime::from_str(next("mtime")?)?;
let payload = optional_str(next("payload")?);
let content = optional_str(next("content")?);
let digest = optional_str(next("digest")?);
let xattrs = components
.map(Xattr::from_str)
.collect::<Result<Vec<_>>>()?;

let item = if is_hardlink {
let target = OsString::from_vec(unescape(
payload.ok_or_else(|| anyhow!("Missing payload"))?,
)?)
.into();
Item::Hardlink { target }
} else {
match libc::S_IFMT & mode {
libc::S_IFREG => {
let fsverity_digest =
digest.ok_or_else(|| anyhow!("Missing digest"))?.to_owned();
Item::Regular {
size,
nlink,
inline_content: content.map(unescape).transpose()?,
fsverity_digest,
}
}
libc::S_IFLNK => {
let target = OsString::from_vec(unescape(
payload.ok_or_else(|| anyhow!("Missing payload"))?,
)?)
.into();
Item::Symlink { nlink, target }
}
libc::S_IFCHR | libc::S_IFBLK => Item::Device { nlink, rdev },
libc::S_IFDIR => Item::Directory {},
o => {
anyhow::bail!("Unhandled mode {o:o}")
}
}
};
Ok(Entry {
path,
uid,
gid,
mode,
mtime,
item,
xattrs,
})
}
}

impl Item {
pub(crate) fn size(&self) -> u64 {
match self {
Item::Regular { size, .. } => *size,
o => 0,
}
}

pub(crate) fn nlink(&self) -> u32 {
match self {
Item::Regular { nlink, .. } => *nlink,
Item::Device { nlink, .. } => *nlink,
Item::Symlink { nlink, .. } => *nlink,
_ => 0,
}
}

pub(crate) fn rdev(&self) -> u32 {
match self {
Item::Device { rdev, .. } => *rdev,
_ => 0,
}
}

pub(crate) fn payload(&self) -> Option<&Path> {
match self {
Item::Symlink { target, .. } => Some(target),
Item::Hardlink { target } => Some(target),
_ => None,
}
}

pub(crate) fn content(&self) -> Option<&[u8]> {
match self {
Item::Regular { inline_content, .. } => inline_content.as_deref(),
_ => None,
}
}
}

impl Display for Mtime {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}.{}", self.sec, self.nsec)
}
}

impl Display for Entry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", escape(self.path.as_os_str().as_bytes()))?;
write!(
f,
" {} {:o} {} {} {} {} {} ",
self.item.size(),
self.mode,
self.item.nlink(),
self.uid,
self.gid,
self.item.rdev(),
self.mtime,
)?;
if let Some(payload) = self.item.payload() {
write!(f, "{payload:?} ")?;
} else {
write!(f, "- ")?;
}
match &self.item {
Item::Regular {
fsverity_digest,
inline_content,
..
} => {
if let Some(content) = inline_content {
let escaped = escape(&content);
write!(f, "{escaped} ")?;
} else {
write!(f, "-")?;
}
write!(f, "{fsverity_digest} ")?;
}
_ => {
write!(f, "- -")?;
}
}
for xattr in self.xattrs.iter() {
write!(
f,
" {}={}",
escape(xattr.key.as_bytes()),
escape(&xattr.value)
)?;
}
std::fmt::Result::Ok(())
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_parse() {
const CONTENT: &str = include_str!("fixtures/composefs-example.txt");
for line in CONTENT.lines() {
let e = Entry::from_str(line).unwrap();
println!("{e}");
}
}
}
6 changes: 6 additions & 0 deletions lib/src/fixtures/composefs-example.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/ 4096 40755 4 1000 1000 0 1695372970.944925700 ‐ ‐ ‐ security.selinux=unconfined_u:object_r:unlabeled_t:s0\x00
/a\x20dir\x20w\x20space 27 40755 2 1000 1000 0 1694598852.869646118 ‐ ‐ ‐ security.selinux=unconfined_u:object_r:unlabeled_t:s0\x00
/a‐dir 45 40755 2 1000 1000 0 1674041780.601887980 ‐ ‐ ‐ security.selinux=unconfined_u:object_r:unlabeled_t:s0\x00
/a‐dir/a‐file 259 100644 1 1000 1000 0 1695368732.385062094 35/d02f81325122d77ec1d11baba655bc9bf8a891ab26119a41c50fa03ddfb408 ‐ 35d02f81325122d77ec1d11baba655bc9bf8a891ab26119a41c50fa03ddfb408 security.selinux=unconfined_u:object_r:unlabeled_t:s0\x00
/a‐hardlink 259 @100644 1 1000 1000 0 1695368732.385062094 /a‐dir/a‐file ‐ 35d02f81325122d77ec1d11baba655bc9bf8a891ab26119a41c50fa03ddfb408 security.selinux=unconfined_u:object_r:unlabeled_t:s0\x00
/inline.txt 10 100644 1 1000 1000 0 1697019909.446146440 ‐ some‐text\n ‐ security.selinux=unconfined_u:object_r:unlabeled_t:s0\x00
1 change: 1 addition & 0 deletions lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ mod lsm;
mod ostree_authfile;
mod podman;
mod podman_ostree;
mod composefs;
mod reboot;
mod reexec;
mod status;
Expand Down
2 changes: 1 addition & 1 deletion lib/src/podman_ostree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ pub(crate) fn prepare_squashed_root(rootfs: &Dir) -> Result<()> {
let name = ent.file_name();
var.rename(&name, factory_var, &name)
.with_context(|| format!("Moving var/{name:?} to {factory_var_path}"))?;
}
}
}
Ok(())
}
Expand Down

0 comments on commit db9f589

Please sign in to comment.