vfs/user: implement chmod utility

This commit is contained in:
Mark Poliakov 2025-03-05 13:10:51 +02:00
parent e330db1e55
commit c35a61fb7f
12 changed files with 235 additions and 33 deletions

View File

@ -3,7 +3,10 @@ use core::mem::MaybeUninit;
use libk_util::ext::OptionExt;
use yggdrasil_abi::{
error::Error,
io::{DirectoryEntry, FileMode, GroupId, OpenOptions, UserId},
io::{
DirectoryEntry, FileMetadataUpdate, FileMetadataUpdateMode, FileMode, GroupId, OpenOptions,
UserId,
},
};
use crate::vfs::{
@ -263,6 +266,34 @@ impl Node {
Ok(*metadata)
}
pub fn update_metadata(self: &NodeRef, update: &FileMetadataUpdate) -> Result<(), Error> {
let FileMetadataUpdate::Permissions(mode) = update else {
return Err(Error::NotImplemented);
};
let mut cache = self.props.lock();
let common = self.data_as_common();
let metadata = cache
.metadata
.get_or_try_insert_with(|| common.metadata(self))?;
match *mode {
FileMetadataUpdateMode::Set(value) => metadata.mode |= value,
FileMetadataUpdateMode::Modify { set, clear } => {
metadata.mode &= !clear;
metadata.mode |= set;
}
}
if !self.flags.contains(NodeFlags::IN_MEMORY_PROPS) {
// Update permissions in the real node
common.set_metadata(self, metadata)?;
}
Ok(())
}
// TODO clarify directory size
/// Returns the size in bytes of the node
pub fn size(self: &NodeRef) -> Result<u64, Error> {

View File

@ -235,14 +235,22 @@ pub(crate) fn clone_fd(source_fd: RawFd, target_fd: Option<RawFd>) -> Result<Raw
pub(crate) fn update_metadata(
at: Option<RawFd>,
_path: &str,
_update: &FileMetadataUpdate,
path: &str,
update: &FileMetadataUpdate,
) -> Result<(), Error> {
let thread = Thread::current();
let process = thread.process();
run_with_io_at(&process, at, |_at, _io| {
todo!();
run_with_io_at(&process, at, |at, mut io| {
let node = if path.is_empty() {
at
} else {
io.ioctx_mut().find(Some(at), path, false)?
};
node.update_metadata(update)?;
Ok(())
})
}

View File

@ -9,14 +9,15 @@ use super::FileMode;
/// Describes an operation to perform when updating certain file metadata elements
#[derive(Debug, Clone)]
pub enum FileMetadataUpdateMode {
/// value = new
Set,
/// value = old | new
Or,
/// value = old & new
And,
/// value = old & !new
AndNot,
/// Assign a new file mode
Set(FileMode),
/// Set and/or clear existing file mode bits
Modify {
/// Bits to set
set: FileMode,
/// Bits to clear
clear: FileMode,
},
}
/// Describes how file times are updated
@ -34,7 +35,7 @@ pub struct FileTimesUpdate {
#[derive(Debug, Clone)]
pub enum FileMetadataUpdate {
/// Changes file permissions
Permissions(FileMode, FileMetadataUpdateMode),
Permissions(FileMetadataUpdateMode),
/// Changes file mtime/atime/ctime
Times(FileTimesUpdate),
}

1
userspace/Cargo.lock generated
View File

@ -536,6 +536,7 @@ checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
name = "cross"
version = "0.1.0"
dependencies = [
"bitflags 2.8.0",
"libc",
"runtime",
"tempfile",

View File

@ -31,6 +31,7 @@ clap = { version = "4.5.20", features = ["std", "derive", "help", "usage"], defa
clap-num = "1.1.1"
serde_json = "1.0.132"
serde = { version = "1.0.214", features = ["derive"] }
bitflags = "2.6.0"
bytemuck = "1.19.0"
thiserror = "1.0.64"
env_logger = "0.11.5"

View File

@ -4,6 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
bitflags.workspace = true
[target.'cfg(target_os = "yggdrasil")'.dependencies]
yggdrasil-rt.workspace = true

View File

@ -0,0 +1,46 @@
use std::{fmt, io, os::fd::RawFd, path::Path};
use bitflags::bitflags;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FileModeUpdate {
Set(FileMode),
Modify { set: FileMode, clear: FileMode },
}
bitflags! {
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct FileMode: u32 {
const OTHER_EXEC = 1 << 0;
const OTHER_WRITE = 1 << 1;
const OTHER_READ = 1 << 2;
const GROUP_EXEC = 1 << 3;
const GROUP_WRITE = 1 << 4;
const GROUP_READ = 1 << 5;
const USER_EXEC = 1 << 6;
const USER_WRITE = 1 << 7;
const USER_READ = 1 << 8;
}
}
impl FileMode {
const MASK: u32 = 0o777;
}
impl From<u32> for FileMode {
fn from(value: u32) -> Self {
Self::from_bits_retain(value & Self::MASK)
}
}
pub use crate::sys::fs::update_file_mode;
pub fn set_file_mode<P: AsRef<Path>>(at: Option<RawFd>, path: P, mode: FileMode) -> io::Result<()> {
update_file_mode(at, path, FileModeUpdate::Set(mode))
}
impl fmt::Debug for FileMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:#03o}", self.bits())
}
}

View File

@ -3,7 +3,8 @@
pub(crate) mod sys;
pub mod fs;
pub mod io;
pub mod net;
pub mod mem;
pub mod net;
pub mod signal;

View File

@ -0,0 +1,37 @@
use std::{io, os::fd::RawFd, path::Path};
use runtime::rt as yggdrasil_rt;
use yggdrasil_rt::io::{FileMetadataUpdate, FileMetadataUpdateMode, FileMode as RawFileMode};
use crate::fs::FileModeUpdate;
pub fn update_file_mode<P: AsRef<Path>>(
at: Option<RawFd>,
path: P,
mode: FileModeUpdate,
) -> io::Result<()> {
// TODO make a soft error?
let path_str = path.as_ref().to_str().expect("Path is not a valid string");
let raw_update_mode = match mode {
FileModeUpdate::Set(mode) => {
let raw_file_mode = unsafe { RawFileMode::from_raw(mode.bits()) };
FileMetadataUpdateMode::Set(raw_file_mode)
}
FileModeUpdate::Modify { set, clear } => {
let raw_set_mode = unsafe { RawFileMode::from_raw(set.bits()) };
let raw_clear_mode = unsafe { RawFileMode::from_raw(clear.bits()) };
FileMetadataUpdateMode::Modify {
set: raw_set_mode,
clear: raw_clear_mode,
}
}
};
unsafe {
yggdrasil_rt::sys::update_metadata(
at,
path_str,
&FileMetadataUpdate::Permissions(raw_update_mode),
)
.map_err(io::Error::from)
}
}

View File

@ -1,3 +1,4 @@
pub mod fs;
pub mod mem;
pub mod pid;
pub mod pipe;

View File

@ -1,22 +1,96 @@
#![feature(rustc_private, yggdrasil_os)]
use std::{
env,
os::yggdrasil::io::{self, FileMetadataUpdate, FileMetadataUpdateMode, RawFileMode},
};
#![feature(iter_chain)]
use std::{env, iter, process::ExitCode};
fn main() {
let args: Vec<_> = env::args().collect();
if args.len() != 3 {
panic!();
}
use cross::fs::{update_file_mode, FileMode, FileModeUpdate};
let mode_str = &args[1];
let filename = &args[2];
fn parse_delta_bit(
bit: &str,
set: &mut FileMode,
clear: &mut FileMode,
) -> Result<(), &'static str> {
let chars = bit.as_bytes();
let (target, plus_or_minus, bit) = match chars.len() {
2 => (b'a', chars[0], chars[1]),
3 => (chars[0], chars[1], chars[2]),
_ => return Err("Expected mode to be in format X[+-]Y or [+-]Y"),
};
let (add_in, remove_in) = match plus_or_minus {
b'+' => (set, clear),
b'-' => (clear, set),
_ => return Err("Expected +/- sign"),
};
let mask = match (target, bit) {
(b'a', b'r') => FileMode::USER_READ | FileMode::GROUP_READ | FileMode::OTHER_READ,
(b'a', b'w') => FileMode::USER_WRITE | FileMode::GROUP_WRITE | FileMode::OTHER_WRITE,
(b'a', b'x') => FileMode::USER_EXEC | FileMode::GROUP_EXEC | FileMode::OTHER_EXEC,
(b'u', b'r') => FileMode::USER_READ,
(b'u', b'w') => FileMode::USER_WRITE,
(b'u', b'x') => FileMode::USER_EXEC,
(b'g', b'r') => FileMode::GROUP_READ,
(b'g', b'w') => FileMode::GROUP_WRITE,
(b'g', b'x') => FileMode::GROUP_EXEC,
(b'o', b'r') => FileMode::OTHER_READ,
(b'o', b'w') => FileMode::OTHER_WRITE,
(b'o', b'x') => FileMode::OTHER_EXEC,
(_, _) => return Err("Invalid mode value"),
};
let mode_oct = u32::from_str_radix(mode_str, 8).unwrap();
*add_in |= mask;
*remove_in &= !mask;
let update =
FileMetadataUpdate::Permissions(RawFileMode::new(mode_oct), FileMetadataUpdateMode::Set);
io::update_metadata(filename, &update).unwrap();
Ok(())
}
fn parse_mode_update(text: &str) -> Result<FileModeUpdate, &'static str> {
if let Some(digits) = text.strip_prefix("0") {
// Decode as absolute octal
let bits = u32::from_str_radix(digits, 8).map_err(|_| "Expected octal permission mask")?;
Ok(FileModeUpdate::Set(FileMode::from(bits)))
} else {
let mut set = FileMode::empty();
let mut clear = FileMode::empty();
// Try to decode as a sequence of +x,-y,+z... flags
for bit in text.split(',') {
parse_delta_bit(bit.trim_matches(',').trim(), &mut set, &mut clear)?;
}
Ok(FileModeUpdate::Modify { set, clear })
}
}
fn usage(invocation: &str) {
eprintln!("Usage: {invocation} OCT-MODE FILENAME [FILENAME...]");
eprintln!(" OR {invocation} [ugoa][+-][rwx] FILENAME [FILENAME...]");
}
fn run<I: Iterator<Item = String>>(mut args: I) -> Result<ExitCode, &'static str> {
let mode_str = args.next().ok_or("Too few arguments")?;
let first = args.next().ok_or("No filenames provided")?;
let args = iter::chain(iter::once(first), args);
let mode = parse_mode_update(&mode_str)?;
let mut code = ExitCode::SUCCESS;
for arg in args {
match update_file_mode(None, &arg, mode) {
Ok(()) => (),
Err(error) => {
eprintln!("{arg}: {error}");
code = ExitCode::FAILURE;
}
}
}
Ok(code)
}
fn main() -> ExitCode {
let mut args = env::args();
let invocation = args.next().unwrap();
match run(args) {
Ok(code) => code,
Err(usage_error) => {
eprintln!("{invocation}: {usage_error}");
usage(&invocation);
ExitCode::FAILURE
}
}
}

View File

@ -141,7 +141,7 @@ fn lex_identifier(i: &str) -> IResult<&str, &str> {
recognize(many1_count(alt((alphanumeric1, is_a("-_")))))(i)
}
fn lex_filename(i: &str) -> IResult<&str, &str> {
recognize(many1_count(alt((alphanumeric1, is_a("./-_:+")))))(i)
recognize(many1_count(alt((alphanumeric1, is_a("./-_:+,")))))(i)
}
fn lex_braced_var(i: &str) -> IResult<&str, &str> {