Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions executables/shell/command_line/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"Failed to resolve domain: {}": "Failed to resolve domain: {}",
"Failed to set current directory: {}": "Failed to set current directory: {}",
"Failed to set environment variable: {}": "Failed to set environment variable: {}",
"Failed to set metadata: {}": "Failed to set metadata: {}",
"Failed to set task user: {}": "Failed to set task user: {}",
"Failed to tokenize command line": "Failed to tokenize command line",
"Format error": "Format error",
Expand Down
1 change: 1 addition & 0 deletions executables/shell/command_line/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"Failed to resolve domain: {}": "Échec de la résolution du domaine: {}",
"Failed to set current directory: {}": "Échec de la définition du répertoire courant: {}",
"Failed to set environment variable: {}": "Échec de la définition de la variable d'environnement: {}",
"Failed to set metadata: {}": "Échec de la définition des métadonnées : {}",
"Failed to set task user: {}": "Échec de la définition de l'utilisateur de la tâche: {}",
"Failed to tokenize command line": "Échec de la tokenisation de la ligne de commande",
"Format error": "Erreur de formatage",
Expand Down
9 changes: 9 additions & 0 deletions executables/shell/command_line/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ mod ping;
mod print_working_directory;
mod statistics;
mod tail;
mod touch;
mod web_request;
mod which;
mod word_count;
Expand Down Expand Up @@ -44,6 +45,7 @@ use self::{
print_working_directory::PrintWorkingDirectoryCommand,
statistics::StatisticsCommand,
tail::TailCommand,
touch::TouchCommand,
web_request::WebRequestCommand,
which::WhichCommand,
word_count::WordCountCommand,
Expand Down Expand Up @@ -99,6 +101,7 @@ pub enum UserCommandKind {
WordCount,
Head,
Tail,
Touch,
}

pub fn resolve_user_command(name: &str) -> Option<UserCommandKind> {
Expand All @@ -125,6 +128,7 @@ pub fn resolve_user_command(name: &str) -> Option<UserCommandKind> {
"wc" => Some(UserCommandKind::WordCount),
"head" => Some(UserCommandKind::Head),
"tail" => Some(UserCommandKind::Tail),
"touch" => Some(UserCommandKind::Touch),
_ => None,
}
}
Expand Down Expand Up @@ -190,6 +194,7 @@ where
UserCommandKind::WordCount => WordCountCommand.execute(context, options, paths).await,
UserCommandKind::Head => HeadCommand.execute(context, options, paths).await,
UserCommandKind::Tail => TailCommand.execute(context, options, paths).await,
UserCommandKind::Touch => TouchCommand.execute(context, options, paths).await,
}
}

Expand Down Expand Up @@ -298,6 +303,10 @@ mod tests {
resolve_user_command("tail"),
Some(UserCommandKind::Tail)
));
assert!(matches!(
resolve_user_command("touch"),
Some(UserCommandKind::Touch)
));
assert!(resolve_user_command("unknown").is_none());
}
}
214 changes: 214 additions & 0 deletions executables/shell/command_line/src/commands/touch.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
use crate::{Error, Result};
use alloc::{borrow::ToOwned, vec::Vec};
use getargs::{Arg, Options};
use xila::{
file_system::{AccessFlags, CreateFlags, Error as FileSystemError, Flags, Path},
virtual_file_system::{self, File},
};

use super::{CommandContext, UserCommand};

pub struct TouchCommand;

impl UserCommand for TouchCommand {
async fn execute<'a, I, C>(
&self,
context: &mut C,
options: &mut Options<&'a str, I>,
_paths: &[&Path],
) -> Result<()>
where
I: Iterator<Item = &'a str>,
C: CommandContext,
{
execute_touch(context, options).await
}
}

struct TouchParameters<'a> {
no_create: bool,
access: bool,
modification: bool,
paths: Vec<&'a str>,
}

fn resolve_path<C: CommandContext>(
context: &C,
path: &str,
) -> Result<xila::file_system::PathOwned> {
let path = Path::from_str(path);

if path.is_absolute() {
Ok(path.to_owned())
} else {
context
.current_directory_owned()
.join(path)
.ok_or(Error::FailedToJoinPath)
}
}

fn parse_touch_parameters<'a, I>(options: &mut Options<&'a str, I>) -> Result<TouchParameters<'a>>
where
I: Iterator<Item = &'a str>,
{
let mut no_create = false;
let mut access = false;
let mut modification = false;
let mut paths = Vec::new();

while let Some(argument) = options.next_arg()? {
match argument {
Arg::Long("no-create") | Arg::Short('c') => {
no_create = true;
}
Arg::Long("access") | Arg::Short('a') => {
access = true;
}
Arg::Long("modification") | Arg::Short('m') => {
modification = true;
}
Arg::Positional(path) => {
paths.push(path);
}
_ => {
return Err(Error::InvalidOption);
}
}
}

if paths.is_empty() {
return Err(Error::MissingPositionalArgument("path"));
}

Ok(TouchParameters {
no_create,
access,
modification,
paths,
})
}

fn resolve_update_mask(access: bool, modification: bool) -> (bool, bool) {
if !access && !modification {
(true, true)
} else {
(access, modification)
}
}

async fn touch_one_path<C: CommandContext>(
context: &C,
path: &Path,
no_create: bool,
update_access: bool,
update_modification: bool,
) -> Result<()> {
let virtual_file_system = virtual_file_system::get_instance();
let exists = match virtual_file_system.get_statistics(&path).await {
Ok(_) => true,
Err(xila::virtual_file_system::Error::FileSystem(FileSystemError::NotFound)) => false,
Err(error) => return Err(Error::FailedToGetMetadata(error)),
};

if !exists {
if no_create {
return Ok(());
}

let _file = File::open(
virtual_file_system,
context.task_id(),
path,
Flags::new(AccessFlags::Write, Some(CreateFlags::Create), None),
)
.await
.map_err(Error::FailedToOpenFile)?;
}

virtual_file_system
.set_times(context.task_id(), path, update_access, update_modification)
.await
.map_err(Error::FailedToSetMetadata)
}

async fn execute_touch<'a, I, C>(context: &mut C, options: &mut Options<&'a str, I>) -> Result<()>
where
I: Iterator<Item = &'a str>,
C: CommandContext,
{
let parameters = parse_touch_parameters(options)?;
let (update_access, update_modification) =
resolve_update_mask(parameters.access, parameters.modification);

let mut first_error: Option<Error> = None;

for path in parameters.paths {
let path = resolve_path(context, path)?;
let result = touch_one_path(
context,
&path,
parameters.no_create,
update_access,
update_modification,
)
.await;

if let Err(error) = result {
if first_error.is_none() {
first_error = Some(error);
}
}
}

if let Some(error) = first_error {
return Err(error);
}

Ok(())
}

#[cfg(test)]
mod tests {
use getargs::Options;

use super::{parse_touch_parameters, resolve_update_mask};

#[test]
fn resolves_default_mask_to_access_and_modification() {
assert_eq!(resolve_update_mask(false, false), (true, true));
}

#[test]
fn resolves_access_only_mask() {
assert_eq!(resolve_update_mask(true, false), (true, false));
}

#[test]
fn resolves_modification_only_mask() {
assert_eq!(resolve_update_mask(false, true), (false, true));
}

#[test]
fn parses_flags_and_multiple_paths() {
let input = ["-c", "-a", "first.txt", "second.txt"];
let mut options = Options::new(input.into_iter());

let parsed = parse_touch_parameters(&mut options).unwrap();

assert!(parsed.no_create);
assert!(parsed.access);
assert!(!parsed.modification);
assert_eq!(parsed.paths, ["first.txt", "second.txt"]);
}

#[test]
fn fails_when_no_path_is_provided() {
let input = ["-m"];
let mut options = Options::new(input.into_iter());

let parsed = parse_touch_parameters(&mut options);

assert!(parsed.is_err());
}
}
4 changes: 4 additions & 0 deletions executables/shell/command_line/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ pub enum Error {
FailedToResolve(network::Error),
FailedToCreateSocket(network::Error),
Format,
FailedToSetMetadata(virtual_file_system::Error),
}

impl<A: getargs::Argument> From<getargs::Error<A>> for Error {
Expand Down Expand Up @@ -168,6 +169,9 @@ impl Display for Error {
Error::FailedToGetMetadata(error) => {
write!(formatter, translate!("Failed to get metadata: {}"), error)
}
Error::FailedToSetMetadata(error) => {
write!(formatter, translate!("Failed to set metadata: {}"), error)
}
Error::FailedToSetCurrentDirectory(error) => {
write!(
formatter,
Expand Down
39 changes: 39 additions & 0 deletions modules/virtual_file_system/src/file_system/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -833,4 +833,43 @@ impl VirtualFileSystem {
let attributes = Attributes::new().set_permissions(permissions);
Self::set_attributes(file_system.file_system, relative_path, &attributes).await
}

pub async fn set_times(
&self,
task: TaskIdentifier,
path: impl AsRef<Path>,
access: bool,
modification: bool,
) -> Result<()> {
if !access && !modification {
return Ok(());
}

let path = path.as_ref();
let file_systems = self.file_systems.read().await;

let (file_system, relative_path, _) =
Self::get_file_system_from_path(&file_systems, &path)?;
let (time, current_user, _) = self.get_time_user_group(task).await?;

Self::check_permissions(
file_system.file_system,
relative_path,
Permission::Write,
current_user,
)
.await?;

let mut attributes = Attributes::new();

if access {
attributes = attributes.set_access(time);
}

if modification {
attributes = attributes.set_modification(time);
}

Self::set_attributes(file_system.file_system, relative_path, &attributes).await
}
}
Loading