vfs/user: implement chmod utility
This commit is contained in:
parent
e330db1e55
commit
c35a61fb7f
@ -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> {
|
||||
|
@ -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(())
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -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
1
userspace/Cargo.lock
generated
@ -536,6 +536,7 @@ checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
|
||||
name = "cross"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"libc",
|
||||
"runtime",
|
||||
"tempfile",
|
||||
|
@ -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"
|
||||
|
@ -4,6 +4,7 @@ version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
bitflags.workspace = true
|
||||
|
||||
[target.'cfg(target_os = "yggdrasil")'.dependencies]
|
||||
yggdrasil-rt.workspace = true
|
||||
|
46
userspace/lib/cross/src/fs.rs
Normal file
46
userspace/lib/cross/src/fs.rs
Normal 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())
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
37
userspace/lib/cross/src/sys/yggdrasil/fs.rs
Normal file
37
userspace/lib/cross/src/sys/yggdrasil/fs.rs
Normal 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)
|
||||
}
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
pub mod fs;
|
||||
pub mod mem;
|
||||
pub mod pid;
|
||||
pub mod pipe;
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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> {
|
||||
|
Loading…
x
Reference in New Issue
Block a user