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 libk_util::ext::OptionExt;
|
||||||
use yggdrasil_abi::{
|
use yggdrasil_abi::{
|
||||||
error::Error,
|
error::Error,
|
||||||
io::{DirectoryEntry, FileMode, GroupId, OpenOptions, UserId},
|
io::{
|
||||||
|
DirectoryEntry, FileMetadataUpdate, FileMetadataUpdateMode, FileMode, GroupId, OpenOptions,
|
||||||
|
UserId,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::vfs::{
|
use crate::vfs::{
|
||||||
@ -263,6 +266,34 @@ impl Node {
|
|||||||
Ok(*metadata)
|
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
|
// TODO clarify directory size
|
||||||
/// Returns the size in bytes of the node
|
/// Returns the size in bytes of the node
|
||||||
pub fn size(self: &NodeRef) -> Result<u64, Error> {
|
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(
|
pub(crate) fn update_metadata(
|
||||||
at: Option<RawFd>,
|
at: Option<RawFd>,
|
||||||
_path: &str,
|
path: &str,
|
||||||
_update: &FileMetadataUpdate,
|
update: &FileMetadataUpdate,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let thread = Thread::current();
|
let thread = Thread::current();
|
||||||
let process = thread.process();
|
let process = thread.process();
|
||||||
|
|
||||||
run_with_io_at(&process, at, |_at, _io| {
|
run_with_io_at(&process, at, |at, mut io| {
|
||||||
todo!();
|
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
|
/// Describes an operation to perform when updating certain file metadata elements
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum FileMetadataUpdateMode {
|
pub enum FileMetadataUpdateMode {
|
||||||
/// value = new
|
/// Assign a new file mode
|
||||||
Set,
|
Set(FileMode),
|
||||||
/// value = old | new
|
/// Set and/or clear existing file mode bits
|
||||||
Or,
|
Modify {
|
||||||
/// value = old & new
|
/// Bits to set
|
||||||
And,
|
set: FileMode,
|
||||||
/// value = old & !new
|
/// Bits to clear
|
||||||
AndNot,
|
clear: FileMode,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Describes how file times are updated
|
/// Describes how file times are updated
|
||||||
@ -34,7 +35,7 @@ pub struct FileTimesUpdate {
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum FileMetadataUpdate {
|
pub enum FileMetadataUpdate {
|
||||||
/// Changes file permissions
|
/// Changes file permissions
|
||||||
Permissions(FileMode, FileMetadataUpdateMode),
|
Permissions(FileMetadataUpdateMode),
|
||||||
/// Changes file mtime/atime/ctime
|
/// Changes file mtime/atime/ctime
|
||||||
Times(FileTimesUpdate),
|
Times(FileTimesUpdate),
|
||||||
}
|
}
|
||||||
|
1
userspace/Cargo.lock
generated
1
userspace/Cargo.lock
generated
@ -536,6 +536,7 @@ checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
|
|||||||
name = "cross"
|
name = "cross"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bitflags 2.8.0",
|
||||||
"libc",
|
"libc",
|
||||||
"runtime",
|
"runtime",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
|
@ -31,6 +31,7 @@ clap = { version = "4.5.20", features = ["std", "derive", "help", "usage"], defa
|
|||||||
clap-num = "1.1.1"
|
clap-num = "1.1.1"
|
||||||
serde_json = "1.0.132"
|
serde_json = "1.0.132"
|
||||||
serde = { version = "1.0.214", features = ["derive"] }
|
serde = { version = "1.0.214", features = ["derive"] }
|
||||||
|
bitflags = "2.6.0"
|
||||||
bytemuck = "1.19.0"
|
bytemuck = "1.19.0"
|
||||||
thiserror = "1.0.64"
|
thiserror = "1.0.64"
|
||||||
env_logger = "0.11.5"
|
env_logger = "0.11.5"
|
||||||
|
@ -4,6 +4,7 @@ version = "0.1.0"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
bitflags.workspace = true
|
||||||
|
|
||||||
[target.'cfg(target_os = "yggdrasil")'.dependencies]
|
[target.'cfg(target_os = "yggdrasil")'.dependencies]
|
||||||
yggdrasil-rt.workspace = true
|
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(crate) mod sys;
|
||||||
|
|
||||||
|
pub mod fs;
|
||||||
pub mod io;
|
pub mod io;
|
||||||
pub mod net;
|
|
||||||
pub mod mem;
|
pub mod mem;
|
||||||
|
pub mod net;
|
||||||
pub mod signal;
|
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 mem;
|
||||||
pub mod pid;
|
pub mod pid;
|
||||||
pub mod pipe;
|
pub mod pipe;
|
||||||
|
@ -1,22 +1,96 @@
|
|||||||
#![feature(rustc_private, yggdrasil_os)]
|
#![feature(iter_chain)]
|
||||||
use std::{
|
use std::{env, iter, process::ExitCode};
|
||||||
env,
|
|
||||||
os::yggdrasil::io::{self, FileMetadataUpdate, FileMetadataUpdateMode, RawFileMode},
|
|
||||||
};
|
|
||||||
|
|
||||||
fn main() {
|
use cross::fs::{update_file_mode, FileMode, FileModeUpdate};
|
||||||
let args: Vec<_> = env::args().collect();
|
|
||||||
if args.len() != 3 {
|
fn parse_delta_bit(
|
||||||
panic!();
|
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"),
|
||||||
|
};
|
||||||
|
|
||||||
|
*add_in |= mask;
|
||||||
|
*remove_in &= !mask;
|
||||||
|
|
||||||
|
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)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mode_str = &args[1];
|
Ok(FileModeUpdate::Modify { set, clear })
|
||||||
let filename = &args[2];
|
}
|
||||||
|
}
|
||||||
let mode_oct = u32::from_str_radix(mode_str, 8).unwrap();
|
|
||||||
|
fn usage(invocation: &str) {
|
||||||
let update =
|
eprintln!("Usage: {invocation} OCT-MODE FILENAME [FILENAME...]");
|
||||||
FileMetadataUpdate::Permissions(RawFileMode::new(mode_oct), FileMetadataUpdateMode::Set);
|
eprintln!(" OR {invocation} [ugoa][+-][rwx] FILENAME [FILENAME...]");
|
||||||
|
}
|
||||||
io::update_metadata(filename, &update).unwrap();
|
|
||||||
|
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)
|
recognize(many1_count(alt((alphanumeric1, is_a("-_")))))(i)
|
||||||
}
|
}
|
||||||
fn lex_filename(i: &str) -> IResult<&str, &str> {
|
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> {
|
fn lex_braced_var(i: &str) -> IResult<&str, &str> {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user